一、系统调用 vs 标准库函数
Linux 下操作文件有两种方式:
| 类型 | 函数 | 头文件 | 返回值 |
|---|---|---|---|
| 标准库函数 | fopen / fclose / fread / fwrite |
<stdio.h> |
FILE* 文件指针 |
| 系统调用 | open / close / read / write |
<fcntl.h> <unistd.h> |
int 文件描述符 |
区别:
-
标准库函数是 C 标准库提供的,带缓冲区,跨平台
-
系统调用是操作系统提供的接口,无缓冲区,直接操作内核
二、文件描述符
文件描述符是 open 函数打开文件后返回的整数,用来标识一个打开的文件。
| 文件描述符 | 对应流 | 说明 |
|---|---|---|
| 0 | stdin |
标准输入(键盘) |
| 1 | stdout |
标准输出(屏幕) |
| 2 | stderr |
标准错误输出(屏幕) |
-
新打开的文件从 3 开始递增
-
上限受系统文件表限制
三、open() - 打开/创建文件
cpp
#include <fcntl.h>
#include <unistd.h>
int open(const char* pathname, int flags);
int open(const char* pathname, int flags, mode_t mode);
参数详解
| 参数 | 含义 | 常见取值 |
|---|---|---|
pathname |
文件路径 | "test.txt", "/home/user/file" |
flags |
打开方式 | 见下表 |
mode |
权限(仅创建时有效) | 0644, 0666 |
flags 取值
必选其一:
| 宏 | 含义 |
|---|---|
O_RDONLY |
只读 |
O_WRONLY |
只写 |
O_RDWR |
读写 |
可选组合:
| 宏 | 含义 |
|---|---|
O_CREAT |
文件不存在则创建 |
O_APPEND |
追加到末尾 |
O_TRUNC |
清空文件 |
mode 权限
cpp
0644 // rw-r--r-- 所有者可读写,其他人只读
0666 // rw-rw-rw- 所有人可读写
返回值
-
成功:返回文件描述符(非负整数,如 3、4、5...)
-
失败:返回 -1
cpp
int fd = open("test.txt", O_RDWR | O_CREAT, 0644);
if (fd == -1) {
perror("open");
exit(1);
}
四、close() - 关闭文件
cpp
#include <unistd.h>
int close(int fd);
参数
| 参数 | 含义 |
|---|---|
fd |
文件描述符(open 的返回值) |
返回值
-
成功:返回 0
-
失败:返回 -1
cpp
close(fd);
五、write() - 写入文件
cpp
#include <unistd.h>
ssize_t write(int fd, const void* buf, size_t count);
参数
| 参数 | 含义 | 示例 |
|---|---|---|
fd |
文件描述符 | fd |
buf |
要写入的数据的指针 | "Hello", &ch, arr |
count |
要写入的字节数 | strlen(str), sizeof(buf) |
返回值
-
成功:返回实际写入的字节数
-
失败:返回 -1
cpp
char* str = "Hello World";
ssize_t ret = write(fd, str, strlen(str));
if (ret == -1) {
perror("write");
}
六、read() - 读取文件
cpp
#include <unistd.h>
ssize_t read(int fd, void* buf, size_t count);
参数
| 参数 | 含义 | 示例 |
|---|---|---|
fd |
文件描述符 | fd |
buf |
存放读取数据的缓冲区 | char buf[1024] |
count |
要读取的最大字节数 | sizeof(buf) |
返回值
-
成功:返回实际读取的字节数(0 表示读到文件末尾)
-
失败:返回 -1
cpp
char buf[1024];
ssize_t ret = read(fd, buf, sizeof(buf) - 1);
if (ret == -1) {
perror("read");
}
else if (ret == 0) {
printf("文件末尾\n");
}
else {
buf[ret] = '\0'; // 添加字符串结束符
printf("读取 %ld 字节: %s\n", ret, buf);
}
七、lseek() - 移动文件偏移量
文件读写时存在偏移量 (当前读写位置),lseek 可以主动设置该偏移量。
cpp
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数
| 参数 | 含义 | 取值 |
|---|---|---|
fd |
文件描述符 | open() 的返回值 |
offset |
偏移量(字节数) | 正数(向后)、负数(向前)、0 |
whence |
基准位置 | 见下表 |
whence 的三种取值
| 宏 | 数值 | 含义 |
|---|---|---|
SEEK_SET |
0 | 从文件开头开始偏移 |
SEEK_CUR |
1 | 从当前位置开始偏移 |
SEEK_END |
2 | 从文件末尾开始偏移 |
常见用法
cpp
// 获取当前位置
off_t pos = lseek(fd, 0, SEEK_CUR);
// 移动到文件开头
lseek(fd, 0, SEEK_SET);
// 移动到文件末尾
lseek(fd, 0, SEEK_END);
// 获取文件大小
off_t size = lseek(fd, 0, SEEK_END);
// 从当前位置向后移动 10 字节
lseek(fd, 10, SEEK_CUR);
返回值
-
成功:返回新的文件偏移量(从文件开头计算的字节数)
-
失败:返回 -1
八、fgets() - 标准库读取一行
cpp
#include <stdio.h>
char* fgets(char* s, int size, FILE* stream);
参数
| 参数 | 含义 | 示例 |
|---|---|---|
s |
存放字符串的缓冲区 | char buf[256] |
size |
最多读取 size-1 个字符 | sizeof(buf) |
stream |
文件流指针 | stdin(标准输入), fp(文件指针) |
返回值
-
成功:返回
s(缓冲区地址) -
失败或文件末尾:返回
NULL
特点
-
自动添加
\0 -
会读取换行符
\n(与gets的区别)
cpp
FILE* fp = fopen("test.txt", "r");
char buf[256];
while (fgets(buf, sizeof(buf), fp) != NULL) {
printf("%s", buf); // buf 中已包含换行符
}
fclose(fp);
九、完整示例
cpp
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main() {
// 1. 打开/创建文件
int fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return 1;
}
// 2. 写入数据
char* str = "Hello Linux File Operation\n";
ssize_t ret = write(fd, str, strlen(str));
if (ret == -1) {
perror("write");
close(fd);
return 1;
}
printf("写入 %ld 字节\n", ret);
// 3. 移动偏移量到文件开头
lseek(fd, 0, SEEK_SET);
// 4. 读取数据
char buf[256];
ret = read(fd, buf, sizeof(buf) - 1);
if (ret == -1) {
perror("read");
close(fd);
return 1;
}
buf[ret] = '\0';
printf("读取 %ld 字节: %s", ret, buf);
// 5. 关闭文件
close(fd);
return 0;
}
十、总结
| 知识点 | 核心要点 |
|---|---|
| 系统调用 vs 标准库 | 系统调用无缓冲、直接操作内核;标准库带缓冲、跨平台 |
| 文件描述符 | 0(stdin)、1(stdout)、2(stderr),新文件从 3 开始 |
| open() | O_RDONLY/O_WRONLY/O_RDWR + O_CREAT/O_APPEND/O_TRUNC |
| read()/write() | 返回实际读/写字节数,0 表示文件末尾,-1 表示错误 |
| lseek() | SEEK_SET(开头)、SEEK_CUR(当前)、SEEK_END(末尾) |
| fgets() | 自动加 \0,会读取 \n,读到末尾返回 NULL |