
char *fgets(char *line, int maxline, FILE *fp);
1
2
3
4
5
6
| - | ! | |
1
2
3
4
| - | ! | |
バッファのサイズを越えてしまうと、残りのデータは入力ストリーム(stdin)に残留したままになってしまい、次のfgets()の呼び出し時に残っている入力ストリームを読み込んでしまう。よって、プログラムによっては対処方法が必要である。
fgets()を実行した直後にfflushでstdinを消去すればよい。しかし、ANSI Cではfflush(stdin);の結果は未定義なため、移植性が低くなってしまう問題がある。
fflush(stdin);
ちなみに、MS VC2008ではfflush(stdin);はうまく動作する。
fflushの代わりにrewindを使って、次のようにしても同様にstdinを消去できる。
rewind(stdin);
[補講]fflush()はバッファを強制的にフラッシュし、rewind()はストリームの位置を先頭にセットする関数である。 ◇
バッファのサイズを越えてstdinに残留データが存在したならば、バッファの大きさを拡張して、再度fgets()を呼び出す。
1行には必ず改行が存在するという前提であれば、残留データを消去する方法がいくつか考えられる。
まず、scanf()で改行までの入力を読み飛ばす方法である。
1
2
3
| - | ! | |
%*[^\n]によって\nの1つ前までスキップし、%*cで\nそのものをスキップするという動作らしい。
次に、whileとfgetsを利用する方法である。
1
2
3
| - | ! | |
fgetsでまとめてゴミ箱用のbufに捨てていくのではなく、せっかく捨てるなら1つずつ捨てていき捨てた回数、即ち削除した数をカウントし、それを返す関数を作るとよいかもしれない。
fgetsで1行データを取ってきて、それからsscanf_sで必要なデータを加工してから変数に格納するという手法がよく取られる。
Windows NT/XPの場合、バイナリモードで開いてfgets()やfputs()を使うと、改行文字の置換洩れが発生することがある。なぜならば、fgets()やfputs()は本来テキストを取り扱う関数だからである。
よって、
if ((pfile1 = fopen(pfilename1, "r")) == NULL)
ではなく、
if ((pfile1 = fopen(pfilename1, "w")) == NULL)
のようにfopenを使う必要がある。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| - | - | | | | | | | ! | | | | | ! | |
ソースコードを解説する。
2行目で、まず配列の先頭アドレスをポインタheadに格納しておく。headは読み込み専用である。
char *const head = s;
3行目〜11行目。streamから1行ずつ読み込み、EOFあるいは改行でない限り、順番に配列へ格納していく。なお、streamに改行しかなかった場合、配列に格納してから、ループを抜ける。これでfgetsが改行も含んで転送することが理解できるはずである。
for (; n > 1; n--) {
int c = fgetc(steram); /* 1文字入力 */
if (c == EOF)
break;
*s = c; /* 配列に代入 */
s++; /* sを進める */
if (c == '\n')
break;
}
13行目〜14行目。NULLを返す条件である。条件は2つある。1番目の条件はEOFを検出し、かつ格納スペースsに1文字も読み取っていなかった場合である。もしEOFを検出しなかったら、1回は必ず転送しているはずなので、この条件に当てはまらない。2番目の条件はstreamのファイルエラーが検出されたときである。
if (s == head || ferror(stream)) return NULL;
15行目で、必ず最後はヌル文字を文字列の後ろに付ける。
*s = '\0';
16行目。エラーが起きなければ、最後はポインタheadを返す。
return head;
1
2
3
4
5
6
7
8
9
10
11
| - | | | | | | | | | ! | |
ソースコードを解説する。
5行目。配列sの先頭要素のアドレス(即ち&s[0])をcsにセット。入力値のsを壊さないようにするため。
cs = s;
6行目。whileのループ処理。iopから1文字ずつgetcしたときEOFでない限り、さらにnが正の数である限り、ループされる。つまり、iopの中身がなくなるか、n-1回1文字ずつの読み込みが終わるかすれば、ループが終わる。
while (--n > 0 && (c = getc(iop)) != EOF)
7行目。c(iopからgetcした1文字)をポインタcsが指すアドレス値であるメモリに代入する。そして、それが\nであれば、8行目のbreakでwhileループを抜ける。つまり、\nが来るまで1文字ずつ配列sに代入作業を行う。1回の代入ごとにポインタcsはインクリメントされていく。
if ((*cs++ = c) == '\n') break;
9行目。while文から抜けたら、格納された文字列の末尾に\0を追加する。
*cs = '\0';
10行目。もしcでwhileループから抜けたとき(即ちcがEOFのとき)、かつcsがまったくインクリメントされていなければ、NULLを返す。そうでなければ、配列sの先頭要素のアドレスを返す。
return (c == EOF && cs == s) ? NULL : s;