一个示例TXT文件
我们创建一个名为 data.txt 的文件,内容如下,我们将使用这个文件作为所有示例的输入。

(图片来源网络,侵删)
data.txt 文件内容:
Hello, World!
This is the second line.
C programming is fun.
12345
使用 fgets() - 最常用、最安全的方法
fgets() 函数是读取文件行的最佳选择,因为它可以一次性读取一行文本,并且有内置的缓冲区大小限制,能有效防止缓冲区溢出。
函数原型
char *fgets(char *str, int n, FILE *stream);
str: 一个字符数组(缓冲区),用于存储读取到的字符串。n: 要读取的最大字符数(包括结尾的\0)。stream: 文件指针(通过fopen获取)。- 返回值: 成功时返回
str,到达文件末尾或出错时返回NULL。
完整示例代码
#include <stdio.h>
#include <string.h> // 用于 strlen()
#define MAX_LINE_LENGTH 256 // 定义每行最大长度
int main() {
// 1. 打开文件
// "r" 表示以只读模式打开文本文件
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
perror("无法打开文件"); // perror会打印出具体的错误信息
return 1; // 返回非零表示错误
}
char line[MAX_LINE_LENGTH]; // 创建一个缓冲区来存放每一行
// 2. 循环读取文件
// fgets会读取一行,包括换行符\n
// 当fgets返回NULL时,表示文件已读完或发生错误
while (fgets(line, sizeof(line), file) != NULL) {
// 去掉行尾的换行符(如果存在)
// 因为fgets会保留换行符,而我们通常不需要它
size_t len = strlen(line);
if (len > 0 && line[len - 1] == '\n') {
line[len - 1] = '\0'; // 将换行符替换为字符串结束符
}
// 3. 处理读取到的字符串
printf("读取到一行: %s\n", line);
}
// 4. 关闭文件
fclose(file);
return 0;
}
代码解释
fopen("data.txt", "r"): 以只读模式("r")打开data.txt文件,如果文件不存在,fopen会返回NULL,所以必须检查。char line[MAX_LINE_LENGTH]: 定义一个足够大的字符数组作为缓冲区。sizeof(line)会自动计算出数组的大小(这里是256)。while (fgets(...)): 这是一个非常经典的读取文件的循环。fgets会尝试读取一行到line中,如果成功,它返回line的地址(非NULL),循环继续,当文件读完或出错时,它返回NULL,循环终止。line[len - 1] = '\0':fgets会保留行尾的换行符\n,这通常不是我们想要的,所以手动检查并替换掉它,让字符串更“干净”。fclose(file): 文件操作完成后,必须关闭文件,释放系统资源。
使用 fscanf() - 按格式读取
fscanf() 函数可以根据指定的格式从文件中读取数据,它类似于 scanf(),只是输入源是文件而不是标准输入。
函数原型
int fscanf(FILE *stream, const char *format, ...);
完整示例代码
#include <stdio.h>
#define MAX_STRING_LENGTH 100
int main() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
char str[MAX_STRING_LENGTH];
// %s 表示读取一个字符串,遇到空白字符(空格、制表符、换行符)会停止
// fscanf会自动在读取的字符串末尾添加'\0'
while (fscanf(file, "%s", str) != EOF) {
printf("读取到一个单词: %s\n", str);
}
fclose(file);
return 0;
}
代码解释
fscanf(file, "%s", str) != EOF:fscanf的返回值是成功匹配并赋值的字段数量,当到达文件末尾时,它会返回EOF(End-Of-File)。%s的局限性:%s会读取一个“单词”,即直到遇到空格、\t或\n为止,对于data.txt的第一行"Hello, World!",它会先读取"Hello,",然后读取"World!",无法一次性读取整行。
fscanf 适合读取结构化的、由特定分隔符隔开的数据(如CSV文件),不适合读取包含空格的整行文本。

(图片来源网络,侵删)
逐个字符读取 (fgetc()) - 最底层的方法
这种方法虽然不常用来读取“字符串”,但能帮助你理解文件读取的本质,它一次只读取一个字符,你需要自己将它们拼接成字符串。
完整示例代码
#include <stdio.h>
#include <stdlib.h> // 用于 malloc 和 free
int main() {
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
perror("无法打开文件");
return 1;
}
// 动态分配内存来存储字符串,初始大小设为100
size_t capacity = 100;
char *str = (char *)malloc(capacity * sizeof(char));
if (str == NULL) {
perror("内存分配失败");
fclose(file);
return 1;
}
size_t length = 0;
int c; // 必须是 int,因为 fgetc 返回的 EOF 是一个 int 值
// 读取字符,直到文件末尾或遇到换行符
while ((c = fgetc(file)) != EOF && c != '\n') {
// 检查是否需要扩容
if (length + 1 >= capacity) {
capacity *= 2; // 容量翻倍
char *temp = (char *)realloc(str, capacity);
if (temp == NULL) {
perror("内存重新分配失败");
free(str);
fclose(file);
return 1;
}
str = temp;
}
str[length++] = (char)c;
}
// 添加字符串结束符
str[length] = '\0';
// 如果读取到了内容(length > 0),则打印
if (length > 0) {
printf("读取到一行(用fgetc拼接): %s\n", str);
}
// 释放内存
free(str);
fclose(file);
return 0;
}
代码解释
fgetc(file): 从文件中读取一个字符,返回int类型,如果到达文件末尾,返回EOF。- 动态内存分配: 因为不知道一行会有多长,所以使用
malloc动态分配内存,并在需要时用realloc扩展。 - 拼接字符串: 在循环中,将每个字符依次存入
str数组,并更新长度length。 str[length] = '\0': 手动添加字符串结束符,这是C语言字符串的必需步骤。free(str): 动态分配的内存,使用后必须手动释放,否则会造成内存泄漏。
这种方法非常灵活,可以处理任意长度的行,但代码复杂,容易出错。在绝大多数情况下,优先使用 fgets()。
总结与对比
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
fgets() |
简单、安全、高效,有缓冲区限制,防止溢出 | 保留行尾的 \n 需要手动处理 |
读取文件行的首选方法,适用于99%的场景。 |
fscanf() |
可以按指定格式(如数字、特定分隔符)读取数据 | 无法正确读取带空格的整行,格式匹配失败时行为复杂 | 读取结构化数据,如配置文件、CSV等。 |
fgetc() |
最底层,控制力最强,可处理任意长度的行 | 代码复杂,需要手动管理内存和字符串拼接 | 需要精细控制读取过程,或处理非标准文本格式的特殊情况。 |
给初学者的建议:
直接学习和使用
fgets(),它是读取文本文件最标准、最安全、最方便的方式,掌握它,你就能解决绝大多数文件读取字符串的问题。
