このページをはてなブックマークに追加このページを含むはてなブックマーク このページをlivedoor クリップに追加このページを含むlivedoor クリップ
*目次 [#p9f3a419]

#contents


*fgets [#qbdddb49]

-1行を読み込み指定した空間に格納する。
-"File GET String"
-【エフゲットエス】


*fgetsのプロトタイプ [#wc1c81e6]

 char *fgets(char *line, int maxline, FILE *fp);


**戻り値 [#j0e98669]

-成功した場合
--読み込んだ文字列へのポインタ(文字列の先頭アドレス)
-失敗した場合
--NULL
---EOFを検出し、かつ格納スペースlineに1文字も読み取っていなかった場合、配列の内容を変化させずに残し、NULLを返す。
---読取りエラーが発生した場合もNULLを返すが、この場合の配列の内容は不定である。

*fgetsの特徴 [#h854760e]

-ファイルfpから読み込み、改行文字またはファイル終端(EOF)を見つけると、末尾をヌル文字に変換して1行の文字列として切り出して(''改行も含む'')、第1引数で指定された文字配列lineに格納する。
-改行文字またはEOFを見つけるよりも前に第2引数で指定された「最大サイズ-1」の文字数に達した場合も、ヌル文字をつけて文字列として切り出す。
-lineに読み込まれるのはmaxline-1個の文字であり、最後に必ずヌル文字が付け加えられる。
--改行文字またはEOFを見つけるよりも前に第2引数で指定されたmaxline-1の文字数に達した場合でも、ヌル文字をつけて文字列として切り出す。
--例えば、stdinをファイルに指定していた場合は、stdinに残留した文字列が残っている。
---入力に必ず改行が含まれるわけではない。よく改行が存在するかどうかをstrchrなどでチェックするソースコードが存在するが、一般的にそれでも動くが、万全とはいえない。なぜならば、「^Z」などで改行なしで1行が終了している可能性があるからである。
-用意された大きさ分だけのデータを格納するので、scanf()やgets()と比べれば安全である。もしscanf()やgets()を使うとしたら、格納文字数に注意が必要になってしまう。
-通常はlineを返し、EOFの検出時(ただし条件付)やエラー時にNULLを返す。
-fgets()と対の関数としてfputs()が存在する。

*テクニック [#m2b5cd73]

**lineから改行を取り除く [#f457e058]

***strrchrを利用する [#g985a26d]

#code(c){{
char *p;

fgets(command, COMMAND_LENGTH, stdin);
if (( p = strrchr(command, '\n')) != NULL) {
	*p = '\0';
}
}}

***strlenを利用する [#kf300b9a]

#code(c){{
fgets(command, COMMAND_LENGTH, stdin);
if (command[strlen(command) - 1] == '\n') {
	command[strlen(command) - 1] = '\0';
}
}}


**stdinの残留データの取扱い [#v7b283ce]

 バッファのサイズを越えてしまうと、残りのデータは入力ストリーム(stdin)に残留したままになってしまい、次のfgets()の呼び出し時に残っている入力ストリームを読み込んでしまう。よって、プログラムによっては対処方法が必要である。


***fflushを利用する [#b061e020]

 fgets()を実行した直後にfflushでstdinを消去すればよい。しかし、ANSI Cではfflush(stdin);の結果は未定義なため、移植性が低くなってしまう問題がある。

 fflush(stdin);

 ちなみに、MS VC2008ではfflush(stdin);はうまく動作する。

 fflushの代わりにrewindを使って、次のようにしても同様にstdinを消去できる。

 rewind(stdin);

[補講]fflush()はバッファを強制的にフラッシュし、rewind()はストリームの位置を先頭にセットする関数である。 ◇

***mallocを利用する [#d8c75f85]

 バッファのサイズを越えてstdinに残留データが存在したならば、バッファの大きさを拡張して、再度fgets()を呼び出す。


***1行には必ず改行が存在するという前提における対処方法 [#i011f9b6]

 1行には必ず改行が存在するという前提であれば、残留データを消去する方法がいくつか考えられる。

 まず、scanf()で改行までの入力を読み飛ばす方法である。

#code(c){{
if (strchr(buf, '\n') == NULL) {
	scanf("%*[^\n]%*c");
}
}}

 %*[^\n]によって\nの1つ前までスキップし、%*cで\nそのものをスキップするという動作らしい。

 次に、whileとfgetsを利用する方法である。

#code(c){{
if (strchr(buf, '\n') == NULL) {
	while(strchr(fgets(buf, BUF_LEN, stdin), '\n') == NULL);
}
}}

 fgetsでまとめてゴミ箱用のbufに捨てていくのではなく、せっかく捨てるなら1つずつ捨てていき捨てた回数、即ち削除した数をカウントし、それを返す関数を作るとよいかもしれない。

#code(c){{
int stdin_erase(FILE *fp){
	int c;
	int erase_count = 0; /* 消去した回数を数えるカウンタ */
	while (1) {
		c = fgetc(stdin);
		if ((c == '\n') && (c == EOF)) {
			return erase_count;
		}
		erase_count++;
	}
	return erase_count;
}
}}

**ファイル入出力の中でfgetsを使う [#x08f3984]

***置換洩れを防ぐ [#vb8a2650]

 Windows NT/XPの場合、バイナリモードで開いてfgets()やfputs()を使うと、改行文字の置換洩れが発生することがある。なぜならば、fgets()やfputs()は本来テキストを取り扱う関数だからである。

 よって、

 if ((pfile1 = fopen(pfilename1, "r")) == NULL)

ではなく、

 if ((pfile1 = fopen(pfilename1, "w")) == NULL)

のようにfopenを使う必要がある。


*fgetsのソースコード [#kb10a46d]

**『新ANSI C言語辞典』バージョン [#qa5c3584]

#code(c){{
char *fgets(char *s, int n, FILE *stream){
	char *const head = s;		/* 先頭ポインタを保存 */
	for (; n > 1; n--) {
		int c = fgetc(steram);	/* 1文字入力 */
		if (c == EOF)
			break;
		*s = c;					/* 配列に代入 */
		s++;					/* sを進める */
		if (c == '\n')
			break;
	}
	/* 入力失敗のとき */
	if (s == head || ferror(stream))
		return NULL;
	*s = '\0';
	return head;
}
}}

 ソースコードを解説する。

 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;

**K&Rバージョン [#ha417198]

#code(c){{
char *fgets(char *s, int n, FILE *iop){
	register int c;
	register char *cs;

	cs = s;
	while (--n > 0 && (c = getc(iop)) != EOF)
		if ((*cs++ = c) == '\n')
			break;
	*cs = '\0';
	return (c == EOF && cs == s) ? NULL : s;
}
}}

 ソースコードを解説する。

 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;


*参考文献 [#aecd23f0]

-『新ANSI C言語辞典』
-『プログラミング言語C 第2版』
-『美しいCプログラミング見本帖』