C言語入門
第 05 章 キーボードと画面を使った入出力

目的

前章までで、C言語の基本的な構文については一とおりの説明を終えました。 簡単なプログラムであれば、今までに紹介したC言語の構文をつかって書くことができます。

本章では、ここまでの復習を兼ねて簡単なプログラムを作成してみます。 あわせて次の 2 つの新しい内容についても説明します。

  1. プログラムの動作や計算条件を変化させるため、キーボードからの入力と画面への出力方法について理解する。
  2. 全ての関数から参照できる大域変数について理解する。

printf 関数の使い方

これまでメッセージや数値を画面に表示するために、C言語が用意している printf 関数を使ってきました。printf 関数は %d や %f のように指定することで、整数や実数を表示できました。%d や %f はフォーマット指定子と呼ばれ、実際にはもっとたくさんの種類があります。フォーマット指定子を組み合わせることで表示の形式を様々に変えることができます。

よく使われるフォーマット指定子

よく使われるフォーマット指定子には次のようなものがあります。

フォーマット指定子表示する文字列対応するデータ型
%d符号あり整数を表示するint, short
%u符号なし整数を表示するunsigned int, unsigned short
%f浮動小数点数を表示するfloat, double
%c文字を表示するchar, unsigned char
%s文字列(メッセージ)を表示するchar*, unsigned char*
%%%を表示する

printf 関数は少し特殊な関数で、1 個以上の引数をとる関数です。第 1 引数がダブルクオート " で囲まれた文字列で、基本的にはこの文字列が画面に出力されます。ただしこの文字列の中にフォーマット指定子が何個か含まれていると、その部分はそれぞれ第 2 引数以降の引数の値で置き換えられます。例えば

printf("%d + %d = %d\n", x, y, x + y);
という関数呼び出しを考えましょう。x と y は int 型の変数です。第 1 引数の文字列には 3 つのフォーマット指定子 %d が含まれています(\n は改行を表します)。これらは左から順に、第 2 引数 x の値、第 3 引数 y の値、第 4 引数 x + y  の値で置き換えらます。最終的に画面に表示されるのは、置き換えがすんだ後の文字列です。変数 x の値が 3、y の値が 4 ならば
3 + 4 = 7
と表示されます。なお、例えば、もし変数 y が int 型ではなくて double 型であれば、2 番目のフォーマット指定子は %d ではなく %f にします。

表現方法の変更

フォーマット指定子には、桁数の指定を加えることもできます。
%《表示形式》《全桁数》.《小数点以下桁数》《フォーマット指定子》

《表示形式》には 0-+ の3つの文字を指定できます。省略も可能です。それぞれの意味は以下のようになります。省略すると右寄せの意味になります。

形式意味使用例結果
0指定された桁数に満たなければ 0 を入れるprintf("%05d", 53)00053
-指定された桁数に満たなければ左寄せするprintf("%-5d, %5d", 30)30 , 30
+数値に符号を付けて表示するprintf("%+d, %+d", 3, -5)+3, -5

《全桁数》にはフォーマット指定子を置き換えた際の最大文字数を指定します。 これも省略して構いません。 桁数に満たない場合は右寄せで表現されます。 以下に例を示します。

#include <stdio.h>

int main() {
printf("%5d\n", 1);
printf("%5d\n", 10);
printf("%5d\n", 100);
printf("%5d\n", 1000);
printf("%5d\n", 10000);
printf("%5d\n", 100000);

printf("%-5d\n", 1);
printf("%-5d\n", 10);
printf("%-5d\n", 100);
printf("%-5d\n", 1000);
printf("%-5d\n", 10000);
printf("%-5d\n", 100000);

return 0;
}
実行結果は次のようになるはずです。
    1
10
100
1000
10000
100000
1
10
100
1000
10000
100000
《小数点以下桁数》では、表示する小数点以下の桁数を指定します。 これは数値の型が浮動小数点数の場合に意味があります。これも省略は可能です。表示する桁のうち最下位については、その下の桁が四捨五入された結果となります。
#include <stdio.h>

int main() {
printf("%12.0f\n", 3.1415926535897932);
printf("%12.1f\n", 3.1415926535897932);
printf("%12.2f\n", 3.1415926535897932);
printf("%12.3f\n", 3.1415926535897932);
printf("%12.4f\n", 3.1415926535897932);
printf("%12.5f\n", 3.1415926535897932);
printf("%12.6f\n", 3.1415926535897932);
printf("%12.7f\n", 3.1415926535897932);
printf("%12.8f\n", 3.1415926535897932);
printf("%12.9f\n", 3.1415926535897932);

return 0;
}
実行結果を示します。
           3
