意外としつこい scanf,fscanf

scanf,fscanf で「%d」「%f」「%s」での入力では,[SPACE],[TAB],[ENTER]は無視される。
このことは,不定形なファイルからの数値入力に便利な性質である。

1.入門コースで説明されるscanf
プログラム中で
scanf("%d",&x);
が実行されると,キーボードからの入力待ちになる。素直な人は,ここで
123[ENTER]
とキーボードから入力して123が変数xに保存される。

2.scanfに対する意地悪な対応
実は意地悪な人がいて
[SPACE]123[ENTER]
[SPACE]123[SPACE][ENTER]
[SPACE]123[SPACE][TAB][ENTER]
[TAB][SPACE]123[SPACE][TAB][ENTER]

のどれを入力しても123が変数xに保存される。
もっと意地悪に
[TAB][ENTER][ENTER][SPACE]123[SPACE][TAB][ENTER]
でもOKである。
scanf("%d",&x);は,[SPACE],[TAB],[ENTER]などがあっても,
最初に見つかる数字文字列をしつこく取り込む関数である。

実は,scanf,で「%d」「%f」「%s」での入力では,[SPACE],[TAB],[ENTER]は無視される。
しかし「%c」だけは,何も無視せず,最初に見つかる1文字([SPACE],[TAB],[ENTER]も文字である)が入力される。

注意しておこう・・scanfはキーボード入力をそのまま受け付けているわけではない。キーボード入力は,そのまま入力バッファに取り込まれて, そこから読み出される。
キーボード

入力バッファ

scanf
入力バッファからscanfに送られるのは[ENTER]が入力されたときである。
しかも,scanfは変数に読み込むべき文字まで受け取り,それ以降は入力バッファに残してしまう。
例えば上記の意地悪入力
[TAB][ENTER][ENTER][SPACE]123[SPACE][TAB][ENTER]
では,最後の[ENTER]が押されたら,
[TAB][ENTER][ENTER][SPACE]123[SPACE][TAB][ENTER]
が入力バッファに送られる。
読み込み書式は%dなのでscanfは

[TAB][ENTER][ENTER][SPACE]
を読み飛ばし,123を読み込み,残りの
[SPACE][TAB][ENTER]
は入力バッファに残される。

3.fscanfで柔軟な読み込み
「2.」で体験したscanfのしつこい性質は,ファイル読み込みの時に便利に使える。
次のプログラムで,sample.txtを読み込んでみると,その1からその5までどれも同じように読めて,
実行結果(printf出力)はおなじになる。
ファイル入力プログラム
#include <stdio.h>
#include <stdlib.h>

int main()
{
    FILE *fp;
    int i;
    char str[128];
    int x[10];
    fp=fopen("sample.txt","r");
    if (fp==NULL) exit(1);

    fscanf(fp,"%s",str);
    for (i=0; i<5; i++) {
        fscanf(fp,"%d",&x[i]);
    }
    fclose(fp);

    printf("%s\n",str);
    printf("%d %d %d %d %d\n",x[0],x[1],x[2],x[3],x[4]);
}


sample.txt その1
sample.txt その2 sample.txt その3
abcd
1 2 3 4 5

abcd 1 2 3 4 5 abcd 1 2
    3 4 5

sample.txt その4
sample.txt その5
abcd
1
2
3
4
5

     abcd
     1
     2
     3
     4
     5

実行結果(5つの入力ファイルに対して同じ結果が得られる)
abcd
1 2 3 4 5


すべて成功し,ファイルからの読み込みが柔軟にできることを示している。
fscanf(fp, "%d", &x); や fscanf(fp, "%s", str); は,[SPACE],[TAB],[ENTER]などがあっても,
最初に見つかる数字文字列や文字列をしつこく取り込む関数である。

実は,fscanf でも「%d」「%f」「%s」での入力では,[SPACE],[TAB],[ENTER]は無視される。
しかし「%c」だけは,何も無視せず,最初に見つかる1文字([SPACE],[TAB],[ENTER]も文字である)が入力される。

注意しておこう・・fscanfはファイルをそのまま受け付けているわけではない。ファイルは,入力バッファに取り込まれて,そこから読み出され る。
ファイル

入力バッファ

scanf
入力バッファからfscanfに送られるのは改行文字[\n]が入力されたときである。

