C_文件

C_文件
BarbecueC_文件I/O
流
在 C 语言中,流 (stream) 表示任意输入的源或任意输出的目的地。流是一个抽象的概念,即可以表示存储硬盘上的文件,也可以表示网络端口或者打印设备。
Linux哲学:一切皆文件
文件缓冲
操作系统会在内存上为流设置缓冲区。
缓冲区是以先进先出的方式管理数据的。缓冲区分为三种类型:
满缓冲:当缓冲区空时,从输入流中读取数据;当缓冲区满时,向输出流中写入数据。
行缓冲:每次从输入流中读取一行数据;每次向输出流中写入一行数据 (stdin,stdout)。
无缓冲:顾名思义,就是没有缓冲区 (stderr)。
文件指针
在C语言中,文件指针是用来处理文件读写操作的一种特殊类型的指针。在使用文件指针之前,需要先定义一个指向 FILE 类型的指针变量,用于表示文件指针。 FILE 类型定义在 stdio.h 头文件中。
1 | typedef struct |
C语言中对文件的各种操作都是基于文件指针来实现的,每操作一个文件都应该让其对应一个文件指针
标准流
C 语言对流的访问是通过文件指针实现的,类型为 FILE* 。并且在 <stdio.h> 头文件中提供了 3 个标准流。这 3 个标准流可以直接使用
| 文件指针 | 流 | 默认函数 |
|---|---|---|
| stdin | 标准输入 | scanf |
| stdout | 标准输出 | printf |
| stderr | 标准错误 | fprintf |
在进行文件操作的时候,定义文件指针的方式如下:
1 | FILE *filePointer; |
文件文本和二进制文本
C 语言支持两种类型的文件:文本文件和二进制文件。文本文件中存储的是字符数据,人类是可以看懂的;二进制文件中的数据,人类是看不懂的。
文本文件具有两个独特的性质:
- 文本文件有行的概念:文本文件被划分为若干行,并且每一行的结尾都以特殊字符进行标记。在 Windows 系统中,是以回车符和换行符 (\r\n) 进行标记的;在 Unix和 Mac 系统中是以换行符 (\n) 标记的。
- 文本文件可能包含一个特殊的 “文件末尾“ 标记:操作系统允许在文本文件的末尾使用一个特殊的字节作为标记。
文本形式可以方便人类阅读和编辑;二进制形式可以节省空间,并且转换效率高。
打开/关闭文件
fopen 函数
fopen函数的原型如下:
1 | FILE *fopen(const char *filename, const char *mode); |
参数:
filename:指定要打开的文件名,需要加上路径(相对、绝对路径)mode:指定文件的打开模式
返回值:
- 成功:返回指向打开文件的文件指针
- 失败:返回 NULL
关于fopen函数第二个参数mode对应的文件打开模式如下表( b 是二进制模式的意思):
| 打开模式 | 含义 |
|---|---|
r或rb |
以只读方式打开一个文本文件(不创建文件,若文件不存在则报错) |
w或wb |
以写方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) |
a或ab |
以追加方式打开文件,在末尾添加内容,若文件不存在则创建文件 |
r+或rb+ |
以可读、可写的方式打开文件(不创建新文件) |
w+或wb+ |
以可读、可写的方式打开文件(如果文件存在则清空文件,文件不存在则创建一个文件) |
a+或ab+ |
以追加方式打开文件,打开文件并在末尾更改文件,若文件不存在则创建文件 |
写模式和追加模式是不一样的。如果文件存在,写模式会清空原有的数据,而追加模式会在原有数据的后面写入新的内容。
fclose 函数
fclose 可以关闭程序不再使用的文件,函数原型如下::
- 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存
- 一个进程同时打开的文件数是有限制的,超过最大同时打开文件数,再次调用
fopen打开文件会失败
1 | int fclose(FILE * stream); |
参数:
- 接受一个文件指针
stream,用于指定要关闭的文件。将缓冲区中的数据写回到文件中,并释放与文件相关的资源。
返回值:返回一个整数值来指示关闭操作的成功与否。
- 成功:关闭文件,返回0
- 失败:返回非零值
读/写文件
fgetc/fputc , fgets/fputs 和 fscanf/fprintf 是用来读写文本文件,fread/fwrite 是用来读写二进制文件。
输入与输出的标准函数
读文件
fscanf 用于从文件中读取格式化数据,类似于从键盘输入的 scanf 函数。从指定的文件指针读取数据,并根据提供的格式字符串将其存储在变量中。函数原型如下:
1 | int fscanf(FILE *stream, const char *format, ...); |
参数说明:
FILE *stream: 文件指针,指向要读取的文件。const char *format: 格式化字符串,定义如何读取数据(例如%d、%f、%s等)。...: 可变参数,存储读取数据的变量地址。
返回值:
- 成功时,返回成功读取的项数。
- 如果到达文件末尾,返回
EOF。
不同的是, scanf 是从标准输入(stdin)中读取数据,而 fscanf 可以从任何一个流中读取数据。当 fscanf 的第一个参数为 stdin 时,效果等价于 scanf ( sscanf 可以从字符串中读取数据)。
fscanf的读取受文件内容和格式字符串的匹配影响,格式不匹配会导致读取失败。- 遇到空格、换行或制表符时,
fscanf会自动忽略。
1 |
|
写文件
fprintf 和 printf 类似,是用来进行格式化输出的,
1 | int fprintf(FILE *stream, const char *format, ...); |
参数说明:
FILE *stream: 文件指针,指向要写入的文件。const char *format: 格式化字符串,定义如何输出数据。...: 可变参数,指定要写入的数据。
返回值:
- 成功时,返回写入字符的总数。
- 失败时,返回负值。
不同的是, printf 始终是向标准输出( stdout )写入内容的,而 fprintf 可以向任何一个输出流中写入内容。当 fprintf 的第一个参数为 stdout 时,效果等价于 printf ( sprintf 可以将内容写入到一个字符数组中)。
1 |
|
格式化输入输出,可以用于序列化和反序列化过程中。所谓序列化,就是将程序中的对象转换成一种可以保存的格式(二进制或文本),从而方便存储(存储到文件或数据库中)或传输(通过网络传输给另一台机器)。反序列化则是序列化的逆过程,将按一定格式存储的数据转换成程序中的对象。
1 | typedef struct{ |
按照字符读写文件
读文件
fgetc是一个C标准库函数,用于从文件中读取一个字符。函数原型如下:
1 |
|
参数:
stream:文件指针,对应要写入字符的文件
返回值:
- 成功:返回写入的字符
- 失败/文件末尾:返回
EOF(end of file)
fgetc 和 getchar 和 getc 的区别:
int fgetc(FILE *stream): 从指定的文件流stream中读取下一个字符,并将其作为unsigned char类型返回。如果到达文件末尾或发生读取错误,则返回EOF。int getchar(): 从标准输入流中读取一个字符,并将其作为unsigned char类型返回。与fgetc(stdin)是等价的。int getc(FILE *stream): 从指定的文件流stream中读取下一个字符,并将其作为unsigned char类型返回。与fgetc几乎完全相同,只是可以被实现为宏,并且通常比fgetc更高效。
三个函数的返回值都是 int 类型,以便能够返回读取的字符或 EOF (End of File)。
1 |
|
写文件
fputc是一个C标准库函数,用于将一个字符写入到文件中。函数原型如下:
1 |
|
参数:
character:要写入的字符,注意参数是整形stream:文件指针,对应要写入字符的文件
返回值:
- 成功:返回写入的字符
- 失败:返回
EOF(end of file)
fputc 和 putchar 类似。不同的是 putchar 只能向标准输出流(stdout)中写入字符,而 fputc 可以向任意一个流中写入字符。
在标准C库中,putc函数实际上是一个宏,而不是一个真正的函数。功能是相同的。
1 |
|
fcopy.c
1 |
|
按照行读写文件
读文件
fgets是一个C标准库函数,用于从文件中读取一行字符。函数原型如下:
1 |
|
参数:
string:字符指针,用于存储读取的字符size:指定要读取的最大字符数(包括终止符)stream:文件指针,用于指定要从中读取字符的文件
返回值:
- 成功:返回参数
string的首地址 - 失败:返回NULL
从输入流 stream 中,最多读取 size-1 个字符,并把读取的字符存入 string 指向的字符数组中。 fgets 遇到换行符 \n,或者文件的末尾就会终止(也就是说,读取的字符数可能不足 size-1 个),并且会存储换行符 \n 。 fgets 会在最后添加空字符\0。
fgets 是 gets 的通用版本,可以从任意输入流中读取数据,而 gets 只能从stdin 中读取数据。 fgets 也比 gets 更为安全,因为它限制了读取字符的最大数目(siez-1)。此外,如果 fgets 是因为读取了换行符而终止,那么它会存储换行符 \n ,而 gets 函数从来不会存储换行符。
1 |
|
写文件
fputs是一个C标准库函数,用于将字符串写入文件。函数原型如下:
1 |
|
参数:
string:字符串的指针,表示要写入的内容stream:文件指针,用于指定要写入字符的文件
返回值:
- 成功:返回一个非负值
- 失败:返回
EOF(end of file)
fputs 是 puts 的通用版本,可以将字符串写入到任意的输出流中,而 puts 只能写入到 stdout 中。此外, fputs 是原样输出字符串,而 puts 会在字符串后面而外输出一个换行符 \n 。在每个字符串之间添加换行符或其他分隔符,需要在调用fputs之后手动添加。
1 |
|
fcopy.c
1 | int main(int argc, char** argv) { |
按照块读写文件
fread 和 fwrite 主要是用来处理二进制文件的。还可以用于序列化和反序列化过程中。
读文件
fread是一个C标准库函数,用于从文件中以二进制形式读取数据。函数原型如下:
1 |
|
参数:
ptr:指向用于存储读取数据的缓冲区的指针size:每个元素的字节数(以字节为单位)count:要读取的元素数量stream:文件指针,用于指定要从中读取数据的文件
返回值:
- 成功:返回实际读取的元素数量
- 失败/文件末尾:返回的元素数量与
count不相等,可以通过feof和ferror函数来判断,到底是读到了文件末尾,还是发生了错误。
fread函数会从文件中读取指定数量的元素,每个元素占据size个字节,将它们存储在ptr指向的缓冲区中。
写文件
fwrite是一个C标准库函数,用于以二进制形式将数据写入文件。函数原型如下:
1 |
|
参数:
ptr:指向要写入数据的指针size:要写入的每个元素的字节数(以字节为单位)count:要写入的元素数量stream:文件指针,用于指定要写入数据的文件
返回值:
- 成功:返回写入的元素数量,即
count的值 - 失败:返回一个小于
count的值
fwrite函数会将指针ptr指向的数据写入到文件中。写入的总字节数是size与count相乘的积。
1 | //序列化/反序列化 |
文件定位
每个流都有相关联的文件位置。在执行读写操作时,文件位置会自动推进,并按照顺序访问文件。<stdio.h> 提供了几个函数来支持:
1 | int fseek(FILE* stream, long int offset, int whence); |
fseek 函数
fseek 函数用于将文件指针移动到文件中的指定位置。函数原型如下:
1 | int fseek(FILE *stream, long offset, int whence); |
参数说明:
stream:文件指针,指向已打开的文件。offset:偏移量,表示相对于 whence 的位置的偏移量(可以是正值、负值或零)。whence:定位起始点,取值如下:SEEK_SET:文件的开头SEEK_CUR:当前文件指针位置SEEK_END:文件的末尾
返回值:
• 成功时返回 0,失败时返回非零值。
示例:
1 |
|
ftell 函数
ftell 函数用于获取当前文件指针的位置。函数原型如下:
1 | long ftell(FILE *stream); |
参数说明:
stream:文件指针,指向已打开的文件。
返回值:
- 返回文件指针的当前偏移量(相对于文件开头),失败时返回 -1L。
示例:
1 |
|
rewind 函数
rewind 函数用于将文件指针移动到文件的开头。函数原型如下:
1 | void rewind(FILE *stream); |
参数说明:
stream:文件指针,指向已打开的文件。
返回值:
- 无返回值。
示例:
1 |
|
总结
fseek:用于将文件指针移动到指定位置。ftell:用于获取当前文件指针的位置。rewind:将文件指针移动到文件开头。
错误处理
C 语言往往是通过函数的返回值,或者是测试 errno 变量来检测错误的;并且需要程序员自己编写代码来处理错误。
errno的简介
errno是C语言标准库中定义的全局变量,用于指示最近发生的错误。该变量由头文件<errno.h>声明,并在出现错误时由系统函数进行设置。errno的初始值为零,每当系统调用或库函数出现错误时,它会被设置为相应的错误代码。
常用的errno错误码
errno变量通常与各种错误码配合使用,错误码预定义在<errno.h>中。以下是常见的错误码:
| 错误码 | 含义 |
|---|---|
EPERM |
操作不允许 |
ENOENT |
文件或目录不存在 |
ESRCH |
无效的进程 |
EINTR |
被中断的系统调用 |
EIO |
输入/输出错误 |
ENOMEM |
内存不足 |
EACCES |
权限被拒绝 |
使用errno的步骤
步骤1:包含必要的头文件
1 |
步骤2:清除 errno
errno 是一个全局变量,默认不会被自动清零。建议在调用可能发生错误的函数之前,手动将 errno 设置为 0,以便准确检测函数调用是否发生错误:
1 | errno = 0; |
步骤3:在函数调用后检测错误
函数调用或系统调用后,可以通过检查errno是否被设置为非零值来判断是否发生了错误。
1 | FILE *file = fopen("nonexistent.txt", "r"); |
如果fopen函数失败,errno会被设置为相应的错误码,并且strerror(errno)将返回描述错误的字符串。
错误处理函数
perror()函数
perror()函数可以用来打印带有错误信息的描述,用于根据当前 errno 的值打印错误消息。perror 会打印一条包含自定义前缀和错误消息的完整错误信息:
示例代码:
1 | FILE *file = fopen("nonexistent.txt", "r"); |
输出结果: perror 自动将 errno 的值转换为错误消息,并将自定义前缀(如 “File opening failed”)一起打印。
1 | File opening failed: No such file or directory |
strerror()函数
strerror()函数根据错误码返回一个描述错误的字符串。示例如下:
1 | FILE *file = fopen("nonexistent.txt", "r"); |
注意事项
- 在每次调用可能出错的函数前,建议将
errno设置为0,以便区分新错误与之前的错误。 - 并非所有函数都会设置
errno,因此仅在函数说明中明确表示会设置errno的函数中使用它。 - 在处理多个函数调用时,及时检查并记录 errno,以免其值被后续的函数调用覆盖。
- 使用 perror 和 strerror 提供更友好的错误信息。
示例:处理文件操作错误
1 |
|
总结
errno是一个全局变量,用于指示最近一次错误发生的类型。- 使用
perror()或strerror()可以将errno变量值转换为可读的错误信息。 - 始终检查函数返回值,并在必要时处理错误。



