Linux:输入输出与文件操作函数学习笔记整理

导言

在 C 语言的编程世界里,输入输出以及文件操作是与外界交互、处理数据存储的重要环节。scanfprintfopenfopenreadfreadwritefwritefseeklseekmmap这些函数各司其职,共同构建起强大的数据处理体系。本文将详细介绍这些函数的功能、用法、示例,并进行对比分析。

一、标准输入输出函数:scanf 与 printf

1. printf 函数及其变体

printf函数是 C 标准输入输出库(stdio.h)中用于格式化输出的函数,其原型为int printf(const char *format, ...)。其中,format是格式控制字符串,包含普通文本和格式说明符;...表示可变参数列表。

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
int num = 10;
float f = 3.14;
char str[] = "Hello, World!";
printf("整数:%d,浮点数:%f,字符串:%s\n", num, f, str);
return 0;
}

在上述代码中,%d%f%s分别对应十进制整数、浮点数、字符串的输出格式,将变量的值以指定格式输出到标准输出流(通常是终端)。

格式说明符

格式说明符 功能描述 示例
%d 输出带符号的十进制整数 printf("%d", 10); 输出 10
%f 输出浮点数(默认保留 6 位小数) printf("%f", 3.14); 输出 3.140000
%.nf 输出浮点数并指定保留 n 位小数 printf("%.2f", 3.1415926); 输出 3.14
%s 输出以空字符\0结尾的字符串 printf("%s", "Hello"); 输出 Hello

printf还有两个重要变体:

  • fprintf函数:用于将格式化数据输出到指定文件,原型为int fprintf(FILE *stream, const char *format, ...)
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
FILE *fp;
int num = 20;
fp = fopen("test.txt", "w");
if (fp != NULL) {
fprintf(fp, "文件中的整数:%d\n", num);
fclose(fp);
}
return 0;
}
  • sprintf函数:将格式化数据写入字符数组,原型为int sprintf(char *str, const char *format, ...)
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
char buffer[100];
int num = 30;
sprintf(buffer, "字符串中的整数:%d", num);
printf("%s\n", buffer);
return 0;
}

2. scanf 函数及其变体

scanf函数用于从标准输入设备(通常是键盘)读取格式化数据,原型为int scanf(const char *format, ...)。与printf不同,scanf的可变参数列表需要传入变量的地址,通过指针实现数据写入。

1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
int num;
printf("请输入一个整数:");
scanf("%d", &num);
printf("你输入的整数是:%d\n", num);
return 0;
}

上述代码通过%d格式说明符读取整数,并使用&num获取变量num的地址,将输入的数据存储到num中。

scanf的变体:

  • fscanf函数:从指定文件流中读取格式化数据,原型为int fscanf(FILE *stream, const char *format, ...)
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>

int main() {
FILE *fp;
int num;
fp = fopen("test.txt", "r");
if (fp != NULL) {
fscanf(fp, "文件中的整数:%d", &num);
printf("从文件中读取的整数是:%d\n", num);
fclose(fp);
}
return 0;
}
  • sscanf函数:从字符串中解析格式化数据,原型为int sscanf(const char *str, const char *format, ...)
1
2
3
4
5
6
7
8
9
#include <stdio.h>

int main() {
char str[] = "整数:15";
int num;
sscanf(str, "整数:%d", &num);
printf("从字符串中读取的整数是:%d\n", num);
return 0;
}

二、文件操作函数:open、fopen、read、fread、write、fwrite、fseek、lseek、mmap

1. 文件打开函数:open 与 fopen

open 函数

open是 UNIX/Linux 系统下的底层文件操作函数,在``头文件中声明,原型如下:

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

其中,pathname是文件路径名,flags指定打开方式(如O_RDONLY只读、O_WRONLY只写、O_RDWR读写),mode在创建新文件时指定权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd;
fd = open("test.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return -1;
}
close(fd);
return 0;
}

上述代码以只读方式打开test.txt文件,若失败通过perror输出错误信息。

fopen 函数

