在标准的 C 语言库函数中,并没有一个叫做 readfile() 的函数。
你可能是在其他语言(如 PHP)中看到了这个函数名,或者是在某个特定的 C 库或框架中自定义的函数,在标准 C 中,文件操作是通过一组以 f(file)开头的函数来完成的,fopen(), fread(), fgets(), fgetc() 等。
下面,我将为你详细介绍标准 C 语言中读取文件的各种方法和最佳实践。
核心概念:文件指针
在 C 语言中,所有文件操作都通过一个指向 FILE 结构体的指针来完成,这个指针被称为文件指针,你可以把它想象成一个书签,它记录了当前在文件中的读写位置。
在开始任何文件操作之前,你必须使用 fopen() 函数打开文件,它会返回这个文件指针。
FILE *fopen(const char *filename, const char *mode);
filename: 你要打开的文件名(可以包含路径)。mode: 打开文件的模式,"r": 以只读模式打开文件,文件必须存在。"w": 以只写模式打开文件,如果文件存在,则清空内容;如果不存在,则创建新文件。"a": 以追加模式打开文件,如果文件存在,则在末尾写入;如果不存在,则创建新文件。"r+": 以读写模式打开文件,文件必须存在。"w+": 以读写模式打开文件,如果文件存在,则清空内容;如果不存在,则创建新文件。"a+": 以读写模式打开文件,如果文件存在,则在末尾写入和读取;如果不存在,则创建新文件。
文件读取的几种主要方法
根据不同的需求,你可以选择不同的函数来读取文件内容。
fread() - 读取二进制数据或固定大小的块
fread() 是最通用的读取函数,它从一个文件中读取一个数据块。
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr: 一个指向内存缓冲区的指针,用于存放从文件中读取的数据。size: 每个数据项的大小(单位是字节)。nmemb: 要读取的数据项的数量。stream: 文件指针。- 返回值: 成功读取的数据项的数量,如果到达文件末尾或发生错误,则可能小于
nmemb。
示例:读取整个文件到内存
#include <stdio.h>
#include <stdlib.h> // for exit()
int main() {
FILE *fp;
char *filename = "my_data.bin";
char *buffer;
long file_size;
// 1. 打开文件
fp = fopen(filename, "rb"); // "rb" 表示二进制读取模式
if (fp == NULL) {
perror("Error opening file");
return 1;
}
// 2. 获取文件大小
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
rewind(fp); // 将文件指针重置到开头
// 3. 分配内存
buffer = (char *)malloc(file_size * sizeof(char));
if (buffer == NULL) {
perror("Memory allocation failed");
fclose(fp);
return 1;
}
// 4. 读取文件
size_t result = fread(buffer, 1, file_size, fp);
if (result != file_size) {
perror("Error reading file");
free(buffer);
fclose(fp);
return 1;
}
// 5. buffer 中就包含了整个文件的内容
printf("Successfully read %ld bytes from file.\n", file_size);
// 在这里处理 buffer 中的数据...
// 打印前100个字节
for (int i = 0; i < 100 && i < file_size; i++) {
printf("%c", buffer[i]);
}
printf("\n");
// 6. 释放内存并关闭文件
free(buffer);
fclose(fp);
return 0;
}
fgets() - 按行读取文本
fgets() 函数从文件中读取一行,直到遇到换行符 \n、文件结束符 EOF,或者读取了指定的字符数 - 1。
char *fgets(char *str, int n, FILE *stream);
str: 一个字符数组,用于存放读取的行。n: 要读取的最大字符数(包括结尾的\0)。stream: 文件指针。- 返回值: 成功时返回
str指针;如果到达文件末尾或发生错误,则返回NULL。
示例:逐行读取文本文件
#include <stdio.h>
int main() {
FILE *fp;
char filename[] = "my_text.txt";
char buffer[256]; // 每行最多读取255个字符
fp = fopen(filename, "r");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
printf("--- File Content ---\n");
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
// fgets 会保留行尾的换行符 \n
printf("%s", buffer);
}
fclose(fp);
return 0;
}
fgetc() - 逐个字符读取
fgetc() 函数从文件中读取一个字符。
int fgetc(FILE *stream);
stream: 文件指针。- 返回值: 返回读取的字符(作为
int类型),如果到达文件末尾或发生错误,则返回EOF。
示例:逐个字符读取并统计行数
#include <stdio.h>
int main() {
FILE *fp;
char filename[] = "my_text.txt";
int c;
int line_count = 0;
fp = fopen(filename, "r");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
while ((c = fgetc(fp)) != EOF) {
if (c == '\n') {
line_count++;
}
}
// 注意:如果文件最后一行没有换行符,行数会少一
// 更精确的做法是检查第一个字符是否为EOF
rewind(fp);
if (fgetc(fp) != EOF) {
line_count++; // 确保至少有一行
}
printf("The file has %d lines.\n", line_count);
fclose(fp);
return 0;
}
关键的辅助函数
-
*`feof(FILE stream)`**
- 作用: 检查文件指针是否到达了文件的末尾。
- 返回值: 如果到达末尾,返回非零值(真);否则返回 0(假)。
- 重要提示:
feof()在读取尝试失败后才会返回真,当fread()或fgets()因为已经到达文件末尾而返回时,feof()才会返回真,你不能用它来预判是否还有内容。
-
*`ferror(FILE stream)`**
- 作用: 检查文件流上是否发生了错误。
- 返回值: 如果发生错误,返回非零值(真);否则返回 0(假)。
-
*`clearerr(FILE stream)`**
- 作用: 清除文件流的文件结束标志和错误标志。
最佳实践:一个健壮的文件读取循环
在读取文件时,正确的循环判断至关重要,推荐使用 fread 或 fgets 的返回值作为循环条件,并在循环体内用 feof 和 ferror 来诊断问题。
// 使用 fgets 的健壮循环
FILE *fp = fopen("file.txt", "r");
if (!fp) { /* 错误处理 */ }
char line[1024];
while (fgets(line, sizeof(line), fp)) {
// 成功读取了一行,处理 line
printf("Read line: %s", line);
}
// 循环结束后,检查原因
if (feof(fp)) {
printf("Reached end of file.\n");
} else if (ferror(fp)) {
perror("An error occurred while reading the file.");
}
fclose(fp);
如何选择合适的函数?
| 场景 | 推荐函数 | 原因 |
|---|---|---|
| 读取整个文件到内存 | fread() + fseek/ftell |
效率高,一次调用即可完成,适合二进制文件或大文本文件。 |
| 逐行处理文本文件 | fgets() |
最简单、最安全的方式,能正确处理不同平台的换行符。 |
| 逐个字符处理 | fgetc() |
适用于需要检查每个字符的特殊逻辑(如统计字符、词法分析等)。 |
| 已知固定大小的数据结构 | fread() |
例如读取一个包含多个学生信息的结构化数组,fread 一次可以读入一个或多个结构体。 |
每次使用 fopen() 打开文件后,在程序结束前都必须使用 fclose() 关闭它,以确保所有缓冲区的数据都被正确写入磁盘,并释放系统资源。
