かつてつまずいたことがあったので、
一度まとめておこうと思います。
まずCの標準入力と言えば、
以下が思いつくかと思います。
全て"stdio.h"に定義されています。
関数 | フォーマット(書式) |
---|---|
|
|
|
|
|
|
|
|
これらは名前が違えば、
使い方も少し違います。
ちなみにgets()の使用は
すでに非推奨になっているので、
今回はそれ以外について説明します。
標準入力関数を理解するうえで、
最もやっかいで頭を悩ますのが、
バッファ(入力ストリーム)の存在です。
ここでいうバッファとは、
実際にユーザが打ち込んだ文字列や数値を
一時的に入れる貯蔵庫を言います。
標準入力関数をコールする際に、
格納先であるポインタ(変数のアドレス)を
一緒に指定します。
※scanf()のイメージです。

※¥n(改行)は、エスケープシーケンスの1つです。
エンターキーが押されたことを意味します。
上記の画像のように、
まずユーザが入力した文字列や数は
バッファに格納されます。
そして関数をコールすることで
バッファから格納先へと移します。
コールした関数によって挙動が異なります。
例えば、改行やスペースの
取り扱い方が違ってきます。
ではどのように挙動が異なるのでしょうか。
実際に関数を順番に見ていきます。
scanf()の仕組みと改行・スペース
scanf()は文字列や数値の入力を受け付けます。
なお、abcなどの文字は受け付けますが、
改行やスペースの扱いには注意です。
期待せぬ結果を招くことになります。
実際に見てみましょう。
使うコードはこちらです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include<stdio.h> int main(void){ char c[5]; printf("[+]First scanf(): "); scanf("%s", c); printf("[+]Result: %s\n", c); printf("0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n\n",c[0], c[1], c[2], c[3], c[4]); printf("[+]Second scanf(): "); scanf("%s", c); printf("[+]Result: %s\n", c); printf("0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n\n", c[0], c[1], c[2], c[3], c[4]); return 0; } |
上のコードを実行すると、文字・数値
どちらを入力しても正常に表示されます。
これはご存知かと思いますので、
文字や数値だけを入力した実行結果は省きます。
ここからがややこしくなります。
例えば次のように入力してみます。
a (スペース) dog

すると上記の結果になりました。
scanfではスペースの入力受付は出来ません。
ちなみにスペースをAsciiコードの
16進数で表現すると 0x20 です。
"a スペース dog" が入力されたら
まずはバッファに送られます。
scanf() も後で紹介するfgets() も
複数の文字の入力を読み込みますが、
実際はバッファから1文字ずつ順に
読み込んでいきます。
つまりscanf関数によって、
バッファから1文字ずつ順に
バッファから格納先へと移されます。
繰り返しますが、scanf()は
1文字ずつ順番に読み込むので、
最初の a が来たときは通常通り格納します。
ところが2文字目にスペースがきたので、
scanf()は読み込めず、終了します。
scanf()はスペースや改行に遭遇すると
読み込みを終了します。
そして2回目のscanf()で、
本来は入力を受け付ける動作を
期待していたのですが、
まだバッファに文字が残っているので、
それらを勝手に読み込みに行きます。
scanfではスペースを読み込めないので、
残っていたスペースは無視されます。
なのでバッファ中にスペース以降に保存された
"dog" を読み込みます。
ここでも g の次に改行(\n)に遭遇するので、
scanf()は読み込みを終了します。
ここで終了するとバッファにまだ
改行(\n)が残ったままになるので、
次の標準入力処理には注意が必要になります。
なのでscanf()を実際に使用する際は、
少し難しい話しになりますが、
「読み飛ばし」や「代入抑止」などの
テクニックを併せて使う必要があります。
例えば次のようなものがあります。
scanf("%100[^\n]%*c", str) |
最大100文字で改行まで読み込み、そして最後の改行は読み捨てる |
要素を分解すると、
%[\n]と%cが基本になります。
%[\n]は改行を読み込む
^ で逆の意味になるので%[^\n]で、
改行以外という意味になります。
そこに100とあるのは、
配列があふれるのを防ぐ目的です。
%cは一文字読み込みます。
そして * は、それを読み捨てる
という意味です。
よって改行以外(改行直前まで)を読み込み、
その次の文字(=改行)を読み捨てる、
ということになるわけです。
ここで頭がパンクしそうになった場合は、
これだけ覚えておいてください。
「scanfはスペースや改行で読み込み終了!」
ということだけを注意して下さい。
fgets()と改行・スペース
scanf()の次はfgets()です。
コードは以下を用います。
1 2 3 4 5 6 7 8 9 10 11 12 |
#include<stdio.h> int main(void){ char c[7]; printf("[+]First fgets(): "); fgets(c, sizeof(c), stdin); printf("[+]Result: %s\n", c); printf("After: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n\n",c[0], c[1], c[2], c[3], c[4], c[5], c[6]); return 0; } |
fgets()は以下を引数に指定します。
格納先ポインタ
読み込みサイズ
ストリーム(標準入力するときは stdin )
scanf() と同様に a dog と入力してみます。

