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

#contents


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

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


**戻り値 [#j0e98669]

-成功した場合
--読み込んだ文字列へのポインタ
-失敗した場合
--NULL
---ファイルの終わりを検出し、かつ配列に1文字も読み取っていなかった場合、配列の内容を変化させずに残し、空ポインタを返す。
---読取りエラーが発生した場合も空ポインタを返すが、この場合の配列の内容は不定である。

*fgetsの特徴 [#h854760e]

-ファイルfpからの入力行(''改行も含む'')が、文字配列lineに読み込まれる。
--改行が見つかった場合、maxline-1以下でもそこまでしか読み込まれず、最後にヌル文字が追加される。
--入力に必ず改行が含まれるわけではない。よく改行が存在するかどうかをstrchrなどでチェックするソースコードが存在するが、一般的にそれでも動くが、万全とはいえない。なぜならば、「^Z」などで改行なしで1行が終了している可能性があるからである。
-lineに読み込まれるのはmaxline-1個の文字であり、最後に必ずヌル文字が付け加えられる。
--用意された大きさ分だけのデータを格納するので、scanf()やgets()と比べれば安全である。もしscanf()やgets()を使うとしたら、格納文字数に注意が必要になってしまう。
-通常はlineを返し、ファイル終了時やエラー時にNULLを返す。

*テクニック [#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]


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

#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;
}
}}

**ソースコード解説 [#c23f5247]

 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]

-『プログラミング言語C 第2版』