什么是 fscanf?
fscanf 是 C 标准库中的一个函数,全称为 "file scan formatted",它的作用是从一个文件中按照指定的格式读取(扫描)数据,并将这些数据存储到程序提供的变量中。

你可以把它理解为 scanf 函数的文件版本。scanf 从标准输入(通常是键盘)读取数据,而 fscanf 从一个文件指针(FILE*)指向的文件中读取数据。
函数原型
#include <stdio.h> int fscanf(FILE *stream, const char *format, ...);
参数解析:
stream:FILE*类型的指针,指向你要读取的文件,这个指针通常是由fopen()函数打开文件后返回的。format: 一个字符串,包含了格式说明符,用来告诉fscanf如何解释文件中的数据,这与printf和scanf中的格式字符串类似。- (省略号) 表示可变参数列表,你需要提供与
format字符串中格式说明符数量和类型相匹配的变量的地址。
返回值:
- 成功时,
fscanf返回成功匹配并赋值的字段的数量,这个数量不包括被跳过的空白字符。 - 如果在读取任何数据之前就到达了文件末尾,则返回
EOF(End of File)。 - 如果发生读取错误(文件损坏),也返回
EOF。 - 如果在匹配过程中发生错误(期望一个数字但遇到了字母),则返回已成功赋值的字段的数量。
格式说明符
fscanf 的强大之处在于其格式化能力,常用的格式说明符如下:

| 格式说明符 | 对应的C数据类型 | 描述 |
|---|---|---|
%d |
int |
读取一个十进制整数。 |
%f |
float |
读取一个浮点数。 |
%lf |
double |
读取一个双精度浮点数。注意:在 fscanf 中,读取 double 必须使用 %lf,这是初学者最容易犯的错误之一。 |
%c |
char |
读取一个单个字符。 |
%s |
char* (字符数组) |
读取一个字符串,直到遇到空白字符(空格、制表符、换行符)。 |
%[...] |
char* (字符数组) |
读取一个字符集合。%[a-z] 读取所有小写字母。%[^,] 读取直到遇到逗号为止的所有字符。 |
%n |
int* |
不读取字符,而是将到目前为止已读取的字符数存入指定的 int 变量中。 |
基本用法示例
示例 1:读取整数
假设我们有一个名为 numbers.txt 的文件,内容如下:
10
20
30
40
代码:
#include <stdio.h>
int main() {
FILE *fp;
int num1, num2, num3, num4;
// 以只读模式打开文件
fp = fopen("numbers.txt", "r");
if (fp == NULL) {
perror("无法打开文件");
return 1;
}
// 从文件中读取4个整数
int count = fscanf(fp, "%d %d %d %d", &num1, &num2, &num3, &num4);
if (count == 4) {
printf("成功读取了 %d 个整数,\n", count);
printf("num1 = %d, num2 = %d, num3 = %d, num4 = %d\n", num1, num2, num3, num4);
} else {
printf("读取失败或文件内容不匹配,成功读取了 %d 个整数,\n", count);
}
// 关闭文件
fclose(fp);
return 0;
}
输出:
成功读取了 4 个整数。
num1 = 10, num2 = 20, num3 = 30, num4 = 40
示例 2:读取混合数据(字符串和浮点数)
假设我们有一个名为 data.txt 的文件,内容如下:

