目录
- 文件的基本概念
- 文本文件和二进制文件的差异
- 文件指针
- FILE 结构体
- 文件指针的初始化和赋值
- 文件打开与关闭
- 常见操作
- 文件的打开
- 文件的关闭
- 常见问题
- 打开文件时的路径问题
- 打开文件失败的常见原因
- fclose 函数的重要性
- 文件读写操作
- 常见操作
- 字符读写
- 字符串读写
- 格式化读写
- 二进制读写
- 常见问题
- 字符读写的性能考虑
- 字符串读写的边界问题
- 格式化读写的精度和错误处理
- 二进制读写的字节序问题
- 文件指针的定位
- 常见操作
- fseek函数
- ftell函数
- rewind函数
- 常见应用场景
- fseek 函数的偏移量计算
- ftell 函数的应用场景
- 文件错误处理
- 常见操作
- ferror函数
- feof函数
- clearerr 函数
- 自定义错误处理函数
文件的基本概念
文本文件和二进制文件的差异
- 存储方式
- 文本文件:以字符编码形式存储,例如 ASCII 码。每个字符(如字母、数字、标点符号)都有对应的编码值,存储时按编码值的二进制形式写入文件。例如,字符 ‘A’ 的 ASCII 码是 65,在文件中存储为二进制的 01000001。
- 二进制文件:直接以数据在内存中的二进制表示形式存储。例如,一个整数 10 在内存中以 32 位二进制 00000000 00000000 00000000 00001010 存储,在二进制文件中也是这样的形式。
- 可读性
- 文本文件:可以用文本编辑器直接查看和编辑,因为其内容是人类可读的字符。
- 二进制文件:用文本编辑器打开会显示乱码,因为它存储的是二进制数据,需要特定的程序来解析。
- 应用场景
- 文本文件:常用于存储配置信息、日志文件、代码文件等,方便人类查看和修改。
- 二进制文件:常用于存储图像、音频、视频等多媒体数据,以及程序的中间数据、数据库文件等,能更高效地存储和处理大量数据。
文件指针
FILE 结构体
FILE 结构体是一个复杂的数据结构,包含了文件操作所需的各种信息,不同的编译器实现可能会有所不同,但通常包含以下几个重要成员:
- 文件状态信息:记录文件的打开模式、是否出错、是否到达文件末尾等状态。
- 文件缓冲区指针:指向用于文件读写的缓冲区,以提高文件操作的效率。
- 文件当前位置指针:指示当前文件读写的位置。
文件指针的初始化和赋值
在使用文件指针之前,需要将其初始化为 NULL,以避免成为野指针。例如:
FILE *fp = NULL;
当使用 fopen 函数打开文件后,将返回的文件指针赋值给 fp,此时 fp 就指向了打开的文件。
文件打开与关闭
常见操作
文件的打开
使用fopen
函数来打开一个文件,其原型如下:
FILE *fopen(const char *filename, const char *mode);
filename
:要打开的文件的名称,可以包含文件的路径。mode
:打开文件的模式,常见的模式有:"r"
:以只读模式打开文本文件,文件必须存在。"w"
:以写入模式打开文本文件,如果文件不存在则创建,如果文件存在则清空文件内容。"a"
:以追加模式打开文本文件,如果文件不存在则创建,写入的数据将追加到文件末尾。"rb"
:以只读模式打开二进制文件,文件必须存在。"wb"
:以写入模式打开二进制文件,如果文件不存在则创建,如果文件存在则清空文件内容。"ab"
:以追加模式打开二进制文件,如果文件不存在则创建,写入的数据将追加到文件末尾。"r+"
:以读写模式打开文本文件,文件必须存在。"w+"
:以读写模式打开文本文件,如果文件不存在则创建,如果文件存在则清空文件内容。"a+"
:以读写模式打开文本文件,如果文件不存在则创建,写入的数据将追加到文件末尾,在读取数据前需要将文件指针移动到文件开头。"rb+"
、"wb+"
、"ab+"
:分别对应二进制文件的读写模式。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以写入模式打开文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打开失败");return 1;}// 文件操作...fclose(fp);return 0;
}
文件的关闭
使用fclose
函数来关闭一个已打开的文件,其原型如下:
int fclose(FILE *stream);
stream:要关闭的文件指针。
返回值:如果文件关闭成功,返回 0;否则返回 EOF(-1)。
常见问题
打开文件时的路径问题
- 绝对路径:完整地指定了文件在文件系统中的位置,从根目录开始。
- 在 Windows 系统中,“C:\Users\Username\Documents\test.txt”;
- 在 Linux 系统中,“/home/username/Documents/test.txt”。
- 相对路径:相对于当前工作目录的路径。例如,当前工作目录是 /home/username,要打开 Documents 目录下的 test.txt 文件,可以使用相对路径 “Documents/test.txt”。
打开文件失败的常见原因
- 文件不存在:使用 “r”、“r+”、“rb”、“rb+” 等模式打开文件时,如果文件不存在,fopen 会返回 NULL。
- 权限不足:没有足够的权限访问文件或目录,例如在 Linux 系统中,尝试以写入模式打开一个只读文件。
- 文件被占用:文件正在被其他程序使用,无法同时打开。
fclose 函数的重要性
关闭文件不仅可以释放系统资源,还可以确保文件缓冲区中的数据被正确写入磁盘。如果不关闭文件,可能会导致数据丢失或文件损坏。例如,在程序结束时,如果没有调用 fclose 关闭文件,缓冲区中的数据可能不会被写入文件。
文件读写操作
常见操作
字符读写
fgetc
函数:从文件中读取一个字符,其原型如下:
int fgetc(FILE *stream);
返回值:如果读取成功,返回读取的字符;如果到达文件末尾或发生错误,返回 EOF。
fputc
函数:向文件中写入一个字符,其原型如下:
int fputc(int c, FILE *stream);
c:要写入的字符。
返回值:如果写入成功,返回写入的字符;如果发生错误,返回 EOF。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以写入模式打开文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打开失败");return 1;}// 写入字符fputc('H', fp);fputc('e', fp);fputc('l', fp);fputc('l', fp);fputc('o', fp);fclose(fp);// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}// 读取字符int ch;while ((ch = fgetc(fp)) != EOF) {putchar(ch);}fclose(fp);return 0;
}
字符串读写
fgets
函数:从文件中读取一行字符串,其原型如下:
char *fgets(char *str, int n, FILE *stream);
str:用于存储读取的字符串的字符数组。
n:最多读取的字符数(包括字符串结束符 ‘\0’)。
返回值:如果读取成功,返回 str;如果到达文件末尾或发生错误,返回 NULL。
fputs
函数:向文件中写入一个字符串,其原型如下:
int fputs(const char *str, FILE *stream);
str:要写入的字符串。
返回值:如果写入成功,返回非负数;如果发生错误,返回 EOF。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以写入模式打开文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打开失败");return 1;}// 写入字符串fputs("Hello, World!\n", fp);fclose(fp);// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}// 读取字符串char buffer[100];while (fgets(buffer, sizeof(buffer), fp) != NULL) {printf("%s", buffer);}fclose(fp);return 0;
}
格式化读写
fscanf
函数:从文件中按照指定的格式读取数据,其原型如下:
int fscanf(FILE *stream, const char *format, ...);
stream:要读取的文件指针。
format:格式化字符串,指定读取数据的格式。
返回值:成功匹配并赋值的输入项的数量,如果到达文件末尾或发生错误,返回 EOF。
fprintf
函数:向文件中按照指定的格式写入数据,其原型如下:
int fprintf(FILE *stream, const char *format, ...);
stream:要写入的文件指针。
format:格式化字符串,指定写入数据的格式。
返回值:成功写入的字符数,如果发生错误,返回负数。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以写入模式打开文件fp = fopen("test.txt", "w");if (fp == NULL) {perror("文件打开失败");return 1;}// 写入格式化数据int num = 10;float f = 3.14;fprintf(fp, "整数: %d, 浮点数: %f\n", num, f);fclose(fp);// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}// 读取格式化数据int read_num;float read_f;fscanf(fp, "整数: %d, 浮点数: %f", &read_num, &read_f);printf("读取的整数: %d, 读取的浮点数: %f\n", read_num, read_f);fclose(fp);return 0;
}
二进制读写
fread
函数:从文件中读取二进制数据,其原型如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:用于存储读取数据的内存地址。
size:每个数据项的大小(字节数)。
nmemb:要读取的数据项的数量。
返回值:实际读取的数据项的数量。
fwrite
函数:向文件中写入二进制数据,其原型如下:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
ptr:要写入的数据的内存地址。
size:每个数据项的大小(字节数)。
nmemb:要写入的数据项的数量。
返回值:实际写入的数据项的数量。
- 示例代码:
#include <stdio.h>typedef struct {int id;char name[20];
} Student;int main() {FILE *fp;// 以二进制写入模式打开文件fp = fopen("students.dat", "wb");if (fp == NULL) {perror("文件打开失败");return 1;}// 写入二进制数据Student s = {1, "John"};fwrite(&s, sizeof(Student), 1, fp);fclose(fp);// 以二进制只读模式打开文件fp = fopen("students.dat", "rb");if (fp == NULL) {perror("文件打开失败");return 1;}// 读取二进制数据Student read_s;fread(&read_s, sizeof(Student), 1, fp);printf("ID: %d, Name: %s\n", read_s.id, read_s.name);fclose(fp);return 0;
}
常见问题
字符读写的性能考虑
- 缓冲区机制:fgetc 和 fputc 函数在读写字符时,会使用文件缓冲区。当调用 fputc 写入字符时,字符先被写入缓冲区,当缓冲区满或调用 fclose 时,缓冲区中的数据才会被写入磁盘。同样,调用 fgetc 读取字符时,会先从缓冲区中读取,如果缓冲区为空,则从磁盘读取一批数据到缓冲区。
- 频繁读写的性能问题:由于每次调用 fgetc 或 fputc 都可能涉及到缓冲区的操作,频繁的字符读写会导致性能下降。在需要大量读写字符时,可以考虑使用 fread 和 fwrite 函数进行批量读写。
字符串读写的边界问题
fgets
函数的截断问题:fgets 函数最多读取 n - 1 个字符,然后在末尾添加字符串结束符 ‘\0’。如果文件中的一行字符数超过 n - 1,则会截断该行,剩余的字符会在下一次调用 fgets 时读取。fputs
函数的换行问题:fputs 函数不会自动添加换行符,需要手动添加。例如:
fputs("Hello\n", fp);
格式化读写的精度和错误处理
- 格式化精度:在使用 fscanf 和 fprintf 进行格式化读写时,需要注意格式化字符串的精度。例如,使用 %.2f 可以控制浮点数的输出精度为两位小数。
- 错误处理:fscanf 函数在读取数据时,如果输入数据的格式与格式化字符串不匹配,可能会导致读取错误。可以通过检查 fscanf 的返回值来判断是否读取成功。
二进制读写的字节序问题
- 字节序:在不同的计算机系统中,整数等多字节数据的存储方式可能不同,分为大端字节序(Big Endian)和小端字节序(Little Endian)。大端字节序将高位字节存储在低地址,小端字节序将低位字节存储在低地址。
- 跨平台问题:在进行二进制文件读写时,如果涉及到跨平台操作,需要注意字节序的问题。可以通过字节序转换函数(如 htons、htonl、ntohs、ntohl)来确保数据的正确读写。
文件指针的定位
在文件操作过程中,有时需要移动文件指针的位置,以便在文件的不同位置进行读写操作。
常见操作
fseek函数
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 *fp;// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}// 将文件指针移动到文件开头偏移3个字节的位置fseek(fp, 3, SEEK_SET);int ch = fgetc(fp);printf("读取的字符: %c\n", ch);fclose(fp);return 0;
}
ftell函数
ftell
函数用于获取当前文件指针的位置,其原型如下:
long ftell(FILE *stream);
stream:要操作的文件指针。
返回值:当前文件指针相对于文件开头的偏移量(字节数),如果发生错误,返回 - 1。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}// 获取当前文件指针的位置long pos = ftell(fp);printf("当前文件指针位置: %ld\n", pos);fclose(fp);return 0;
}
rewind函数
rewind
函数用于将文件指针移动到文件开头,其原型如下:
void rewind(FILE *stream);
stream:要操作的文件指针。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}// 移动文件指针到文件末尾fseek(fp, 0, SEEK_END);// 将文件指针移动到文件开头rewind(fp);int ch = fgetc(fp);printf("读取的字符: %c\n", ch);fclose(fp);return 0;
}
常见应用场景
fseek 函数的偏移量计算
- 正数和负数偏移:fseek 函数的偏移量 offset 可以是正数或负数。正数表示向文件末尾方向偏移,负数表示向文件开头方向偏移。例如,fseek(fp, -10, SEEK_CUR) 表示将文件指针从当前位置向文件开头方向移动 10 个字节。
- 偏移量的范围:偏移量的范围受到文件大小和系统限制的影响。在某些系统中,偏移量可能不能超过文件的最大长度。
ftell 函数的应用场景
文件大小计算:可以通过将文件指针移动到文件末尾,然后使用 ftell 函数获取文件指针的位置,从而得到文件的大小。例如:
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}fseek(fp, 0, SEEK_END);long size = ftell(fp);printf("文件大小: %ld 字节\n", size);fclose(fp);return 0;
}
文件错误处理
常见操作
ferror函数
ferror
函数用于检查文件操作是否发生错误,其原型如下:
int ferror(FILE *stream);
stream:要检查的文件指针。
返回值:如果发生错误,返回非零值;否则返回 0。
feof函数
feof
函数用于检查文件指针是否到达文件末尾,其原型如下:
int feof(FILE *stream);
stream:要检查的文件指针。
返回值:如果到达文件末尾,返回非零值;否则返回 0。
- 示例代码:
#include <stdio.h>int main() {FILE *fp;// 以只读模式打开文件fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}int ch;while ((ch = fgetc(fp)) != EOF) {putchar(ch);}if (ferror(fp)) {printf("文件读取发生错误\n");} else if (feof(fp)) {printf("到达文件末尾\n");}fclose(fp);return 0;
}
clearerr 函数
clearerr
函数用于清除文件的错误标志和文件结束标志,其原型如下:
void clearerr(FILE *stream);
stream:要操作的文件指针。
在发生错误后,可以使用 clearerr 函数清除错误标志,以便继续进行文件操作。例如:
#include <stdio.h>int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}int ch;while ((ch = fgetc(fp)) != EOF) {if (ferror(fp)) {printf("文件读取发生错误\n");clearerr(fp); // 清除错误标志}putchar(ch);}fclose(fp);return 0;
}
自定义错误处理函数
可以编写自定义的错误处理函数,以便在文件操作发生错误时进行统一处理。例如:
#include <stdio.h>void handle_file_error(FILE *fp, const char *message) {if (ferror(fp)) {perror(message);clearerr(fp);}
}int main() {FILE *fp = fopen("test.txt", "r");if (fp == NULL) {perror("文件打开失败");return 1;}int ch;while ((ch = fgetc(fp)) != EOF) {handle_file_error(fp, "文件读取错误");putchar(ch);}fclose(fp);return 0;
}