4.複数変数入力時での意地悪な対応
scanf("%d%d",&x,&y);
scanf("%d %d",&x,&y);  「scanf("%d[SPACE]%d",&x,&y);」
では,
123[SPACE]456[ENTER]
[SPACE]123[SPACE]456[
ENTER]
[SPACE]123[
ENTER]456[ENTER]
[tab][SPACE]123
[SPACE][tab]456[ENTER]
[tab][SPACE]123[
ENTER][SPACE][tab]456[ENTER]
[
ENTER][tab][SPACE]123[ENTER][SPACE][tab]456[SPACE][ENTER]
これらがすべて正しく動作する。
すなわち,[SPACE],[TAB],[ENTER]などがあっても,2つの数字文字列をしつこく取り込む。
このことは,ファイル入力時の fscanf でも同様である。

入力バッファがどのように動作しているのか考えてみよう。
[tab][SPACE]123[ENTER][SPACE][tab]456[ENTER]
このように入力すると,
[tab][SPACE]123[ENTER]
までがまず入力バッファに送られる。
そしてscanfは最初の%dを処理し123がxに読み込まれる。
このとき入力バッファにはまだ
[ENTER]
が残っている状態である。しかしまだもう1つの%dが残っているので,scanfは待たされた状態になる。
そして,
[SPACE][tab]456[ENTER]
が入力バッファに送られたときに,入力バッファには,先ほどの残りと合わせて
[ENTER][SPACE][tab]456[ENTER]
が入っていることになり,もう1つの%dで456をyに読み込み,scanfは完了する。
ただし入力バッファには
[
ENTER]
が残っていることになる。

5.カンマで区切られた複数変数入力時での意地悪な対応(1)
scanf("%d,%d",&x,&y);
scanf("%d, %d",&x,&y);   「scanf("%d,[SPACE]%d",&x,&y);」という表現
では次の2つは正しく動作しない。
123[SPACE]456[ENTER]
123[SPACE],[SPACE]345[
ENTER]
しかし次のものはすべて正しく動作する。
123,345[ENTER]
123, 345[
ENTER]
[tab]123,[SPACE]345[
ENTER]
[
ENTER][tab]123,[SPACE][ENTER][SPACE][SPACE]345[ENTER]
「%d,」のように「%d」と「,」が連続している場合には,キーボード入力においても
最初の数字文字列の直後に「,」の存在を要求する。
そして,[SPACE],[TAB],[ENTER]などがあっても,2つの数字文字列をしつこく取り込む。
このことは,ファイル入力時の fscanf でも同様である。

6.カンマで区切られた複数変数入力時での意地悪な対応(2)
scanf("%d ,%d",&x,&y);   「scanf("%d[SPACE],%d",&x,&y);」という表現
scanf("%d , %d",&x,&y);   「scanf("%d[SPACE],[SPACE]%d",&x,&y);」という表現
では
123[SPACE]456[ENTER]
は正しく動作しないが,
123,345[ENTER]
123,[SPACE]345[
ENTER]
123[SPACE],345[
ENTER]
123[SPACE],[SPACE][SPACE]345[
ENTER]
[tab]123[SPACE],[SPACE][SPACE][SPACE]345[
ENTER]
[tab]123
[SPACE],[ENTER][SPACE][SPACE][SPACE]345[ENTER]
[tab]123
[SPACE][ENTER],[ENTER][SPACE][SPACE]345[ENTER]
[
ENTER][tab]123,[SPACE][ENTER][SPACE][SPACE]345[ENTER]
これらはすべて正しく動作する。
すなわち,[SPACE],[TAB],[ENTER]などがあっても,カンマ区切りの2つの数字文字列をしつこく取り込む。
このことは,ファイル入力時の fscanf でも同様である。

7.まとめ
結局,scanf,fscanf で「%d」での入力では,[SPACE],[TAB],[ENTER]はないものと同じである。
ただし,最後の[ENTER]がないと文字列がプログラムまで届かない。
また,数字文字列を必要なところまで読み込んで,有効な数字文字列の次の文字から[ENTER]まではバッファに残っていて,
次回の読み込みのときには,有効な数字文字列の次の文字から読み込みが始まる。

scanf,fscanf で「%d」「%f」「%s」での入力では,[SPACE],[TAB],[ENTER]は無視される。
しかし「%c」だけは,何も無視せず,最初に見つかる1文字([SPACE],[TAB],[ENTER]も文字である)が入力される。

上記のような意地悪なことは実際にはscanfでは起こらないが,「3.fscanfで柔軟な読み込み」に示したように,
不定形なファイルからの数値入力では重要な性質であり,
うまく使えば,簡単なプログラムで,面倒そうな形式のファイルから数値や文字列を効率よく読み取ることが出来る。