C语言scanf与fscanf函数使用规则及常见问题咨询
fscanf与scanf常见问题解答 首先明确基础逻辑:你对两个函数的核心差异理解是正确的——int fscanf(FILE *stream, const char *format, ...)和int scanf(const char *format, ...)的格式匹配规则完全一致,唯一区别是scanf固定从标准输入流stdin读取内容,fscanf可以手动指定输入流。
从标准输入读取两个int类型整数的示例代码如下:
int first_number; int second_number; scanf("%d%d", &first_number, &second_number);
一、通用格式匹配规则疑问
疑问1:即使第二个整数在输入的下一行,是否也无需在格式说明符之间添加换行符?函数是否会自动查找匹配下一个十进制整数?
不需要加。%d这类数值转换说明符、%s字符串转换说明符,在正式匹配内容前会自动跳过任意数量的空白字符——空格、制表符、换行、回车全属于被跳过的范畴,不管两个整数之间隔了几个空行、多少空格,%d%d都能准确找到下一个合法的十进制整数完成匹配。疑问2:若输入两个字符而非int类型整数,函数会产生什么行为?
会触发匹配失败,函数返回值小于预期读取的项数。fscanf/scanf的返回值是成功匹配并完成赋值的参数个数,碰到和格式要求不匹配的输入(比如要读整数却碰到字母),函数会立刻停止匹配,文件/流指针停在匹配失败的位置不动,既不会自动跳过错误内容,也不会主动抛出错误。如果不手动检查返回值、处理错误、移动指针,后续所有读取操作都会卡在这个错误位置,持续读取失败。疑问3:为何有时格式说明符之间添加空格会导致函数运行异常?
本质是没搞懂格式串里空白字符的匹配逻辑,踩了%c、%[]这类不自动跳过前导空白的转换符的坑。格式串里任意连续的空白字符(不管是空格、换行还是制表符),都会匹配输入流里0到任意多个连续空白字符,直到碰到第一个非空白字符才停下。
大部分转换符比如%d、%s本身就会自动跳过前导空白,所以格式串里加不加空格、换行看起来都没区别;但%c的逻辑是直接读当前位置的第一个字符,不管它是不是空白——比如你想读一个用户输入的可见字符,写scanf("%c", &c)很可能读到的是上次输入残留在缓冲区里的换行符,要是你没搞懂规则在不该加空格的地方加了空格,比如需要捕获空格/换行字符时在%c前面加了空格,函数会跳过所有前导空白,反而读不到目标字符,就会出现“加了空格就运行异常”的情况。
二、文件读取场景疑问
待读取的目标txt文件为PPM格式,内容如下:
P6 255 1920 1080
你当前使用的读取代码如下:
FILE *input = /* 已初始化的有效文件指针 */; char type[2]; int tr; int width; int height; fscanf(input, "%s\n", type); fscanf(input, "%d\n", &tr); fscanf(input, "%d %d\n", &width, &height);
针对该场景的三个疑问解答如下:
疑问1:上述代码中添加
\n标识换行是否有必要?
完全没必要,反而容易埋下隐患。首先%s、%d本身就会自动跳过前导的换行、空格,值后面跟的是换行还是空格根本不影响匹配;其次你在格式串末尾加的\n属于空白匹配规则,会一直往后读所有连续的空白字符,直到碰到第一个非空白字符才会返回——如果文件读完1080之后就到文件末尾了倒是不会出问题,要是后面还有像素数据,这个\n会直接把指针移到后面第一个非空白字符的位置,直接打乱后续读取的位置预期。
你把格式串里所有\n都删掉,甚至连%d %d中间的空格都可以省,直接写%d%d就能正确读取。顺便提一句你贴的代码有两处可修正的问题:第二个fscanf的格式串和&tr之间少了逗号,会触发编译错误;type数组长度只有2,读"P6"刚好够,要是碰到更长的格式标识会出现内存越界,实际使用建议把数组开长一点留余量。疑问2:fscanf是否会对fread()等其他文件读取函数的运行造成影响?
不存在函数层面的冲突,但会因为共享文件位置指针带来逻辑层面的问题。所有C标准库的文件操作函数,针对同一个打开的文件流,共享同一个位置指针,fscanf匹配过程中移动指针的行为是完全符合C标准的,但如果你没搞准fscanf执行完之后指针停在哪,后续调用fread就会从你意料之外的位置开始读,最常见的坑就是刚才说的格式串末尾加空白导致指针多移了一段,或者匹配失败时指针根本没动,fread直接读到没匹配上的垃圾内容。两者混用在语法上是合法的,不会破坏流缓冲,但非常容易出现位置计算错误。疑问3:操作同一文件时全程使用同一种读取函数是否属于良好实践?
是非常推荐的良好实践。全程用同一种读取函数,你只需要记一套指针移动规则,不用在不同函数的行为逻辑之间反复切换,能大幅减少位置错位这类低级bug。比如读结构化文本配置就全程用fscanf,读固定长度二进制块就全程用fread,按行处理文本就全程用fgets再做内容解析,尽量不要混着用。如果确实有特殊场景必须混用,一定要在每次切换读取函数前确认当前指针位置,并且逐次检查所有读取操作的返回值,避免出现读取错位。
内容的提问来源于stack exchange,提问作者enuliver