Alice 95.5
Bob 88.0
Charlie 76.5
代码:
#include <stdio.h>
#include <string.h> // 为了使用 strlen
int main() {
FILE *fp;
char name[50];
double score;
fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("无法打开文件");
return 1;
}
printf("正在读取学生数据...\n");
// 循环读取,直到文件末尾
// fscanf 返回成功读取的字段数,对于 "name score" 这个格式,成功应该是2
while (fscanf(fp, "%s %lf", name, &score) == 2) {
printf("姓名: %-10s 分数: %.2f\n", name, score);
}
fclose(fp);
return 0;
}
输出:
正在读取学生数据...
姓名: Alice 分数: 95.50
姓名: Bob 分数: 88.00
姓名: Charlie 分数: 76.50
示例 3:处理文件末尾
使用 feof() 函数可以更准确地判断是否到达文件末尾。
#include <stdio.h>
int main() {
FILE *fp;
int number;
fp = fopen("numbers.txt", "r");
if (fp == NULL) {
perror("无法打开文件");
return 1;
}
printf("开始读取文件内容...\n");
while (1) {
int items_read = fscanf(fp, "%d", &number);
if (items_read == EOF) {
// 检查是否是因为到达文件末尾而退出
if (feof(fp)) {
printf("\n已到达文件末尾,\n");
} else {
// 或者是因为发生错误
printf("\n读取文件时发生错误,\n");
}
break; // 退出循环
} else if (items_read == 1) {
// 成功读取一个整数
printf("读取到数字: %d\n", number);
} else {
// 格式不匹配等意外情况
printf("格式不匹配,停止读取,\n");
break;
}
}
fclose(fp);
return 0;
}
重要注意事项和最佳实践
-
检查
fopen的返回值:在尝试使用FILE*指针之前,必须检查它是否为NULL,如果文件打开失败(文件不存在或没有权限),后续的fscanf操作会导致未定义行为(通常是程序崩溃)。 -
double必须用%lf:这是最常见的错误,在scanf和fscanf中,读取double类型变量时,格式说明符必须是%lf,如果你写成%f,行为是未定义的,几乎肯定会导致数据读取错误。 -
处理返回值:不要假设
fscanf总是能成功读取你期望的数据,总是检查它的返回值,以确定实际读取了多少个字段,这对于健壮的程序至关重要。 -
关闭文件:使用完文件后,务必调用
fclose(fp)来关闭文件,这会释放系统资源,并将缓冲区中的数据写入磁盘(如果是以写模式打开),如果程序异常退出,没有关闭的文件可能会导致数据丢失。 -
%s的陷阱:%s遇到空白字符就会停止,并且它不会在读取的字符串末尾自动添加'\0'。fscanf会为你做这件事,因为它会根据读取的字符数量来填充字符数组,如果你用一个固定大小的数组去读取一个可能很长的字符串,有缓冲区溢出的风险,使用fgets通常比fscanf来读取一行字符串更安全。 -
%c会读取空白字符:与%s不同,%c会读取文件中的每一个字符,包括空格、制表符和换行符,如果你想在fscanf中跳过这些空白字符,可以在%c前面加一个空格,fscanf(fp, " %c", &c),这个空格会告诉fscanf先跳过所有连续的空白字符,然后再读取一个非空白字符。
fscanf vs. fgets + sscanf
在读取结构化文本文件时,一个更常用、更稳健的模式是:
- 使用
fgets一次读取一行文本到一个字符串缓冲区中。 - 使用
sscanf(从字符串扫描) 从这个缓冲区中解析出所需的数据。
为什么这样做更好?
- 健壮性:
fgets读取一行,可以处理每行长度不一致的情况,而fscanf如果某行格式不对,可能会“卡住”在那一行,影响后续所有行的读取。 - 可控性:你可以先检查整行内容,再用
sscanf解析,逻辑更清晰。
示例: 读取 data.txt
#include <stdio.h>
int main() {
FILE *fp;
char line[100]; // 缓冲区,用于存放一行文本
char name[50];
double score;
fp = fopen("data.txt", "r");
if (fp == NULL) {
perror("无法打开文件");
return 1;
}
while (fgets(line, sizeof(line), fp) != NULL) {
// 尝试从字符串 line 中解析数据
if (sscanf(line, "%s %lf", name, &score) == 2) {
printf("姓名: %-10s 分数: %.2f\n", name, score);
}
}
fclose(fp);
return 0;
}
这个版本和前面的 fscanf 版本功能相同,但通常被认为更易于维护和调试。
fscanf 是一个强大的工具,用于从文件中按格式读取数据,掌握它的关键在于:
- 正确使用:始终检查
fopen返回值和fscanf的返回值。 - 理解格式:特别是
%lf用于double。 - 知道替代方案:对于复杂的文本解析,
fgets+sscanf往往是更可靠的选择。
希望这份详细的讲解能帮助你完全掌握 fscanf 的用法!
