如何读取/parsingC中的input? FAQ

当我尝试读取/parsinginput时,遇到我的C程序问题。

帮帮我?


这是一个FAQ条目。

StackOverflow有许多与读取C语言input有关的问题,答案通常集中在特定用户的具体问题上,而不是真正地绘制整个图像。

这是一个全面涵盖一些常见错误的尝试,所以这个特定的问题家族可以简单地将它们标记为这个问题的重复:

  • 为什么最后一行打印两次?
  • 为什么我的scanf("%d", ...) / scanf("%c", ...)失败?
  • 为什么gets()崩溃?

答案被标记为社区维基。 随意改进(谨慎)扩展。

初学者的Cinput入门

  • 文本模式与二进制模式
  • 检查fopen()是否失败
  • 陷阱
    • 检查您呼吁成功的任何function
    • EOF或“为什么最后一行打印两次”
    • 不要使用gets() ,永远
    • 不要使用* scanf()来input可能的格式错误
    • 当* scanf()不能按预期工作时
  • 阅读, 然后parsing
    • 通过fgets()读取(部分)一行input
    • 分析内存中的行
  • 清理

文本模式与二进制模式

一个“二进制模式”数据stream的读取与写入完全一样。 但是,可能(或可能不)是在stream末尾附加的实现定义数量的空字符(' \0 ')。

“文本模式”stream可以进行许多转换,包括(但不限于):

  • 在行结束之前立即移除空格;
  • 在输出上改变换行符( '\n' )为其他内容(例如Windows上的"\r\n" ),然后回到'\n'
  • 添加,更改或删除既不是打印字符( isprint( c ) == true ),水平制表符或换行符的字符。

应该很明显,文本和二进制模式不会混合。 以文本模式打开文本文件,以二进制模式打开二进制文件。

检查fopen()是否失败

打开文件的尝试可能由于各种原因而失败 – 缺less权限,或者找不到最常用的文件。 在这种情况下, fopen()将返回一个NULL指针。

可以设置全局的errnovariables,它的值可以用perror()变成纯文本错误信息。 这是POSIX的要求,而不是C语言,所以它可能不适用于每个平台。

 #include <stdio.h> #include <errno.h> int main() { errno = 0; FILE * fp = fopen( "file.txt", "rb" ); if ( fp != NULL ) { // ready to read } else { // If supported by fopen(), will print a message // *why* fopen() failed, exactly. perror( "fopen() failed" ); } fclose( fp ); } 

陷阱

检查您呼吁成功的任何function

这应该是显而易见的。 但是, 检查您所要求的任何函数的文档以获取其返回值和error handling,并检查这些条件。

这些错误很容易在你早期发现的情况下发生,但是如果你不这样做的话会导致很多头痛。

EOF或“为什么最后一行打印两次”

如果已经达到EOF,函数feof(FILE * stream)返回true 。 对“到达”EOF实际上意味着什么的误解使许多初学者写这样的东西:

 // BROKEN CODE while ( ! feof( fp ) ) { fgets( buffer, BUFFER_SIZE, fp ); puts( buffer ); } 

这会使input的最后一行打印两次 ,因为当读取最后一行时(直到最后一个换行符,即inputstream中的最后一个字符), EOF 不会被设置。

当您尝试读取最后一个字符时,EOF才会被设置!

因此,上面的代码再次循环, fgets()无法读取另一行,设置EOF 并保持buffer的内容不变 ,然后再次打印。

因此,在读取之后检查feof() ,但处理之前

 // GOOD CODE while ( fgets( buffer, BUFFER_SIZE, fp ) != NULL ) { puts( buffer ); } 

不要使用gets() ,永远

没有办法安全地使用这个function。 正因为如此,随着C11的出现,它已经从语言中移除了。

不要使用* scanf()来input可能的格式错误

许多教程教你使用* scanf()读取任何types的input,因为它是如此多才多艺。

但是* scanf()的目的实际上是读取可能依赖于预定义格式的批量数据。 (如另一个程序写的)

即使这样* scanf()可以绊倒不受欢迎的人:

  • 使用格式string在某种程度上可能会受到用户的影响是一个巨大的安全漏洞。
  • 如果input与预期的格式不匹配, * scanf()立即停止parsing,剩下的参数未初始化。
  • 它会告诉你它已经成功完成了多less任务 ,但是不知道它停止parsinginput的地方,使错误恢复变得困难。
  • 它跳过input中的所有空格,除非不是( [cn转换])。 (见下一段)
  • 在一些angular落案件中,这有些奇特的行为。

当* scanf()不能按预期工作时

* scanf()的一个常见问题是当inputstream中有未读空白( ' ''\n' ,…)时,用户没有考虑到这个空白。

读取一个数字( "%d"等)或一个string( "%s" )可以停止任何空格。 虽然大多数*scanf()转换说明符会忽略input中的前导空白, [cn不会。 所以换行符仍然是第一个挂起的input字符,使得%c%[不匹配。

您可以跳过input中的换行符,例如通过fgetc()或通过向* scanf()格式的string添加空格来显式读取。 (格式string中的单个空格与input中的任意数量的空格匹配。)

但是,如果您打算保持便携性, 请不要在inputstream上调用fflush() 。 这只是POSIX平台的明确定义; 在纯C中,在inputstream上调用fflush()是未定义的行为 。

阅读, 然后parsing

我们只是build议不要使用* scanf(),除非你确实知道你在做什么。 那么,用什么来替代?

而不是一次读取和parsinginput,就像* scanf()试图做的那样,分离这些步骤。

通过fgets()读取(部分)一行input

fgets()有一个限制input的参数,这个参数至less有很多字节,避免缓冲区溢出。 如果input行完全适合你的缓冲区,缓冲区中的最后一个字符将是换行符( '\n' )。 如果不是,你正在看一个部分阅读的线。

分析内存中的行

对于内存分析特别有用的是strtol()和strtod()函数系列,它们提供与* scanf()转换说明符diuoxaefg类似的function。

但是他们也会告诉你他们停止parsing的地方,并且对目标types的数字进行有意义的处理。

除此之外,C提供了广泛的string处理function 。 既然你在内存中有input,并且总是知道你已经分解了多less,你可以多次尝试去理解input。

如果一切都失败了,你可以用整行打印一条有用的错误消息给用户。

清理

确保你明确closures了你已经(成功)打开的任何stream。 这刷新所有尚未写入的缓冲区,并避免资源泄漏。

 fclose( fp );