- 核心概念:文件描述符 vs. 文件指针
- C 标准库 I/O (高级接口,推荐日常使用)
- 打开文件:
fopen() - 关闭文件:
fclose() - 写入数据:
fprintf(),fputs(),fwrite() - 读取数据:
fscanf(),fgets(),fread() - 定位文件指针:
fseek(),ftell(),rewind() - 判断文件结束:
feof() - 错误处理:
ferror(),perror() - 刷新缓冲区:
fflush()
- 打开文件:
- Linux 系统调用 I/O (底层接口,性能更高,更灵活)
- 打开文件:
open() - 创建文件:
creat()(已不推荐,使用open()替代) - 关闭文件:
close() - 写入数据:
write() - 读取数据:
read() - 定位文件指针:
lseek()
- 打开文件:
- 综合示例与对比
- 最佳实践与注意事项
核心概念:文件描述符 vs. 文件指针
在开始之前,理解这两个概念至关重要。

(图片来源网络,侵删)
-
文件指针
- 来源:C 标准库 (
stdio.h)。 - 本质:一个指向
FILE结构体的指针。FILE结构体包含了文件缓冲区、当前读写位置、文件描述符等丰富的信息。 - 特点:高级、易用、带缓冲,I/O 操作通过函数如
fread(),fwrite()完成,这些函数会先将数据读写到内存缓冲区,再批量与磁盘交互,大大提高了 I/O 效率。 - 示例:
FILE *fp;
- 来源:C 标准库 (
-
文件描述符
- 来源:Linux/Unix 操作系统内核。
- 本质:一个非负整数,是内核为了管理一个进程所打开的文件而创建的索引,它指向内核中维护的文件表。
- 特点:底层、高效、无缓冲,直接与内核交互,没有标准库的额外开销,适合需要极致性能或精细控制的场景(如网络编程、设备驱动)。
- 标准文件描述符:
0: 标准输入1: 标准输出2: 标准错误
关系:当你使用 fopen() 打开一个文件时,C 标准库会向内核请求一个文件描述符,并将其存放在 FILE 结构体中,你通过 FILE* 指针操作文件,实际上是 FILE 结构体内部的文件描述符在与内核通信。
C 标准库 I/O (高级接口)
这是最常用、最安全的方式,适用于绝大多数文件操作场景。

(图片来源网络,侵删)
1 打开文件:fopen()
#include <stdio.h> FILE *fopen(const char *pathname, const char *mode);
-
pathname: 文件路径。 -
mode: 打开模式,常用模式如下:"r": 只读,文件必须存在。"w": 只写,如果文件存在,则清空内容;如果不存在,则创建新文件。"a": 追加,如果文件存在,则在末尾写入;如果不存在,则创建新文件。"r+": 读写,文件必须存在,读写指针在文件开头。"w+": 读写,如果文件存在,则清空内容;如果不存在,则创建新文件。"a+": 读写,如果文件存在,则在末尾写入;如果不存在,则创建新文件,读操作可以从任意位置开始,但写操作总是在末尾。
-
返回值:成功返回
FILE*指针,失败返回NULL。
2 关闭文件:fclose()
#include <stdio.h> int fclose(FILE *stream);
stream:fopen()返回的文件指针。- 返回值:成功返回
0,失败返回EOF(通常是-1)。
3 写入数据
fprintf(): 格式化写入,类似于printf()。fprintf(fp, "Name: %s, Age: %d\n", "Alice", 30);
fputs(): 写入一个字符串,不自动换行。fputs("Hello, World!\n", fp);fwrite(): 以二进制块形式写入数据。int data[] = {10, 20, 30, 40, 50}; size_t items_written = fwrite(data, sizeof(int), 5, fp); // 写入5个int
4 读取数据
fscanf(): 格式化读取,类似于scanf()。char name[50]; int age; fscanf(fp, "Name: %s, Age: %d\n", name, &age);
fgets(): 从文件中读取一行,包括换行符\n。char buffer[256]; if (fgets(buffer, sizeof(buffer), fp) != NULL) { // buffer 中包含一行内容 }fread(): 以二进制块形式读取数据。int data[5]; size_t items_read = fread(data, sizeof(int), 5, fp); // 读取5个int
5 定位文件指针
fseek(): 将文件指针移动到指定位置。fseek(fp, 0, SEEK_SET); // 移动到文件开头 (SEEK_SET: 0) fseek(fp, 10, SEEK_CUR); // 从当前位置向后移动10字节 (SEEK_CUR: 1) fseek(fp, -10, SEEK_END); // 从文件末尾向前移动10字节 (SEEK_END: 2)
ftell(): 获取当前文件指针的位置(相对于文件开头)。long position = ftell(fp);
rewind(): 将文件指针重置到文件开头,相当于fseek(fp, 0, SEEK_SET)。
6 判断文件结束与错误
feof(): 检查是否到达文件结尾。while (!feof(fp)) { // 循环读取直到文件末尾 }注意:
feof()在读取到文件末尾的下一次调用时才会返回真,在循环条件中直接判断fread()或fgets()的返回值更可靠。
(图片来源网络,侵删)ferror(): 检查文件流是否发生错误。if (ferror(fp)) { perror("Error reading from file"); }perror(): 打印一条描述性错误信息到标准错误流 (stderr)。if (fp == NULL) { perror("fopen failed"); // 会打印 "fopen failed: [具体的系统错误信息,如 No such file or directory]" }
Linux 系统调用 I/O (底层接口)
当你需要最高性能或访问标准库不支持的文件类型(如 /dev/null)时,可以使用系统调用。
1 打开文件:open()
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int open(const char *pathname, int flags, mode_t mode);
pathname: 文件路径。flags: 打开标志,常用标志(可通过 组合):O_RDONLY: 只读O_WRONLY: 只写O_RDWR: 读写O_CREAT: 如果文件不存在,则创建它,需要提供mode参数。O_TRUNC: 如果文件存在且以写/读写方式打开,则清空文件。O_APPEND: 每次写入都追加到文件末尾。
mode: 创建文件时的权限(仅当O_CREAT被设置时才有效)。0644(rw-r--r--)。- 返回值:成功返回文件描述符(一个整数),失败返回
-1。
2 关闭文件:close()
#include <unistd.h> int close(int fd);
fd:open()返回的文件描述符。- 返回值:成功返回
0,失败返回-1。
3 写入数据:write()
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
fd: 文件描述符。buf: 要写入数据的缓冲区指针。count: 要写入的字节数。- 返回值:成功返回实际写入的字节数,失败返回
-1。
4 读取数据:read()
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
fd: 文件描述符。buf: 存放读取数据的缓冲区指针。count: 要读取的最大字节数。- 返回值:成功返回实际读取的字节数(可能小于
count),到达文件末尾返回0,失败返回-1。
5 定位文件指针:lseek()
#include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
fd: 文件描述符。offset: 偏移量。whence: 起始位置,与fseek()相同:SEEK_SET,SEEK_CUR,SEEK_END。- 返回值:成功返回新的文件指针位置,失败返回
-1。
综合示例与对比
示例1:使用 C 标准库写入并读取文本文件
#include <stdio.h>
#include <string.h>
int main() {
FILE *fp;
char filename[] = "test_std.txt";
char buffer[100];
// --- 写入 ---
fp = fopen(filename, "w"); // 以写模式打开
if (fp == NULL) {
perror("Failed to open file for writing");
return 1;
}
fprintf(fp, "Hello from C Standard I/O!\n");
fputs("This is the second line.\n", fp);
fclose(fp);
printf("File written successfully.\n");
// --- 读取 ---
fp = fopen(filename, "r"); // 以读模式打开
if (fp == NULL) {
perror("Failed to open file for reading");
return 1;
}
printf("\n--- Reading file content ---\n");
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
fclose(fp);
return 0;
}
示例2:使用 Linux 系统调用写入并读取二进制文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
int fd;
char filename[] = "test_sys.dat";
char data[] = "Hello from System Calls!";
char read_buffer[50];
ssize_t bytes_written, bytes_read;
// --- 写入 ---
// O_WRONLY | O_CREAT | O_TRUNC: 只写,创建,清空
// 0644: 权限 rw-r--r--
fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("Failed to open file for writing");
return 1;
}
bytes_written = write(fd, data, strlen(data));
if (bytes_written == -1) {
perror("Failed to write to file");
close(fd);
return 1;
}
printf("Wrote %zd bytes.\n", bytes_written);
close(fd);
// --- 读取 ---
fd = open(filename, O_RDONLY);
if (fd == -1) {
perror("Failed to open file for reading");
return 1;
}
printf("\n--- Reading file content ---\n");
bytes_read = read(fd, read_buffer, sizeof(read_buffer) - 1);
if (bytes_read == -1) {
perror("Failed to read from file");
close(fd);
return 1;
}
read_buffer[bytes_read] = '\0'; // 确保字符串正确终止
printf("Read %zd bytes: %s\n", bytes_read, read_buffer);
close(fd);
return 0;
}
最佳实践与注意事项
- 检查返回值:所有 I/O 函数都可能失败。务必检查它们的返回值,并根据错误情况进行处理(如使用
perror打印错误信息)。 - 关闭文件:使用完文件后,一定要记得关闭,否则可能导致文件描述符泄漏(系统资源耗尽)或数据丢失(缓冲区中的数据可能没有真正写入磁盘)。
- 选择合适的接口:
- 优先使用 C 标准库 (
fopen,fread等):它更安全、更方便,并且自带缓冲机制,对大多数应用来说性能已经足够好。 - 仅在必要时使用系统调用 (
open,read等):在需要处理大量小文件、追求极致性能、或与设备/网络交互时。
- 优先使用 C 标准库 (
- 缓冲区大小:对于大文件读写,合理设置缓冲区大小可以显著提高性能,标准库会自动管理缓冲区,而使用系统调用时,你需要自己管理读写缓冲区。
- 二进制 vs. 文本模式:在 Windows 平台上,C 标准库的
"b"模式(如"rb","wb")非常重要,因为它会处理\r\n和\n的转换,在 Linux 上,这个区别不大,但为了代码的可移植性,处理二进制文件时最好也加上"b"模式。 - 原子性操作:如果多个进程同时读写同一个文件,可能会发生数据错乱,如果需要原子性操作(如写入一个完整的记录),可以考虑使用
O_APPEND标志(系统调用)或文件锁(flock或fcntl)。