fgets() は scanf()と違って、
改行・スペースに遭遇しても
しっかりと指定したサイズ分読み込みます。
ちなみに出力した16進数は、
これらを意味しています。
a = 0x61
スペース = 0x20
d = 0x64
o = 0x6f
g = 0x67
改行 = 0x0a
これだけ聞いたら、それじゃ
scanf()よりもfgets()の方が便利?じゃんか
って思うかもしれません。
実はfgets()にも注意が必要な点があります。
scanf()とfgets()の実行結果を
それぞれ見比べてみて下さい。
気づきましたでしょうか。
scanf()の場合、Resultのすぐ下に
16進数で中身が出力されています。
一方でfgets()の方は、
ResultとAfterの行間に1行空いています。
本来は空かないはずですが、
fgets()で表示した "a dog" には、
改行も一緒に出力してしまっているからです。
つまりfgets()は改行も読み込む分、
中身を出力したときに改行も一緒に、
出力されてしまう羽目になり、
見た目がおかしなことになりかねません。
実はfgets()にはまだ注意点があります。
次のコードと実行結果を見て下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include<stdio.h> int main(void){ char c[7] = {'a','a','a','a','a','a','a'}; printf("Before: 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n\n",c[0], c[1], c[2], c[3], c[4], c[5], c[6]); printf("[+]First fgets(): "); fgets(c, sizeof(c), stdin); printf("[+]Result: %s\n", c); printf("0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n\n",c[0], c[1], c[2], c[3], c[4], c[5], c[6]); return 0; } |
配列の初期化に文字 a を入れています。
Asiciiコードは0x61になります。
そこに fgets() で入力を行います。
とりあえず a だけを入力します。
すると中身が次のようになります。

1バイト目の0x61は入力した a です。
2バイト目は改行を意味する0x0aです。
問題の3バイト目です。
0x00 はどこから来たのでしょうか。
実はfgets()はこのような仕様になっています。
指定した文字数に対して、
入力した文字 + 改行 を読み込んでも
まだ余裕があれば、最後に0x00を入れます。
例えば、もしfgets()をコールした時に
7バイトの読み込みを指定した場合を考えます。
ここで入力に a a a の3バイトを入力したら、
配列には a が3つと改行の計4バイト分と、
最後に0x00が格納されます。
つまり合計5バイト埋まることになります。
次にもし入力時に a a a a a a の6バイトを
入力した場合、改行は格納されずに、
7バイト目には0x00が格納されることになります。
こんな感じです。
After_2 の箇所はただfgets()をしただけです。
改行を示す 0x0a の後ろに、fgets()の仕様に従って、
0x00が格納されています。

確かにscanf()と違って、
改行・スペースを読み込む点は
とても便利?ですが、上述したような点に
注意する必要があります。
getchar()の使い方と改行・スペース
これまでの2つの関数と違い、
getchar() は1文字の入力のみ受け付けます。
改行・スペースも読み込みます。
1 2 3 4 5 6 7 8 9 10 11 |
#include<stdio.h> int main(void){ char c; printf("[+]First getchar(): "); c = getchar(); printf("[+]Result: 0x%02x\n", c); return 0; } |
実行結果は以下になります。
最初は 'a' を入力し、
2回目はただエンターを押しただけす。
エンターを押しただけなので、
改行意味する '\n' = 0x0a が入ります。

実はこの getchar() は、
scanf() で読み込むことが出来なかった
改行0x0a だけを読み込むために
使われることがしばしあります。
使い方はscanf() の後に、
getchar() の一行を追加するだけなので、
とても簡単です。
以上です。
最後まで読んでいただきありがとうございました。