目录
引言
一、基本概念
1.什么是文件
2.文件的属性
3.为什么使用文件
4.二进制文件和文本文件
二、文件的打开和关闭
1.流和标准流
2.文件指针
3.文件的打开和关闭
三、⽂件的顺序读写
1.顺序读写函数
2.详细介绍
1.fgetc
2.fputc
3.fgets
4.fputs
5.fscanf
6.fprintf
7.fread
8.fwrite
3.对比一组函数
四、文件的随机读写
1.相关函数
1.fseek
2.ftell
3.rewind
2.总结
五、文件的错误处理
1.相关函数
1.perror
2.feof
3. ferror
2.总结
六、文件缓冲区
总结
引言
在 C 语言中,文件操作是一个非常重要的主题。无论是保存用户数据、配置程序、还是读写日志文件,掌握文件操作都能使你的程序更加灵活和实用。本文将带你深入了解 C 语言中的文件操作,帮助你从基础到进阶,逐步掌握文件操作的技巧。
一、基本概念
1.什么是文件
文件是操作系统中存储数据的基本单位。文件可以是文本文档、二进制数据、图片、音频等各种形式。但是在程序设计中,我们⼀般谈的⽂件有两种:程序⽂件、数据⽂件(从⽂件功能的⻆度来分类的)。
程序⽂件:程序文件是包含程序代码的文件,例如 .c 文件(源代码文件),.obj文件(目标文件)和 .exe 文件(可执行文件)。它们用于编译和运行程序。
数据⽂件:数据文件用于存储程序运行时生成或处理的数据。例如,文本文件、二进制文件、日志文件等。它们可以用来存储用户输入、计算结果、程序状态等信息。
2.文件的属性
文件的主要属性包括:
文件名:文件的名称。
文件路径:文件在文件系统中的位置。
文件大小:文件的字节数。
⽂件名:⼀个⽂件要有⼀个唯⼀的⽂件标识,以便⽤⼾识别和引⽤。
⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀
例如:
c:\code\test.txt
为了⽅便起⻅,⽂件标识常被称为⽂件名。
3.为什么使用文件
文件是持久化数据的主要手段之一。使用文件可以将数据存储到硬盘上,以便程序关闭后仍能保存数据。文件操作提供了以下几个主要用途:
- 数据持久化:将运行时的数据保存到文件中,程序重新启动时可以恢复这些数据。
- 配置管理:程序配置和用户设置通常保存在文件中,便于修改和持久保存。
- 日志记录:将程序运行中的日志信息记录到文件中,方便后续分析和调试。
4.二进制文件和文本文件
文本文件:存储的是可读的字符数据,通常以 ASCII 或 UTF-8 编码。文本文件在不同平台(如 Windows 和 Unix)可能有不同的换行符表示方式(
\r\n
vs\n
)。示例:example.txt文件中包含字符数据。
二进制文件:存储的是原始的二进制数据,不进行编码转换。适用于存储图像、音频、视频和其他非文本数据。
示例:exemple.bin文件中包含整数、浮点数等原始数据。
二、文件的打开和关闭
文件操作开始于打开文件,结束于关闭文件。C 语言提供了一系列函数来管理文件的打开和关闭。
1.流和标准流
流
流是数据输入和输出的抽象概念。通过流,程序可以读取数据或将数据写入文件。C 语言的标准库提供了对流的支持,主要通过 FILE 类型和相关函数实现。
FILE *:表示文件流的指针。标准流
标准流是预定义的文件流,通常用于处理程序的输入和输出。
stdin:标准输入流,通常连接到键盘。
stdout:标准输出流,通常连接到屏幕。
stderr:标准错误流,通常连接到屏幕,用于输出错误信息。
2.文件指针
struct _iobuf {char *_ptr; // 指向当前读取/写入位置的指针int _cnt; // 缓冲区中的字节数char *_base; // 指向缓冲区起始位置的指针int _flag; // 文件状态标志int _file; // 文件描述符int _charbuf; // 用于存储单个字符的缓冲区int _bufsiz; // 缓冲区大小char *_tmpfname; // 临时文件名(如果有)// 可能还会有其他字段
};
typedef struct _iobuf FILE;
每当打开⼀个⽂件的时候,系统会根据⽂件的情况⾃动创建⼀个FILE结构的变量,并填充其中的信 息,使⽤者不必关⼼细节。 ⼀般都是通过⼀个FILE的指针来维护这个FILE结构的变量,这样使⽤起来更加⽅便。 下⾯我们可以创建⼀个FILE*的指针变量:
FILE* pf;//⽂件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个⽂件的⽂件信息区(是⼀个结构体变 量)。通过该⽂件信息区中的信息就能够访问该⽂件。也就是说,通过⽂件指针变量能够间接找到与它关联的⽂件。 ⽐如:
3.文件的打开和关闭
打开文件: 使用 fopen() 函数。filename:文件名。FILE *fopen(const char *filename, const char *mode);
mode:文件打开模式(如 "r", "w", "a")。
关闭文件: 使用 fclose() 函数。关闭文件后,文件指针会失效,文件所占用的资源会被释放。int fclose(FILE *stream);
mode表⽰⽂件的打开模式,下⾯都是⽂件的打开模式:
文件使用方式 | 含义 | 如果文件不存在 |
---|---|---|
“r”(只读) | 为了输⼊数据,打开⼀个已经存在的⽂本⽂件 | 出错 |
“w”(只写) | 为了输出数据,打开⼀个⽂本⽂件 | 建⽴⼀个新的⽂件 |
“a”(追加) | 向⽂本⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“rb”(只读) | 为了输⼊数据,打开⼀个⼆进制⽂件 | 出错 |
“wb”(只写) | 为了输出数据,打开⼀个⼆进制⽂件 | 建⽴⼀个新的⽂件 |
“ab”(追加) | 向⼀个⼆进制⽂件尾添加数据 | 建⽴⼀个新的⽂件 |
“r+”(读写) | 为了读和写,打开⼀个⽂本⽂件 | 出错 |
“w+”(读写) | 为了读和写,建议⼀个新的⽂件 | 建⽴⼀个新的⽂件 |
“a+”(读写) | 打开⼀个⽂件,在⽂件尾进⾏读写 | 建⽴⼀个新的⽂件 |
“rb+”(读写) | 为了读和写打开⼀个⼆进制⽂件 | 出错 |
“wb+”(读写) | 为了读和写,新建⼀个新的⼆进制⽂件 | 建⽴⼀个新的⽂件 |
“ab+”(读写) | 打开⼀个⼆进制⽂件,在⽂件尾进⾏读和写 | 建⽴⼀个新的⽂件 |
示例代码:
#include <stdio.h>
int main() {//打开文件FILE *file = fopen("example.txt", "w");if (file == NULL) {perror("Error opening file");return 1;}//文件操作fprintf(file, "Writing to a file.\n");//关闭文件fclose(file);return 0;
}
三、⽂件的顺序读写
1.顺序读写函数
函数名 | 功能 | 适⽤于 |
---|---|---|
fgetc | 字符输⼊函数 | 所有输⼊流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | ⽂本⾏输⼊函数 | 所有输⼊流 |
fputs | ⽂本⾏输出函数 | 所有输出流 |
fscanf | 格式化输⼊函数 | 所有输⼊流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | ⼆进制输⼊ | ⽂件 |
fwrite | ⼆进制输出 | ⽂件 |
2.详细介绍
1.fgetc
功能:从文件中读取一个字符。
用法:int fgetc(FILE *stream);
返回值:成功读取一个字符,返回字符的 ASCII 码;遇到文件结尾或错误,返回 EOF。
示例:
FILE *file = fopen("example.txt", "r");
if (file != NULL) {int c;while ((c = fgetc(file)) != EOF) {putchar(c); // 输出读取的字符}fclose(file);
}
2.fputc
功能:将一个字符写入到文件。
用法:int fputc(int c, FILE *stream);
返回值:成功写入字符,返回字符;若出现错误,返回 EOF。
示例:
FILE *file = fopen("example.txt", "w");
if (file != NULL) {fputc('A', file); // 写入字符 'A'fclose(file);
}
3.fgets
功能:从文件中读取一行文本。
用法:char *fgets(char *str, int n, FILE *stream);
参数:
str:存储读取数据的缓冲区。
n:要读取的最大字符数(包括终止符 \0)。
stream:文件流。
返回值:成功读取一行,返回 str;遇到文件结束或错误,返回 NULL。
示例:
FILE *file = fopen("example.txt", "r");
if (file != NULL) {char buffer[100];while (fgets(buffer, sizeof(buffer), file) != NULL) {printf("%s", buffer); // 输出读取的行}fclose(file);
}
4.fputs
功能:将一个字符串写入到文件。
用法:int fputs(const char *str, FILE *stream);
返回值:成功写入字符串,返回非负值;若出现错误,返回 EOF。
示例:
FILE *file = fopen("example.txt", "w");
if (file != NULL) {fputs("Hello, World!\n", file); // 写入字符串fclose(file);
}
5.fscanf
功能:从文件中读取格式化输入。
用法:int fscanf(FILE *stream, const char *format, ...);
参数:
stream:文件流。
format:格式字符串,指定输入格式。
...:用于存储读取数据的变量。
返回值:成功读取的项目数量;若出现错误或到达文件末尾,返回 EOF。
示例:
FILE *file = fopen("example.txt", "r");
if (file != NULL) {int num;fscanf(file, "%d", &num); // 读取整数printf("Number: %d\n", num);fclose(file);
}
6.fprintf
功能:将格式化数据写入到文件。
用法:int fprintf(FILE *stream, const char *format, ...);
参数:
stream:文件流。
format:格式字符串,指定输出格式。
...:要写入的数据。
返回值:成功写入的字符数;若出现错误,返回负值。
示例:
FILE *file = fopen("example.txt", "w");
if (file != NULL) {fprintf(file, "Name: %s, Age: %d\n", "Alice", 30); // 写入格式化数据fclose(file);
}
7.fread
功能:从文件中读取数据块。
用法:size_t fread(void *ptr, size_t size, size_t count, FILE *stream);
参数:
ptr:数据存储缓冲区。
size:每个元素的大小(字节)。
count:要读取的元素数量。
stream:文件流。
返回值:成功读取的元素数量。
示例:
FILE *file = fopen("example.bin", "rb");
if (file != NULL) {int buffer[10];size_t n = fread(buffer, sizeof(int), 10, file); // 读取数据块printf("Read %zu integers.\n", n);fclose(file);
}
8.fwrite
功能:将数据块写入到文件。
用法:size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
参数:
ptr:要写入的数据。
size:每个元素的大小(字节)。
count:要写入的元素数量。
stream:文件流。
返回值:成功写入的元素数量。
示例:
FILE *file = fopen("example.bin", "wb");
if (file != NULL) {int buffer[10] = {1, 2, 3, 4, 5};size_t n = fwrite(buffer, sizeof(int), 5, file); // 写入数据块printf("Wrote %zu integers.\n", n);fclose(file);
}
3.对比一组函数
输入函数
scanf:从标准输入(如键盘)读取格式化数据。
示例:int num; scanf("%d", &num);
fscanf:从指定的文件流中读取格式化数据。
示例:FILE *file = fopen("data.txt", "r"); int num; fscanf(file, "%d", &num); fclose(file);
sscanf:从字符串中读取格式化数据。
示例:const char *str = "42"; int num; sscanf(str, "%d", &num);
输出函数
printf:将格式化数据输出到标准输出(如屏幕)。
示例:printf("Hello, %s!\n", "World");
fprintf:将格式化数据输出到指定的文件流。
示例:FILE *file = fopen("output.txt", "w"); fprintf(file, "Hello, %s!\n", "World"); fclose(file);
sprintf:将格式化数据写入到字符串中。
示例:char buffer[100]; sprintf(buffer, "Hello, %s!", "World");
四、文件的随机读写
随机读写允许在文件中任意位置进行读写操作。使用fseek()、ftell() 和 rewind() 函数来实现。
1.相关函数
1.fseek
功能:设置文件指针的位置。可以通过这个函数将文件指针移动到文件的任意位置,从而进行随机访问读写操作。
用法:int fseek(FILE *stream, long offset, int whence);
参数:
stream:文件流指针,指定要操作的文件。
offset:相对位置的偏移量,以字节为单位。
whence:起始位置,用于确定偏移量的参考点。可以是以下值之一:SEEK_SET:文件开头。
SEEK_CUR:当前位置。
SEEK_END:文件末尾。
返回值:成功时返回 0;失败时返回非零值。
示例:
#include <stdio.h>int main() {FILE *file = fopen("example.bin", "wb+");if (file == NULL) {perror("Error opening file");return 1;}// 写入数据int data = 12345;fseek(file, 0, SEEK_SET); // 移动到文件开头fwrite(&data, sizeof(int), 1, file);// 随机读取数据fseek(file, 0, SEEK_SET); // 移动到文件开头fread(&data, sizeof(int), 1, file);printf("Read from file: %d\n", data);fclose(file);return 0;
}
2.ftell
功能:获取当前文件指针的位置。此函数返回文件指针相对于文件开头的字节位置。
用法:long ftell(FILE *stream);
参数:
stream:文件流指针,指定要查询位置的文件。
返回值:当前文件指针的位置(以字节为单位);失败时返回 -1L。
示例:
#include <stdio.h>int main() {FILE *file = fopen("example.txt", "r");if (file == NULL) {perror("Error opening file");return 1;}fseek(file, 0, SEEK_END); // 移动到文件末尾long filesize = ftell(file); // 获取文件大小printf("File size: %ld bytes\n", filesize);fclose(file);return 0;
}
3.rewind
功能:将文件指针重置到文件开头。rewind 函数是 fseek 函数的简化版本,专门用于将文件指针设置到文件的起始位置。
用法:void rewind(FILE *stream);
参数:
stream:文件流指针,指定要重置位置的文件。
示例:
#include <stdio.h>int main() {FILE *file = fopen("example.txt", "r");if (file == NULL) {perror("Error opening file");return 1;}fseek(file, 10, SEEK_SET); // 移动到文件的第 10 字节rewind(file); // 将文件指针重置到文件开头char buffer[100];fgets(buffer, sizeof(buffer), file); // 读取文件开头的数据printf("Data at the beginning: %s", buffer);fclose(file);return 0;
}
2.总结
fseek:用于在文件中设置文件指针的位置。可以通过 offset 和 whence 参数指定新的位置。
ftell:用于获取当前文件指针的位置,以字节为单位。它可以帮助你确定文件指针在文件中的具体位置。
rewind:用于将文件指针重置到文件开头。它是 fseek 的简化版本,专门用于返回文件开头的操作。
五、文件的错误处理
在 C 语言的文件操作中,错误处理是确保程序稳定性和正确性的关键部分。下面详细介绍了常用的错误处理函数。
1.相关函数
1.perror
功能:perror 用于输出错误信息。它将描述 errno 变量中存储的错误代码对应的错误信息,并附加一个自定义的错误消息前缀。
用法:void perror(const char *str);
参数:str:自定义的错误消息前缀,通常是描述错误来源的字符串。它会与 errno 中的错误信息一起输出。
输出:输出的错误信息包括自定义前缀和 errno 对应的系统错误描述。通常输出到标准错误流(stderr)。
示例:
#include <stdio.h>
#include <errno.h>int main() {FILE *file = fopen("nonexistentfile.txt", "r");if (file == NULL) {perror("Error opening file");// Outputs something like: Error opening file: No such file or directory}return 0;
}
解释:
在尝试打开一个不存在的文件时,fopen 返回 NULL,perror 会输出类似于 “Error opening file: No such file or directory” 的错误信息,其中“Error opening file”是自定义的前缀。
2.feof
功能:feof 用于检查文件流是否到达文件末尾。它在尝试读取文件时非常有用,以确定是否已经读取到文件的末尾。
用法:int feof(FILE *stream);
参数:
stream:要检查的文件流指针。
返回值:
如果文件流到达文件末尾,返回非零值(通常是 1)。
如果文件流尚未到达文件末尾,返回 0。
示例:
#include <stdio.h>int main() {FILE *file = fopen("example.txt", "r");if (file == NULL) {perror("Error opening file");return 1;}char buffer[100];while (fgets(buffer, sizeof(buffer), file)) {// 读取文件内容}if (feof(file)) {printf("End of file reached.\n");} else {printf("File read error or other issue.\n");}fclose(file);return 0;
}
解释:
在 fgets 读取文件的过程中,循环直到 fgets 返回 NULL。之后使用 feof 检查是否因为到达文件末尾而结束循环。
3. ferror
功能:ferror 用于检查文件流是否发生了读取或写入错误。它帮助检测文件操作过程中是否出现了错误,并提供了对错误的响应处理。
用法:int ferror(FILE *stream);
参数:
stream:要检查的文件流指针。
返回值:
如果发生了错误,返回非零值(通常是 1)。
如果没有发生错误,返回 0。
示例:
#include <stdio.h>int main() {FILE *file = fopen("example.txt", "r");if (file == NULL) {perror("Error opening file");return 1;}char buffer[100];if (fgets(buffer, sizeof(buffer), file) == NULL) {if (ferror(file)) {perror("Error reading file");fclose(file);return 1;}}fclose(file);return 0;
}
解释:
在尝试读取文件时,如果 fgets 返回 NULL,使用 ferror 检查是否发生了错误。如果 ferror 返回非零值,则调用 perror 输出错误信息。
2.总结
perror:输出 errno 变量中存储的错误信息,并附加自定义的前缀,帮助诊断错误原因。
feof:检查文件流是否到达文件末尾,用于判断读取操作是否结束。
ferror:检查文件流是否发生了读取或写入错误,用于确定文件操作是否正常。
这些函数可以帮助你更有效地处理文件操作中的各种错误情况,确保程序在面对意外情况时能够做出适当的反应。
六、文件缓冲区
#include <stdio.h>
#include <windows.h>
int main()
{FILE*pf = fopen("test.txt", "w");fputs("abcdef", pf);//先将代码放在输出缓冲区printf("睡眠10秒-已经写数据了,打开test.txt⽂件,发现⽂件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到⽂件(磁盘)//注:fflush 在⾼版本的VS上不能使⽤了printf("再睡眠10秒-此时,再次打开test.txt⽂件,⽂件有内容了\n");Sleep(10000);fclose(pf);//注:fclose在关闭⽂件的时候,也会刷新缓冲区pf = NULL;return 0;
}
由此得出结论:因为有缓冲区的存在,C语⾔在操作⽂件的时候,需要做刷新缓冲区或者在⽂件操作结束的时候关闭⽂件。如果不做,可能导致读写⽂件的问题。
总结
在 C 语言中进行文件操作涉及打开、读写、定位和关闭文件等多个方面。通过掌握文件的基本概念、顺序和随机读写、错误处理、缓冲区等内容,你可以更加高效地进行文件操作。这不仅能帮助你保存和管理数据,还能在程序中实现更多功能。希望这些详细的讲解对你有所帮助。如果你有更多问题或需要进一步讨论,欢迎随时交流!