fopen是 C 标准库提供的高层文件操作函数,在``中声明,原型为FILE *fopen(const char *filename, const char *mode)。其中,filename是文件名,mode指定打开模式(如"r"只读、"w"只写、"a"追加写)。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main() {
FILE *fp;
fp = fopen("test.txt", "w");
if (fp == NULL) {
perror("fopen");
return -1;
}
fclose(fp);
return 0;
}

该代码以只写模式打开test.txt文件,若失败输出错误信息。

2. 文件读取函数:read 与 fread

read 函数

read是与open配套的底层文件读取函数,原型为ssize_t read(int fd, void *buf, size_t count)。其中,fdopen返回的文件描述符,buf是数据缓冲区,count是期望读取的字节数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd;
char buffer[100];
ssize_t bytes_read;
fd = open("test.txt", O_RDONLY);
if (fd == -1) {
perror("open");
return -1;
}
bytes_read = read(fd, buffer, sizeof(buffer));
if (bytes_read == -1) {
perror("read");
} else {
buffer[bytes_read] = '\0';
printf("读取的内容: %s\n", buffer);
}
close(fd);
return 0;
}

上述代码从test.txt文件读取数据到buffer缓冲区并打印。

fread 函数

fread是 C 标准库的文件读取函数,原型为size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)。其中,ptr是缓冲区指针,size是每个数据项大小,nmemb是数据项数量,streamfopen返回的文件指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>

typedef struct {
int id;
char name[50];
} Person;

int main() {
FILE *fp;
Person p[2];
size_t items_read;
fp = fopen("people.dat", "rb");
if (fp == NULL) {
perror("fopen");
return -1;
}
items_read = fread(p, sizeof(Person), 2, fp);
if (items_read < 2) {
if (feof(fp)) {
printf("已到达文件末尾,未读取到完整数据\n");
} else {
perror("fread");
}
} else {
for (int i = 0; i < 2; i++) {
printf("ID: %d, 姓名: %s\n", p[i].id, p[i].name);
}
}
fclose(fp);
return 0;
}

此代码从二进制文件people.dat中读取Person结构体数据。

3. 文件写入函数:write 与 fwrite

write 函数

write是底层文件写入函数,原型为ssize_t write(int fd, const void *buf, size_t count)。其中,fd是文件描述符,buf是待写入数据缓冲区,count是写入字节数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd;
char message[] = "Hello, file!";
ssize_t bytes_written;
fd = open("test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
perror("open");
return -1;
}
bytes_written = write(fd, message, sizeof(message));
if (bytes_written == -1) {
perror("write");
}
close(fd);
return 0;
}

上述代码向test.txt文件写入字符串数据。

fwrite 函数

fwrite用于向文件写入数据,原型为size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

typedef struct {
int id;
char name[50];
} Person;

int main() {
FILE *fp;
Person p = {1, "Alice"};
fp = fopen("people.dat", "wb");
if (fp == NULL) {
perror("fopen");
return -1;
}
if (fwrite(&p, sizeof(Person), 1, fp) != 1) {
perror("fwrite");
}
fclose(fp);
return 0;
}

该示例将Person结构体数据写入二进制文件people.dat

4. 文件定位函数:fseek 与 lseek

fseek 函数

fseek是 C 标准库中的文件定位函数,用于设置文件指针的位置,原型为int fseek(FILE *stream, long int offset, int whence)。其中,streamfopen返回的文件指针,offset是偏移量,whence指定起始位置(SEEK_SET文件开头、SEEK_CUR当前位置、SEEK_END文件末尾) 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

int main() {
FILE *fp;
fp = fopen("test.txt", "r+");
if (fp == NULL) {
perror("fopen");
return -1;
}

// 将文件指针移动到文件末尾
if (fseek(fp, 0, SEEK_END) == 0) {
// 写入新内容
fputs(" appended text", fp);
} else {
perror("fseek");
}

fclose(fp);
return 0;
}

上述代码以读写模式打开文件,使用fseek将文件指针移动到末尾,并追加新的文本内容。

lseek 函数

lseek是 UNIX/Linux 系统下的底层文件定位函数,原型为off_t lseek(int fd, off_t offset, int whence)。其中,fdopen返回的文件描述符,offsetwhence含义与fseek类似 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
int fd;
off_t new_position;
fd = open("test.txt", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}

