C语言文件操作与系统调用学习笔记
1. C标准I/O库函数回顾
1.1 打开/关闭文件
1.1.1 fopen函数
fopen
函数用于打开文件,其原型为:
FILE *fopen(const char *restrict filename, const char *restrict mode);
-
参数:
filename
:要打开文件的路径和名称。mode
:访问模式,包括:"r"
:只读模式,文件必须存在。"w"
:只写模式,文件存在则清空,不存在则创建。"a"
:只追加写模式,文件不存在则创建。"r+"
:读写模式,文件必须存在。"w+"
:读写模式,文件存在则清空,不存在则创建。"a+"
:读写追加模式,文件不存在则创建。
-
返回值:成功返回
FILE
结构体指针,失败返回NULL
。
示例代码:
#include <stdio.h>
int main() {
char *filename = "io.txt";
FILE *ioFile = fopen(filename, "a+");
if (ioFile == NULL) {
printf("FAILED,a+不能打开不存在的文件\n");
} else {
printf("SUCCESS,a+能打开不存在的文件\n");
}
}
1.1.2 fclose函数
fclose
函数用于关闭文件,其原型为:
int fclose(FILE *stream);
-
参数:
stream
,需要关闭的文件。 -
返回值:成功返回
0
,失败返回EOF
(负数)。
示例代码:
#include <stdio.h>
int main() {
char *filename = "io1.txt";
FILE *ioFile = fopen(filename, "r");
if (ioFile == NULL) {
printf("r不能打开不存在的文件\n");
} else {
printf("r能打开不存在的文件\n");
}
int result = fclose(ioFile);
if (result != 0) {
printf("关闭文件失败");
return 1;
}
return 0;
}
1.2 向文件中写入数据
1.2.1 fputc函数
fputc
函数用于向文件写入单个字符,其原型为:
int fputc(int c, FILE *stream);
-
参数:
c
:要写入的字符(按ASCII值)。stream
:要写入的文件。
-
返回值:成功返回字符的值,失败返回
EOF
。
示例代码:
#include <stdio.h>
int main() {
char *filename = "io.txt";
FILE *ioFile = fopen(filename, "a+");
if (ioFile == NULL) {
printf("a+不能打开不存在的文件\n");
} else {
printf("a+能打开不存在的文件\n");
}
int putcR = fputc(97, ioFile);
if (putcR == EOF) {
printf("写入字符失败\n");
} else {
printf("写入字符成功:%c\n", putcR);
}
int result = fclose(ioFile);
if (result != 0) {
printf("关闭文件失败");
return 1;
}
return 0;
}
1.2.2 fputs函数
fputs
函数用于向文件写入字符串,其原型为:
int fputs(const char *restrict s, FILE *restrict stream);
-
参数:
s
:需要写入的字符串。stream
:要写入的文件。
-
返回值:成功返回非负整数(一般是0或1),失败返回
EOF
。
示例代码:
#include <stdio.h>
int main() {
char *filename = "io.txt";
FILE *ioFile = fopen(filename, "a+");
if (ioFile == NULL) {
printf("a+不能打开不存在的文件\n");
} else {
printf("a+能打开不存在的文件\n");
}
int putsR = fputs(" love letter\n", ioFile);
if (putsR == EOF) {
printf("写入字符串失败\n");
} else {
printf("写入字符串成功:%d\n", putsR);
}
int result = fclose(ioFile);
if (result != 0) {
printf("关闭文件失败");
return 1;
}
return 0;
}
1.2.3 fprintf函数
fprintf
函数用于格式化输出到文件,其原型为:
int fprintf(FILE *restrict stream, const char *restrict format, ...);
-
参数:
stream
:要写入的文件。format
:格式化字符串。...
:变长参数列表,用于匹配格式化字符串中的占位符。
-
返回值:成功返回写入字符总数(不包含换行符),失败返回
EOF
。
示例代码:
#include <stdio.h>
int main() {
char *filename = "io.txt";
FILE *ioFile = fopen(filename, "a+");
if (ioFile == NULL) {
printf("a+不能打开不存在的文件\n");
} else {
printf("a+能打开不存在的文件\n");
}
char *name = "大海";
int fprintfR = fprintf(ioFile, "哎呀,那边窗户透出了什么光?\n那是东方,而你则是太阳!\n升起吧,骄阳,去让忌妒的月黯然失色!\n\t\t%s", name);
if (fprintfR == EOF) {
printf("写入字符串失败");
} else {
printf("写入字符串成功:%d\n", fprintfR);
}
int result = fclose(ioFile);
if (result != 0) {
printf("关闭文件失败");
fprintf(stderr, "%s\n", filename);
return 1;
}
return 0;
}
1.3 从文件中读取数据
1.3.1 fgetc函数
fgetc
函数用于从文件读取单个字符,其原型为:
int fgetc(FILE *stream);
-
参数:
stream
,需要读取的文件。 -
返回值:读取的一个字节,到文件结尾或出错返回
EOF
。
示例代码:
#include <stdio.h>
int main() {
FILE *ioFile = fopen("io.txt", "r");
if (ioFile == NULL) {
printf("不能读不存在的文件");
}
char c = fgetc(ioFile);
while (c != EOF) {
printf("%c", c);
c = fgetc(ioFile);
}
int result = fclose(ioFile);
if (result != 0) {
printf("关闭文件失败");
return 1;
}
return 0;
}
1.3.2 fgets函数
fgets
函数用于从文件读取字符串,其原型为:
char *fgets(char *restrict s, int n, FILE *restrict stream);
-
参数:
s
:接收读取的数据字符串。n
:能够接收数据的长度。stream
:需要读取的文件。
-
返回值:成功返回字符串,失败返回
NULL
(可以直接用于while
循环)。
示例代码:
#include <stdio.h>
int main() {
FILE *ioFile = fopen("io.txt", "r");
if (ioFile == NULL) {
printf("不能读不存在的文件");
}
char buffer[100];
while (fgets(buffer, sizeof(buffer), ioFile)) {
printf("%s", buffer);
}
int result = fclose(ioFile);
if (result != 0) {
printf("关闭文件失败");
return 1;
}
return 0;
}
1.3.3 fscanf函数
fscanf
函数用于从文件读取格式化数据,其原型为:
int fscanf(FILE *restrict stream, const char *restrict format, ...);
-
参数:
stream
:读取的文件。format
:读取的匹配表达式。...
:变长参数列表,用于接收匹配的数据。
-
返回值:成功返回参数的个数,失败返回0,报错或结束返回
EOF
。
示例代码:
#include <stdio.h>
int main() {
FILE *userFile = fopen("user.txt", "r");
if (userFile == NULL) {
printf("不能打开不存在的文件");
}
char name[50];
int age;
char wife[50];
int scanfR;
while (scanfR = fscanf(userFile, "%s %d %s\n", name, &age, wife) != EOF) {
printf("%s在%d岁爱上了%s\n", name, age, wife);
}
int result = fclose(userFile);
if (result != 0) {
printf("关闭文件失败");
return 1;
}
return 0;
}
1.4 标准输入/输出/错误
1.4.1 标准输入/输出/错误简介
在C语言中,标准输入(stdin)、标准输出(stdout)和标准错误(stderr)是三种特殊的文件流,分别用于从控制台读取输入、向控制台输出数据和发送错误消息。
stdin
:标准输入文件流,文件描述符为0。stdout
:标准输出文件流,文件描述符为1。stderr
:标准错误文件流,文件描述符为2。
1.4.2 示例代码
stdin_out_err_test.c:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[]) {
char *ch = malloc(100);
fgets(ch, 100, stdin);
printf("你好:%s", ch);
fputs(ch, stdout);
fputs(ch, stderr);
free(ch);
return 0;
}
在这个示例中,程序从标准输入读取一行数据,然后分别通过标准输出和标准错误输出这行数据。
1.5 系统调用
1.5.1 系统调用简介
系统调用是操作系统内核提供给应用程序的接口,使得应用程序能够间接访问硬件资源。以下是一些常见的系统调用:
1. open
open
系统调用用于打开一个文件,并返回一个文件描述符。
int open(const char *__path, int __oflag, ...);
-
参数:
__path
:文件路径。__oflag
:打开文件的方式,可以是只读、只写、读写等模式的组合。...
:可选参数,如文件权限位(仅在创建新文件时使用)。
-
返回值:成功返回非负的文件描述符,失败返回-1。
2. read
read
系统调用用于从文件描述符读取数据。
ssize_t read(int __fd, void *__buf, size_t __nbytes);
-
参数:
__fd
:文件描述符。__buf
:缓冲区指针,用于存放读取的数据。__nbytes
:要读取的最大字节数。
-
返回值:成功返回实际读取的字节数,失败返回-1。
3. write
write
系统调用用于向文件描述符写入数据。
ssize_t write(int __fd, const void *__buf, size_t __n);
-
参数:
__fd
:文件描述符。__buf
:缓冲区指针,存放要写入的数据。__n
:要写入的字节数。
-
返回值:成功返回实际写入的字节数,失败返回-1。
4. close
close
系统调用用于关闭文件描述符。
int close(int __fd);
-
参数:
__fd
,要关闭的文件描述符。 -
返回值:成功关闭返回0,失败返回-1。
5. exit和_exit
_exit
:立即终止当前进程,不执行任何清理操作。exit
:终止当前进程,并执行清理操作,如刷新I/O缓冲区、关闭打开的文件等。
1.5.2 综合案例
system_call_test.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc, char const *argv[]) {
int fd = open("io.txt", O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
char buffer[1024];
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0) {
write(STDOUT_FILENO, buffer, bytes_read);
}
if (bytes_read == -1) {
perror("read");
close(fd);
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
在这个综合案例中,程序使用open
系统调用打开一个文件,然后使用read
系统调读取数据,并使用write
系统调用将数据写入标准输出。最后,使用close
系统调用关闭文件描述符。
1.6 文件描述符
1.6.1 文件描述符定义
在Linux系统中,文件描述符(File Descriptor,FD)是一个非负整数,用于标识打开的文件(或套接字)。它是操作系统为应用程序操作底层资源所提供的引用或“句柄”。
1.6.2 文件描述符的特殊含义
在Linux中,文件描述符0、1、2具有特殊含义:
- 0:标准输入(stdin)
- 1:标准输出(stdout)
- 2:标准错误(stderr)
1.6.3 文件描述符关联的数据结构
struct file
每个文件描述符都关联到内核的一个struct file
类型的结构体数据,记录了与文件相关的所有信息。
- 关键字段:
f_count
:引用计数,管理文件对象的生命周期。f_pos
:当前文件位置(读写位置)。f_path
:记录文件路径。f_inode
:指向与文件相关联的inode对象的指针,用于维护文件元数据。f_op
:指向文件操作函数表的指针,定义了文件支持的操作。
struct path
struct path
结构体包含了文件路径的信息,由struct vfsmount
和struct dentry
组成。
- struct vfsmount:虚拟文件系统挂载点的表示。
- struct dentry:目录项结构体,代表了文件系统中的一个目录项。
struct inode
struct inode
结构体记录了文件的元数据,如文件类型、访问权限等。
- 关键字段:
i_mode
:文件类型和权限。i_uid
:文件的用户ID。i_gid
:文件的组ID。i_ino
:inode编号,文件系统中文件的唯一标识。i_size
:文件大小。
1.6.4 文件描述符表关联的数据结构
struct files_struct
用于维护一个进程中所有打开文件信息。
- 关键字段:
fdt
:指向当前使用的文件描述符表(fdtable)。next_fd
:存储下一个可用的最小文件描述符编号。fd_array
:struct file指针的数组,用于快速访问。
struct fdtable
打开文件描述符表底层的数据结构。
- 关键字段:
max_fds
:文件描述符数组的容量。fd
:指向struct file指针数组的指针。close_on_exec
、open_fds
、full_fds_bits
:管理文件描述符的状态。
1.6.5 文件描述符和fd或fd_array的关系
文件描述符的值实际上是其关联的struct file
在fd
指向的数组或fd_array
中的下标。
1.7 总结与实际应用
1.7.1 总结
通过本文档的学习,我们深入了解了C语言中文件操作的基本概念和系统调用的工作原理。我们学习了如何使用标准I/O库函数进行文件的打开、关闭、读写操作,以及如何使用系统调用来直接操作文件描述符。此外,我们还探讨了文件描述符背后的数据结构,包括struct file
、struct path
、struct inode
以及文件描述符表struct files_struct
和struct fdtable
。
1.7.2 实际应用示例
示例1:使用系统调用来读取文件内容
以下是一个使用系统调用来读取文件内容并打印到标准输出的示例代码:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd == -1) {
perror("Failed to open file");
return EXIT_FAILURE;
}
char buffer[1024];
ssize_t bytesRead;
while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
write(STDOUT_FILENO, buffer, bytesRead);
}
if (bytesRead == -1) {
perror("Failed to read file");
close(fd);
return EXIT_FAILURE;
}
close(fd);
return EXIT_SUCCESS;
}
在这个示例中,我们使用open
系统调打开文件,read
系统调读取文件内容,并使用write
系统调将内容写入标准输出。最后,我们使用close
系统调关闭文件描述符。
示例2:使用标准I/O库函数处理文件
以下是一个使用标准I/O库函数来复制文件内容的示例代码:
#include <stdio.h>
#include <stdlib.h>
int main() {
FILE *srcFile = fopen("source.txt", "r");
FILE *destFile = fopen("destination.txt", "w");
if (srcFile == NULL || destFile == NULL) {
perror("Failed to open files");
return EXIT_FAILURE;
}
char buffer[1024];
size_t bytesRead;
while ((bytesRead = fread(buffer, 1, sizeof(buffer), srcFile)) > 0) {
fwrite(buffer, 1, bytesRead, destFile);
}
if (ferror(srcFile) || ferror(destFile)) {
perror("Error reading or writing files");
fclose(srcFile);
fclose(destFile);
return EXIT_FAILURE;
}
fclose(srcFile);
fclose(destFile);
return EXIT_SUCCESS;
}
在这个示例中,我们使用fopen
函数打开源文件和目标文件,fread
函数从源文件读取数据,并使用fwrite
函数将数据写入目标文件。最后,我们使用fclose
函数关闭文件。
1.7.3 进一步学习
对于想要进一步深入学习文件操作和系统调用的读者,建议阅读以下资源:
- 《UNIX环境高级编程》(Advanced Programming in the UNIX Environment):这本书详细介绍了UNIX系统下的文件操作和系统调用。
- Linux内核源码:直接查看Linux内核源码,了解文件描述符和相关数据结构的实现细节。