C语言文件操作
0. 目录
1. 文件的作用
文件的意义:大家试想一下,在没有文件之前,我们所编写的C语言程序都是不具有保存功能的,这是因为程序运行时才会加载到内存,但是内存不具有记忆功能,换句话说一经断电关机内存中存储的数据就会全部丢失,这个时候我们想要保存数据,就要使用如硬盘、磁盘等存储设备。
在举一个编程的例子,我们已经实现了一个学生成绩管理系统,当我们已经录入学生信息完毕后,我们终止程序运行,但是当下一次重新运行程序的时候,之前录入的数据就会全部丢失!所以我们想要使用保存数据的功能,使用文件就是一种保存数据的解决方案,也就是说文件就是用来实现数据持久化的。
2. 什么是文件
2.1 程序文件与数据文件
在程序设计语言中所谈论的文件主要分为以下两大类:程序文件和数据文件
- 程序文件:例如说C语言所编写的源代码文件在经过编译链接之后会生成后缀名为
.obj
的目标文件和后缀名为.exe
的可执行文件 - 数据文件:是指程序运行时所需的文件,例如在程序运行中想要从文件中读出相应数据,或者将一些数据写入某个文件中
2.2 文件名
文件名:一个文件用以区别于别的文件的唯一标识,也称作文件名
文件名的组成:通常由三部分构成——文件路径
、文件名主干
、文件后缀
例如:c:\code\test.txt
注意:文件名可以不包括文件后缀,例如单纯一个c:\code\test
的文本文件
3. 文件的打开与关闭
3. 1 文件指针
文件信息区:在C语言中,每个所正在使用的文件都会在内存中开辟相应的文件信息区,用来存放关于文件的信息(如文件名、文件大小、文件状态)这些信息被保存在一个结构体变量当中,该结构体类型由系统进行声明与定义、取名为FILE
例如在VS2013编译环境中stdio.h
的头文件中就有以下FILE
类型的定义
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* pf; // 文件指针变量
通过pf指针变量,我们就可以让pf指针指向一个文件信息区,通过这个pf指针进一步找到与之相关联的文件
3.2 文件的打开与关闭
3.2.1 文件打开与关闭函数介绍
C语言提供了一个打开文件的函数:fopen
网站链接:https://legacy.cplusplus.com/reference/cstdio/fopen/?kw=fopen
语法格式:FILE* fopen(const char* filename, const char* mode)
函数作用:通过mode指定的方式,打开一个文件名为filename的文件,并返回一个与之相关联的文件类型指针
下面是文件常见的一些打开方式(mode):
文件打开方式 | 含义 | 如果文件不存在 |
---|---|---|
“r”(只读) | 为了输入数据,打开一个已经存在的文本文件 | 报错 |
“w”(只写) | 为了输出数据,打开一个文本文件 | 创建一个文件 |
“a”(追加) | 向文本文件末尾追加文本数据 | 创建一个文件 |
“rb”(只读) | 为了输入数据,打开一个已经存在的二进制文件 | 报错 |
“wb”(只写) | 为了输出数据,打开一个二进制文件 | 创建一个文件 |
“ab”(追加) | 向一个二进制文件末尾追加数据 | 创建一个文件 |
“r+”(读写) | 为了读写数据,打开一个已经存在的文本文件 | 报错 |
“w+”(读写) | 为了读写数据,打开一个文本文件 | 创建一个文件 |
“a+”(读写) | 打开一个文本文件,在文件尾读写数据 | 创建一个文件 |
“rb+”(读写) | 为了读写数据,打开一个已经存在的二进制文件 | 报错 |
“wb+”(读写) | 为了读写数据,打开一个二进制文件 | 创建一个文件 |
“ab+”(读写) | 打开一个二进制文件,在末尾读写数据 | 创建一个文件 |
C语言还提供了一个关闭文件的函数:fclose
网站链接:https://legacy.cplusplus.com/reference/cstdio/fclose/?kw=fclose
语法格式:int fclose(FILE* stream)
函数作用:关闭文件流对象stream
3.2.2 文件打开与关闭演示
int main() {
// 打开文件
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return -1;
}
// 文件操作
// 关闭文件
fclose(pf);
return 0;
}
4. 文件的顺序读写
4.1 fgetc与fputc函数
4.1.1 函数使用介绍
C语言提供了一个字符输出函数:fputc
网站链接:https://legacy.cplusplus.com/reference/cstdio/fputc/?kw=fputc
语法格式:int fputc(int character, FILE* stream)
函数作用:将字符character写入文件流对象stream中,如果成功,返回写入字符,如果写入失败,返回EOF
C语言也提供了对应的字符读入函数:fgetc
网站链接:https://legacy.cplusplus.com/reference/cstdio/fgetc/?kw=fgetc
语法格式:int fgetc(FILE* stream)
函数作用:返回文件流对象中当前所指向的字符,如果成功读取返回相应字符,读取失败或读取完毕返回EOF
4.1.2 函数使用演示
写入文件:
// 写入文件
int main() {
FILE* pf = fopen("test.txt", "w");
if (pf == NULL) {
perror("fopen");
return -1;
}
// 使用文件
fputc('a', pf);
fputc('b', pf);
fputc('c', pf);
// 关闭文件
fclose(pf);
return 0;
}
读取文件:
// 读取文件
int main() {
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return -1;
}
// 使用文件
char ch;
while ((ch = fgetc(pf)) != EOF) {
printf("%c ", ch);
}
// 关闭文件
fclose(pf);
return 0;
}
4.2 fgets与fputs函数
4.2.1 函数使用介绍
C语言不仅有针对单个字符进行读写的函数,还提供了一个文本行输出函数:fputs
网站链接:https://legacy.cplusplus.com/reference/cstdio/fputs/?kw=fputs
语法格式:int fputs(const char* str, FILE* stream)
函数作用:将str所指向的字符串写入文件流对象stream中,如果成功返回非负值,若失败返回EOF
同样C语言也提供了与之配套的文本行读取函数,可读取多个字符:fgets
网站链接:https://legacy.cplusplus.com/reference/cstdio/fgets/?kw=fgets
语法格式:char* fgets(char* str, int num, FILE* stream)
函数作用:读取文件流对象当中的多个字符,但是最多读取num-1
个字符,如果期间读取到文件结束标志符或者一行文本读取完毕则结束读取,将读取结果写入str指向的内存空间中,并追加\0
,如果正常读取则返回str,如果读取过程中发生了错误或者没有一个字符可以读取则返回空指针NULL
4.2.2 函数使用演示
写入文件
// fputs使用
int main() {
// 打开文件
FILE* pf = fopen("fputs.txt", "w");
if (pf == NULL) {
perror("fopen");
return -1;
}
int ret = fputs("hello world!", pf);
printf("%d\n", ret);
// 关闭文件
fclose(pf);
return 0;
}
读取文件
// fgets使用
int main() {
// 打开文件
FILE* pf = fopen("fputs.txt", "r");
if (pf == NULL) {
perror("fopen");
return -1;
}
char str[20] = { 0 };
char* ret = fgets(str, 19, pf);
printf("%s\n", ret);
// 关闭文件
fclose(pf);
return 0;
}
4.3 fscanf与fprintf函数
4.3.1 函数使用介绍
C语言提供了一个格式化输出函数:fprintf
网站链接:https://legacy.cplusplus.com/reference/cstdio/fprintf/?kw=fprintf
语法格式:int fprintf(FILE* stream, const char* format, ...)
函数作用:将format指向的格式化数据写入文件流对象stream中,如果写入成功返回写入字符,反之返回负数
C语言也提供了与之相配套的格式化读取函数:fscanf
网站链接:https://legacy.cplusplus.com/reference/cstdio/fscanf/?kw=fscanf
语法格式:int fscanf(FILE* stream, const char* stream, ...)
函数作用:将文件流对象stream中读取格式化数据存储到format指向对象中
4.3.2 函数使用演示
写入文件
typedef struct S {
char name[10];
int age;
}S;
// fprintf使用
int main() {
// 打开文件
FILE* pf = fopen("fprintf.txt", "w");
if (pf == NULL) {
perror("fopen");
return -1;
}
S s = {"rice", 20};
// 使用文件
int ret = fprintf(pf, "%s %d", s.name, s.age);
printf("%d\n", ret);
// 关闭文件
fclose(pf);
return 0;
}
读取文件
typedef struct S {
char name[10];
int age;
}S;
// fscanf使用
int main() {
// 打开文件
FILE* pf = fopen("fprintf.txt", "r");
if (pf == NULL) {
perror("fopen");
return -1;
}
S s = { 0 };
int ret = fscanf(pf, "%s %d", s.name, &s.age);
printf("%d\n", ret);
printf("%s %d\n", s.name, s.age);
// 关闭文件
fclose(pf);
return 0;
}
4.4 fread与fwrite函数
注:上述的函数都是适用于所有输入流和输出流的,即也是可以从控制台读取和输出到控制台上的,但是这组函数只适用于文件流对象,并且下面要介绍的这一对函数是针对二进制文件的。
4.4.1 函数使用介绍
C语言提供了一个二进制输出函数:fwrite
网站链接:https://legacy.cplusplus.com/reference/cstdio/fwrite/?kw=fwrite
语法格式:size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream)
函数作用:会将ptr指向空间内容count * size
个字节转化为二进制写入文件流对象stream中
同样的C语言也提供了与之相配套的二进制读取函数:fread
网站链接:https://legacy.cplusplus.com/reference/cstdio/fread/?kw=fread
语法格式:size_t fread(void* ptr, size_t size, size_t count, FILE* stream)
函数作用:会将文件流对象中读取count* size
个字节对应的二进制信息读取存入ptr指向的空间中
4.4.2 函数使用演示
写入文件
typedef struct S {
char name[10];
int age;
}S;
// fwrite使用
int main() {
// 打开文件
FILE* pf = fopen("fwrite.txt", "wb");
if (pf == NULL) {
perror("fopen");
return -1;
}
S s = { "rice", 20 };
int ret = fwrite(&s, sizeof(S), 1, pf);
printf("%d\n", ret);
// 关闭文件
fclose(pf);
return 0;
}
读取文件
typedef struct S {
char name[10];
int age;
}S;
// fread使用
int main() {
// 打开文件
FILE* pf = fopen("fwrite.txt", "rb");
if (pf == NULL) {
perror("fopen");
return -1;
}
S s = { 0 };
int ret = fread(&s, sizeof(S), 1, pf);
printf("%d\n", ret);
printf("%s %d\n", s.name, s.age);
// 关闭文件
fclose(pf);
return 0;
}
5. 文件的随机读写
5.1 fseek函数
网站链接:https://legacy.cplusplus.com/reference/cstdio/fseek/?kw=fseek
语法格式:int fseek(FILE* stream, long int offset, int origin)
函数作用:可以根据origin
与offset
参数设置重新变更文件指针的位置
origin | 含义 |
---|---|
SEEK_SET | 文件起始位置 |
SEEK_CUR | 文件指针当前位置 |
SEEK_END | 文件末尾位置 |
下面是fseek函数使用演示
int main() {
// 打开文件
FILE* pfWrite = fopen("fseek.txt", "w");
if (pfWrite == NULL) {
perror("fopen pfWrite");
return -1;
}
fputs("hello world!", pfWrite);
// 关闭文件
fclose(pfWrite);
// 打开文件
FILE* pfRead = fopen("fseek.txt", "r");
if (pfRead == NULL) {
perror("fopen pfRead");
return -1;
}
// 文件指针起始位置偏移一位
fseek(pfRead, 1, SEEK_SET);
char ch1 = fgetc(pfRead);
printf("%c\n", ch1);
// 文件指针当前位置偏移一位
fseek(pfRead, 1, SEEK_CUR);
ch1 = fgetc(pfRead);
printf("%c\n", ch1);
// 文件指针距离末尾偏移-1位
fseek(pfRead, -1, SEEK_END);
ch1 = fgetc(pfRead);
printf("%c\n", ch1);
// 关闭文件
fclose(pfRead);
return 0;
}
5.2 ftell函数
C语言提供了一个可以计算文件指针当前位置距离文件起始位置的偏移量函数:ftell
网站链接:https://legacy.cplusplus.com/reference/cstdio/ftell/?kw=ftell
语法格式:long int ftell(FILE* stream)
函数作用:返回文件指针当前位置与文件起始位置的偏移量
下面是ftell函数的使用演示:
// ftell使用
int main() {
// 打开文件
FILE* pfWrite = fopen("ftell.txt", "w");
if (pfWrite == NULL) {
perror("pfWrite fopen");
return -1;
}
fputs("hello world!", pfWrite);
// 关闭文件
fclose(pfWrite);
// 打开文件
FILE* pfRead = fopen("ftell.txt", "r");
if (pfRead == NULL) {
perror("pfRead fopen");
return -1;
}
char ch = fgetc(pfRead);
ch = fgetc(pfRead);
ch = fgetc(pfRead);
ch = fgetc(pfRead);
// 计算当前偏移量
int offset = ftell(pfRead);
printf("当前偏移量为: %d\n", offset);
// 关闭文件
fclose(pfRead);
return 0;
}
5.3 rewind函数
C语言还提供了可以让文件指针重新回到文件起始位置函数:rewind
网站链接:https://legacy.cplusplus.com/reference/cstdio/rewind/?kw=rewind
语法格式:void rewind(FILE* stream)
函数作用:让文件指针重新回到文件起始位置
下面是rewind函数的使用演示
// rewind函数使用
int main() {
// 打开文件
FILE* pfWrite = fopen("rewind.txt", "w");
if (pfWrite == NULL) {
perror("pfWrite fopen");
return -1;
}
fputs("hello world!", pfWrite);
// 关闭文件
fclose(pfWrite);
// 打开文件
FILE* pfRead = fopen("rewind.txt", "r");
if (pfRead == NULL) {
perror("pfRead fopen");
return -1;
}
fseek(pfRead, 2, SEEK_SET);
// 计算当前偏移量
int offset = ftell(pfRead);
printf("当前偏移量为%d\n", offset);
// 使用rewind函数
rewind(pfRead);
// 重新计算偏移量
offset = ftell(pfRead);
printf("当前偏移量为%d\n", offset);
// 关闭文件
fclose(pfRead);
return 0;
}
6. 文件读取结束的判定
6.1 文件读取结束的判定
注意:文件读取过程中,不要依靠feof
函数的返回值来判断文件是否读取结束
feof
函数的作用是:当文件读取结束的时候判断结束的原因是否是因为遇到文件尾
-
文本文件判断是否读取结束,需要通过
fgetc
或者fgets
函数的返回值进行判断fgetc
函数如果返回值为EOF
,说明文件读取结束fgets
函数如果返回值为NULL
,说明文件读取结束
-
二进制文件判断是否读取结束,需要通过返回值的个数是否小于实际应该读取的个数
- 如果
fread
函数返回值小于实际需要读取的个数则说明文件读取结束
- 如果
6.2 文件读取结束判定演示
对文本文件读取结束进行判断:
// 文本文件判断文件是否读取结束
int main() {
// 打开文件
FILE* pfWrite = fopen("TextEndOfFile.txt", "w");
if (pfWrite == NULL) {
perror("pfWrite fopen");
return -1;
}
fputs("hello world!", pfWrite);
// 关闭文件
fclose(pfWrite);
// 打开文件
FILE* pfRead = fopen("TextEndOfFile.txt", "r");
if (pfRead == NULL) {
perror("pfRead fopen");
return -1;
}
char ch = 0;
while ((ch = fgetc(pfRead)) != EOF) {
putchar(ch);
}
// 当文件读取结束后,使用feof函数判断是否是读取到末尾
if (feof(pfRead)) {
printf("文件正常读取到末尾!\n");
} else {
printf("文件异常读取结束!\n");
}
// 关闭文件
fclose(pfRead);
}
对二进制文件读取结束进行判断
// 二进制文件判断是否读取结束
typedef struct S {
char name[10];
int age;
}S;
int main() {
// 打开文件
FILE* pfWrite = fopen("BinEndOfFile.txt", "wb");
if (pfWrite == NULL) {
perror("pfWrite fopen");
return -1;
}
S s1 = { "rice", 20};
S s2 = { "json", 21 };
S stus[2] = { s1, s2 }; // 创建一个结构体数组
fwrite(stus, sizeof(S), 2, pfWrite);
// 关闭文件
fclose(pfWrite);
// 打开文件
FILE* pfRead = fopen("BinEndOfFile.txt", "rb");
if (pfRead == NULL) {
perror("pfRead fopen");
return -1;
}
int sz = sizeof(stus) / sizeof(stus[0]);
if (fread(stus, sizeof(S), 2, pfRead) == sz) {
printf("文件读取完毕\n");
}
// 关闭文件
fclose(pfRead);
return 0;
}