3.1
3.14
3.142
3.1416
3.14159
3.141593
3.1415927
3.14159265
3.141592654

演習 5−1

以下のようにy = x2/16のグラフを表示するプログラムを書いてください。 y の値は 0〜49 とします。
*                            |                            *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
* | *
*|*
なお、この問題のように文字の組み合わせでグラフを表示する場合、前章で学んだ for文と組み合わせて半角空白を連続出力して位置を調整してください。

演習 5−2

Σ1/nの値を、n = 1...100 まで計算してください。 その際数値の比較がしやすいように、数値は左寄せで表示、小数点以下の桁数も違いがわかるように表示してください。

scanf 関数を使った入力

C言語には printf 関数のような表示(画面出力)のための関数だけではなく、入力のための関数も用意されています。 その1つである scanf 関数について説明します。

scanf 関数を使うと、キーボードからの文字入力を認識して加工した結果を、変数に代入することができます。 たとえば、ユーザがキーボードから 5 2 を入力してから改行キーを押した場合、 変数に代入される値は 52 になります。5 2 とタイプして改行したのですから 52 を入力したのは当然と思うかもしれません。しかしコンピュータからしてみれば、ユーザが 5 と 2 をタイプしたらそれは数の 52 を入力したことである、という認識は必ずしも自明ではありません。scanf 関数は、そのあたりの認識アルゴリズムを実際にプログラムしたものです。例として、キーボードから入力した値を整数として変数に代入するプログラムを以下に示します。

なお、このプログラムは Eclipse 上では正しく動きません! プログラムを動作させるためには、Windows ではコマンドプロンプト、Mac OS X ではターミナルを使って、プログラムを実行してください。これは今後作成する全てのプログラムにおいて共通の制限です。scanf 関数を使うプログラムでは Eclipse 以外で動作を確認してください。

コンパイル後、コマンドプロンプトまたはターミナル内で、cd コマンドを使って実行可能ファイルがあるフォルダ(ディレクトリ)へ移動するとよいでしょう。

あるいは scanf 関数の呼び出し直前に

fflush(0);

と fflush 関数を呼び出すようにプログラムを修正すると Eclipse 上でも動くかもしれません。

#include <stdio.h>

int main() {
int input = 0;

printf("整数を入力して、改行キーを押してください\n");
scanf("%d", &input);
printf("入力した整数は%dです。\n", input);

return 0;
}
実行結果は次のようになります。
整数を入力して、改行キーを押してください
5433
入力した整数は5433です。
scanf 関数の使い方は、printf 関数の使い方に似ています。scanf 関数の最初の引数は文字列で、キーボードからの入力として期待する文字を表すフォーマット指定子です。 ここでは %d となっているため、整数が入力されることを期待しています。 続く 2 番目の引数は入力された値を代入する変数です。つまり上のプログラムの 7 行目は「キーボードからの入力を整数値に変換し、その値を変数 input に代入する」という意味になります。

ところで、scanf 関数の第 2 引数になっている変数の先頭には & がついていることに注意してください。これには深い意味があるのですが、詳しくは第 9 章で説明します。いまは「scanf 関数の引数になっている変数名の前には & をつける」と機械的に覚えておけば十分です。

scanf 関数を使う際の注意

便利な scanf 関数ですが、使う際に気をつけなければならないこともあります。それは、フォーマット指定子で %d と指定したにもかかわらず、キーボードから入力された文字列が数でないなど、例外的な入力があった場合の動作です。 そのような場合、それ以降の scanf 関数の動作がおかしくなってしまい、プログラムを終了するまで復旧しないことがあります。たとえば、以下のプログラムを見てください。
#include <stdio.h>

int main() {
int input = 0;
printf("整数を入力して、改行キーを押してください\n");
scanf("%d", &input);
printf("入力した整数は%dです。\n", input);

float input2 = 0.0;
printf("実数を入力して、改行キーを押してください\n");
scanf("%f", &input2);
printf("入力した整数は%fです。\n", input2);

return 0;
}
2 回目の scanf に渡されるフォーマット指定子は %f ですが、これは浮動小数点数の入力を期待していることを意味します。実行結果は次のようになります。
整数を入力して、改行キーを押してください
91
入力した整数は91です。
実数を入力して、改行キーを押してください
9.191
入力した整数は9.191000です。
これはキーボードからの入力が正しかった場合です。わざと間違った入力をするとどうなるでしょうか。
整数を入力して、改行キーを押してください
a bdfac badsf
入力した整数は0です。
実数を入力して、改行キーを押してください
入力した整数は0.000000です。
scanf 関数は正しい入力が与えられることを仮定して書かれているので、入力が誤っていると結果もおかしなものになってしまいます。このため、実用的に使われる(ユーザが誤った入力をしてしまう可能性がある)プログラムは scanf 関数を使うべきではありません。入力が正しくない場合のエラー処理をきちんとおこなうことは、実用的なプログラムでは大切なことです。

