
2.5.2 C99
严格符合C99的应用程序的两个选项是用fgets()或getchar()取代gets()。
C标准fgets()函数和gets()拥有相似的行为。fgets()函数接受两个额外的参数:期望读入的字符数和输入流。我们可以通过指定stdin作为输入流来使fgets()模拟gets()的行为。
例2.9的程序片段中使用fgets()函数从stdin中读取一行文本。
例2.9 使用fgets()从stdin中读取
01 char buf[LINE_MAX]; 02 int ch; 03 char *p; 04 05 if (fgets(buf, sizeof(buf), stdin)) { 06 /* fgets 成功, 扫描查找换行符 */ 07 p = strchr(buf, '\n'); 08 if (p) { 09 *p = '\0'; 10 } 11 else { 12 /* 未找到换行符, 刷新stdin 到行尾 */ 13 while (((ch = getchar()) != '\n') 14 && !feof(stdin) 15 && !ferror(stdin) 16 ); 17 } 18 } 19 else { 20 /* fgets 失败, 处理错误 */ 21 }
与gets()不同,fgets()函数保留换行符,这意味着不能将fgets()作为gets()的直接替代品。
当使用fgets()时,可能只读取了一行的部分数据,然而,可以确定用户的输入是否被截断了,因为那样的话,输入缓冲区内将不会包含一个换行符。
fgets()函数从流中最多读入比指定数量少1个的字符到一个数组中。如果遇到换行符或者EOF标志,则不会继续读取。在最后一个字符读入数组中后,一个空字符随即被写入缓冲区的结尾处。
使用fgets()可以安全地处理太长而不能在目标数组中存储的输入行,但出于性能方面的原因,不推荐使用它。如果指定的输入字符数超过目标缓冲区的长度,fgets()函数也可能导致缓冲区溢出。
在严格符合C99标准的应用程序中更换gets()函数的第二个方法是使用getchar()函数。getchar()函数返回stdin指向的输入流中的下一个字符。如果流在EOF处,则该流的EOF标记就会被设置,且getchar()返回EOF。如果发生读取错误,则该流的错误标记就会被设置,且getchar()返回EOF。例2.10中的程序片段使用getchar()函数从stdin中读取一行文本。
例2.10 使用getchar()从stdin中读取
01 char buf[BUFSIZ]; 02 int ch; 03 int index = 0; 04 int chars_read = 0; 05 06 while (((ch = getchar()) != '\n') 07 && !feof(stdin) 08 && !ferror(stdin)) 09 { 10 if (index < BUFSIZ-1) { 11 buf[index++] = (unsigned char)ch; 12 } 13 chars_read++; 14 } /* while 循环结束 */ 15 buf[index] = '\0'; /* 空终结符 */ 16 if (feof(stdin)) { 17 /* 处理 EOF */ 18 } 19 if (ferror(stdin)) { 20 /* 处理错误 */ 21 } 22 if (chars_read > index) { 23 /* 处理截断 */ 24 }
如果在循环结束时,,feof(stdin) ! = 0,则表示循环读取到文件末尾,而没有遇到一个换行符。如果在循环结束时,fferror(stdin) ! = 0,则表示循环在遇到一个换行符之前,发生了读取错误。如果在循环结束时,chars_read > index,则表示输入的字符串已被截断。《C安全编码标准》[Seacord 2008],“FIO 34-C.使用int捕捉字符IO函数的返回值”,在此解决方案中也适用。
如果写入到没有设定正确的边界的缓冲区中,那么使用getchar()函数读取一行仍然可能导致缓冲区溢出。
一次读取一个字符在控制行为方面提供了更多的灵活性,而无须额外的性能开销。下面的测试对于while循环通常是足够的:
while (( (ch = getchar()) ! = '\n') && ch ! = EOF )
参见《C安全编码标准》[Seacord 2008],“FIO 35-C.在sizeof(int)==sizeof(char)的情况下,用feof()和ferror()检测文件结束和文件错误”,这种情况下,必须用feof()和ferror()来代替上面的语句的写法。