<問題点と原因>
従来から用いられているscanf()関数による入力方法では、書式が文字である場合と数値である場合では、入力された改行文字の扱いが異なります。
例えば、1つの整数と1つの文字をキーボードから入力したい場合、 以下のようなコーディングを行ったとします。一見すると正常に動作しそうに思えます。
int dt;
char ch;
scanf("%d", &dt); /* 数値の入力 */
scanf("%c", &ch); /* 文字の入力 */
しかし、整数+[改行]を入力した時点でプログラムは終了してしまいます。これは、入力書式が文字の場合、整数の値を入力し次に押したEnter(改行文字)を、文字変数chに読み込んでしまうからです。
一方、scanf("%c", &ch)の代わりに scanf("%d", &dt);とすれば、もう一度、整数を正しく読み込んでくれることがわかります。これは、書式が数値の場合は、scanf()が改行文字を無視することにその原因があります。
int dt;
char ch;
scanf("%d", &dt); /* 数値の入力 */
scanf("%d", &dt); /* 数値の入力 */
このことを意識しないで記述すると、scanf()関数が意図しない動作をすることがあるので注意が必要です。
<対策>
下記のように、fgets関数を用いて、入力を文字列に統一して読み込み、書式変換にはsscanf関数を用いた方が混乱を生じないと思われます。例えば、
int value;
scanf("%d", &value);
と同じことをする場合、次のように記述します。
int value;
char valstr[15];
fgets(valstr, sizeof(valstr), stdin);
sscanf(valstr, "%d", &value);
ここで、valstr[15]の15は、キーボードから入力する整数の最大桁数+2を示しています。
この+2は、改行文字と終端文字'\0'の2バイト分の確保用です。
<補足:scanfとfgetsの使い方の相違について>
まずscanfとfgetsを比較する事自体、正確にいうと間違っています。あえて比較をするならばfscanfとfgetsでしょう。また、scanf(...)とfscanf(stdin,...)が同じ系列ということになります。
- fscanf()
fscanfは書式化入力であり、決められた書式を指定した場合、そのデータとして入力を受け付け、変換を行い、代入します。
fscanfはデータの種類を書式として指定するために、その区切りはあらかじめ決められてしまっています。そのため、異なった形式のデータの読み込みの様な場合には、その区切り方に注意を要します。
- fgets()
それに対してfgetsは単なる文字列としてのストリーム入力であり、データの種類に基づくデータ変換は、その後の処理に任されます。
fgetsは単なる文字列として入力ですから、何が個々のデータの区切りかは、プログラマが勝手に選択することができます。その代わりに、データ形式の変換も、プログラマが行う必要があります。
どちらも注意しなければならないのはファイルポインタという考え方と、ストリーム入力という考え方です。ストリーム入力においては、改行やファイルの終りもnewlineやEOFという特別な文字とみなします。すなわち、改行で複数行にわたるデータも、UNIXでの入力としてはnewlineという特別の文字を挟み、最後にnewlineとEOFのある1行の(一連の)データであるという考え方なのです。
そして入力においては、ファイルポインタで指されているストリーム上の文字から指定された条件に叶う文字まで読み込み、かつファイルポインタを次の位置まで読み進めるのです。
上図において、fgetsは文字列の読み込みであるので6の位置でファイルポインタが止まっていた場合、NL自体を一つの文字列と見なし、それで入力は終了します。この例において、
scanf("%d", &i); fscanf("%d\n", &j); fgets(str, 10,stdin);
であれば改行も処理してうまく読み込みます。すなわち0x10、0x30、0xABというデータの読み込みができるのです。