フォーマット指定子と変数の型

scanf 関数の第 1 引数には、printf 関数と同じくフォーマット指定子を含んだ文字列を渡すと説明しました。たとえば、浮動小数点数を入力させたい場合には、以下のように %f を使うのでした。第 2 引数の変数の型は float 型です(フォーマット指定子を %lf にすれば double 型となります)。
#include <stdio.h>

int main() {
float input;

printf("実数を入力して、改行キーを押してください\n");
scanf("%f", &input);
printf("入力した実数は%fです。\n", input);

return 0;
}
実行結果は次のようになります。
実数を入力して、改行キーを押してください
5.34534
入力した実数は5.345340です。

フォーマット指定子を使い分ければ、さまざまな型の値をキーボードから入力させることができますが、型に関して注意点があります。printf 関数、scanf 関数の両方にいえることですが、第 1 引数の文字列中のフォーマット指定子は、続く第 2 引数以降の変数の型と一致させなければなりません。型が一致していないと、プログラムの異常終了やデータの欠損などが発生する可能性があります。たとえば、以下のプログラムでは int 型の変数 input に対してフォーマット指定子 %f を使っています。このため正しい値が変数 input に代入されません。

#include <stdio.h>

int main() {
int input;

printf("実数を入力して、改行キーを押してください\n");
scanf("%f", &input);
printf("入力した実数は%dです。\n", input);

return 0;
}
実際の実行例を以下に示します。
実数を入力して、改行キーを押してください
788
入力した実数は1145372672です。

大域変数

これまでに紹介した変数は、普通に宣言して使う変数(局所変数といいます)も、関数の引数になっている変数も、関数の中でだけ有効なものでした。しかしC言語では、関数の外で宣言し、すべての関数から共有される変数もあります。そのような変数を大域変数といいます。局所変数の場合、スコープが関数の中に制限されるので、異なる関数の中で同名の変数を使っていても別の変数として扱われることを思い出してください。一方、大域変数はプログラム全体で 1 つで、全ての関数からアクセスできます。
#include <stdio.h>

/* 大域変数の宣言 */
int data = 0;

/* 大域変数の初期化 */
void init() {
data = 0;
}

/* 大域変数に値を追加*/
void add(int value) {
data = data + value;
}

/* 配列の値を表示する。 */
void show() {
printf("%d\n", data);
}

int main() {
init();
add(5);
add(3);
show();

init();
add(9);
add(15);
add(1);
show();

return 0;
}
(init 関数と add 関数の戻り値の型は void で、return 文も含みませんが、これについては後で説明します。)

変数 data はどの関数から見ても外で宣言されています。関数の中とは { } の中のことで、変数 data の宣言は 4 つある関数 init と add、show、main のどの { } の中にも含まれません。したがって、どの関数の中からも、値を参照したり、新しい値を代入したりできます。上のプログラムの実行結果は次のようになります。
8
25
add 関数を呼ぶたびに data の値が引数 value 分だけ増えます。add 関数は計算した新しい値を data に代入しますから、data の値は新しい値に変わります。変数 data は大域変数なので、この新しい値は add 関数の計算が終わっても記録されて残り、次にまた add 関数が呼ばれたり、show 関数が呼ばれたときに変数 data の現在の値として使われます。
init 関数も同様です。main 関数の中では 2 回 init 関数が呼ばれていますが、呼ばれるたびに 0 が変数 data に代入されているので、その時点までの変数 data の値が何であろうと、data の値は 0 に変わります。新しい値 0 は init 関数の終了後も記録されるので、次に add 関数が呼ばれたときの変数 data の値は 0 になります。

関数と副作用