// 将文件指针移动到文件开头后5个字节的位置
new_position = lseek(fd, 5, SEEK_SET);
if (new_position == -1) {
perror("lseek");
} else {
// 进行读写操作...
}

close(fd);
return 0;
}

上述代码通过lseek实现文件指针的底层定位,可用于后续的读写操作。

5. 内存映射函数:mmap

mmap是 UNIX/Linux 系统下用于内存映射的函数,它可以将文件内容映射到内存区域,实现高效的数据访问。其原型为void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)。其中,addr指定映射的起始地址(通常设为NULL由系统分配),length是映射的字节数,prot指定内存保护模式(如PROT_READ可读、PROT_WRITE可写),flags指定映射标志(如MAP_SHARED共享映射),fd是文件描述符,offset是文件内的偏移量 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>

int main() {
int fd;
void *map_start;
struct stat file_stat;
fd = open("test.txt", O_RDWR);
if (fd == -1) {
perror("open");
return -1;
}

if (fstat(fd, &file_stat) == -1) {
perror("fstat");
close(fd);
return -1;
}

map_start = mmap(NULL, file_stat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_start == MAP_FAILED) {
perror("mmap");
close(fd);
return -1;
}

// 修改映射区域的数据
char *data = (char *)map_start;
data[0] = 'X';

if (munmap(map_start, file_stat.st_size) == -1) {
perror("munmap");
}
close(fd);
return 0;
}

上述代码通过mmap将文件内容映射到内存,直接修改内存中的数据,实现高效的文件数据修改,最后通过munmap解除映射。

三、函数对比与总结

功能分类 函数名 特点与应用场景 注意事项
标准输出 printf 格式化输出到标准输出流(通常为终端),支持 % d、% s 等格式控制符,用于实时展示程序运行结果。 输出缓存可能导致数据延迟显示,可使用fflush(stdout)强制刷新;格式控制符需与参数类型匹配。
fprintf 格式化输出到指定文件流,用于持久化存储结构化数据,如日志文件或配置文件写入。 文件指针需提前通过fopen打开,且注意文件打开模式对写入权限的影响。
sprintf 将格式化数据写入字符数组,常用于动态生成字符串(如拼接 URL、格式化时间戳)。 目标数组需确保足够大,避免缓冲区溢出;结果字符串以\0结尾。
标准输入 scanf 从标准输入流(通常为键盘)读取格式化数据,通过变量地址传递结果,需注意输入验证。 容易引发缓冲区溢出(如%s不限制长度),推荐使用fgets结合sscanf替代。
fscanf 从文件流中解析格式化数据,适用于读取结构化文件(如 CSV、配置文件)。 需配合fopen打开文件,注意文件指针位置及读取失败时的错误处理。
sscanf 从字符串中提取格式化数据,常用于文本分析或协议解析(如解析 HTTP 请求头)。 源字符串必须以\0结尾,支持%n等特殊控制符获取读取字符数。
文件打开 open 底层系统调用,返回文件描述符(整数),支持设置文件权限、O_CREAT/O_RDWR 等标志位。 底层系统调用,返回文件描述符(整数),支持设置文件权限、O_CREAT/O_RDWR 等标志位。
fopen 高层文件操作函数,返回文件指针(FILE*),支持rwa等简易模式及缓冲机制。 返回NULL时需检查errno判断错误原因(如文件不存在、权限不足)。
文件读取 read 基于文件描述符的底层读取操作,按字节读取数据,常用于高性能、非格式化文件读取。 需手动计算读取字节数,适合处理二进制文件,不支持文本模式转换。
fread 从文件流中读取指定长度的数据块,适用于读取结构体、数组等二进制数据。 需指定数据项大小及数量,返回实际读取的项数,可能因文件尾或错误中断。
文件写入 write 基于文件描述符的底层写入操作,按字节写入数据,常用于高效写入二进制文件。 需处理返回值判断写入成功字节数,注意文件描述符可写权限。
fwrite 向文件流写入指定长度的数据块,适合写入结构体、数组等二进制数据。 需指定数据项大小及数量,写入失败时检查ferror获取错误信息。