LoT ラブオンテック

非モテ男の、非モテ男による、【女の子にモテたい】非モテ男のためIT活用ブログ

プログラミング

scanf/fgets/getchar/getsの違いとエスケープシーケンス(Cの標準入力)のまとめ

更新日:



かつてつまずいたことがあったので、

一度まとめておこうと思います。

まずCの標準入力と言えば、

以下が思いつくかと思います。


全て"stdio.h"に定義されています。

関数 フォーマット(書式)

scanf()

int scanf(const char *format, ....)

fgets()

char *fgets(char *s, int size, FILE *stream)

getchar()

int getchar(void)

gets()

char *gets(char *s)


これらは名前が違えば、

使い方も少し違います。



ちなみにgets()の使用は

すでに非推奨になっているので、

今回はそれ以外について説明します。



標準入力関数を理解するうえで、

最もやっかいで頭を悩ますのが、

バッファ(入力ストリーム)
の存在です。



ここでいうバッファとは、

実際にユーザが打ち込んだ文字列や数値を

一時的に入れる貯蔵庫を言います。



標準入力関数をコールする際に、

格納先であるポインタ(変数のアドレス)を

一緒に指定します。


※scanf()のイメージです。

¥n(改行)は、エスケープシーケンスの1つです。
 エンターキーが押されたことを意味します。




上記の画像のように、

まずユーザが入力した文字列や数は

バッファに格納されます。



そして関数をコールすることで

バッファから格納先へと移します。



コールした関数によって挙動が異なります。

例えば、エスケープシーケンスの

取り扱い方が違ってきます。



ではどのように挙動が異なるのでしょうか。

実際に関数を順番に見ていきます。



scanf()の仕組みとエスケープシーケンス


scanf()は文字列や数値の入力を受け付けます。



なお、abcなどの文字は受け付けますが、

エスケープシーケンスの扱いには注意です。

期待せぬ結果を招くことになります。



実際に見てみましょう。

使うコードはこちらです。


上のコードを実行すると、文字・数値

どちらを入力しても正常に表示されます。

これはご存知かと思いますので、

文字や数値だけを入力した実行結果は省きます。




ここからがややこしくなります。

例えば次のように入力してみます。

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はエスケープシーケンスが読み込めない」

ということだけを注意して下さい。


fgets()とエスケープシーケンス



scanf()の次はfgets()です。

コードは以下を用います。



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()にはまだ注意点があります。

次のコードと実行結果を見て下さい。


配列の初期化に文字 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文字の入力のみ受け付けます。

エスケープシーケンスも読み込みます。





実行結果は以下になります。

最初は 'a' を入力し、

2回目はただエンターを押しただけす。

エンターを押しただけなので、

改行意味する '\n' = 0x0a が入ります。





実はこの getchar() は、

scanf() で読み込むことが出来なかった

改行0x0a だけを読み込むために

使われることがしばしあります。



使い方はscanf() の後に、

getchar() の一行を追加するだけなので、

とても簡単です。




以上です。

最後まで読んでいただきありがとうございました。


-プログラミング

Copyright© LoT ラブオンテック , 2020 All Rights Reserved Powered by STINGER.