プログラムを実行している間中ずっと必要な値を記録しておくのに、大域変数は不可欠です。しかし一般に大域変数の乱用は避けるべきだといわれています。大域変数を使わなければプログラムを書けない場合を除き、極力、普通の局所変数を使うべきです。大域変数は、プログラム中のどの関数の中ででも代入によって値を変更することができます。したがって、大域変数の値が、プログラムの実行の進展に応じて、どのように変わっていくかを正しく追いかけるのは難しいのです。大域変数の値の変化を誤解してしまうと、誤ったプログラムを書いてしまったり、他人の書いたプログラムの意味を間違って理解してしまったりします。これを避けるため、大域変数の利用には注意が必要です。

計算を実行することによって何らかの痕跡を残し、次の(未来の)計算に影響を与える場合、その計算は副作用がある、といいます。大域変数を使うと、副作用のある関数を書けてしまいます。過去に add 関数を何回どのような引数で呼んだかによって、show 関数が表示する値が変わってしまいました。add 関数は未来の show 関数による計算に影響を与えているのです。大域変数を使わなくても副作用のある関数を書いてしまうことがありますが、一般に、副作用のある関数には注意が必要で、乱用は避けるべきだといわれています。

例えば printf 関数や本章で紹介した scanf 関数も、広い意味では副作用のある関数です。printf 関数の場合、引数が変わらなければ画面表示される文字列は変わりませんが、関数を呼び出した回数分その文字列が繰り返して表示されます。画面上に残った 文字列の内容も計算結果に含まれると思うと、printf 関数も呼び出すたびに計算結果が異なるといえます。scanf 関数も入力されたデータを読み取りますから、何度も scanf 関数が呼ばれたときは、その分たくさんデータを入力しないと、入力があるまでプログラムがそこで一時停止してしまいます。何回目かの呼び出しかによって scanf 関数が一時停止するか否か動作が変わるので、scanf 関数も副作用があるといえます。

戻り値のない関数

関数によっては副作用をおこすこと自体が計算の目的であるものがあります。そのような関数の中には、そもそも戻り値がないものすらあります。printf 関数がその例ですが、上のプログラム例に登場する init 関数や add 関数もそのような関数です。

そのような関数の戻り値の型は void とします。void とは「空」の意味です。また return 文にも戻り値(を計算する式)を return の右に続けて書きません。戻り値の型が void である関数の中に含まれる return 文は必ず次のような形をとります。
return;
return 文に続けてすぐ ; を書きます。また上に示した init 関数や add 関数のように、return 文をそもそも省略することもできます。省略された場合、関数の中の文が上から順に実行されてゆき、最後の文が実行されたところで、関数の計算の実行も終了します。

戻り値のない関数も「関数」と呼ぶのには少し違和感を感じるかもしれません。実際、プログラミング言語によっては、戻り値のない関数は「関数」とは呼ばずに、手続きと呼んで区別するものもあります。

演習 5−3

キーボードから摂氏温度を実数で入力して、華氏温度を表示するプログラムを作成し、実行結果を示してください。

演習 5−4

走行距離 [km] とガソリンの消費量 [cc] を入力して燃費(ガソリン 1 リットルあたりの走行距離)を計算するプログラムを作ってください。

総合演習

第 1 章からこれまでの内容を踏まえて、以下の演習問題に取り組んでください。

総合演習 1

「数当てゲーム」を作成してください。 数当てゲームとは、あらかじめ決めた数字はユーザが当てていくゲームです。 正解の数字は以下の条件を満たすこととします。

  • 桁数は 4 桁であること。
    ○:3512, ×:91
  • 各桁の数は重複しないこと。
    ○:1234, ×:3593

ユーザが入力した値が正解に一致すればゲームは終了です。一致しない場合には、各桁の数字について比較を行い、ヒントを示します。

  • 桁と数値があたっていれば「ヒット」
    【正解】3518 : 【入力値】1592 ⇒ 1ヒット
  • 桁が違うが数値があたっていれば「ブロウ」
    【正解】9156 : 【入力値】6831 ⇒ 2ブロウ

総合演習 2

映画「ダイハード 3」にも出てくる以下の問題を解くプログラムを作成してください。

3 リットルの容器 A と 5 リットルの容器 B があります。 この二つの容器を使って 1 リットルの水を計測しようと思います。 ただし、容器 A でしか水を汲めず、容器 B では水を全部捨てることしかできません。 どのように容器を操作すればよいか、手順を以下の例のように表示してください。

容器Aに水を入れます
容器Aから容器Bに水を移します
容器Aに水を入れます
容器Aから容器Bに水を移します
容器Aに1リットル入っています

目次


Copyright 2009-2011 the Compview project, Tokyo Institute of Technology. All rights reserved.