目录
1.为什么使用文件
2.什么是文件?
2.1 程序文件
2.2 数据文件
2.3 文件名
3.二进制文件和文本文件
4.文件的打开和关闭
4.1 流和标准流
4.1.1 流
4.1.2 标准流
4.2 文件指针
4.3 文件的打开和关闭
4.3.1 fopen函数
4.3.2 fclose函数
5.文件的顺序读写
5.1 fputc函数
5.2 fgetc函数
5.3 feof和ferror函数
5.4 fputs函数
5.5 fgets函数
5.6 fprintf函数
5.7 fscanf函数
5.8 fwrite函数
5.9 fread函数
5.10 对比一组函数
5.10.1 sprintf函数
5.10.2 fscanf函数
6.文件的随机读写
6.1 fseek函数
6.2 ftell函数
6.3 rewind函数
7.文件缓冲区
7.1 fflush函数
8.更新文件
1.为什么使用文件
如果没有文件,我们写的程序的数据是存储在电脑内存中,如果程序退出,内存回收,数据就会丢失了,等再次运行程序,是看不到上次程序的结构的,如果要将数据进行持久化的保存,我们可以使用文件。
2.什么是文件?
磁盘,就是硬盘上的文件是文件。
但是我们在设计程序的时候,一般有两种文件:程序文件、数据文件。
2.1 程序文件
程序文件包括源程序文件(后缀为.c),目标文件(后缀为.obj)和可执行程序(后缀为.exe)。
2.2 数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从文中读取数据的文件,或者输出内容的文件。
2.3 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀。
文件标识常被称为文件名。
3.二进制文件和文本文件
根据数据的组织形式,数据文件被分为文本文件和二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
4.文件的打开和关闭
4.1 流和标准流
4.1.1 流
我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输入和输出操作各不相同。
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。
一般情况下,我们要想向流里写数据,或者从流中读取数据,都是需要打开流,然后操作。
4.1.2 标准流
那为什么从键盘输入数据,并没有打开流呢?
因为C语言程序启动时,默认打开了3个流:
(1)stdin---标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
(2)stdout---标准输出流,大多数环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。
(3)stderr---标准错误流,大多数环境中输出到显示器界面。
stdin、stdout、stderr三个流的类型是:FILE*,通常称为文件指针。
C语言中,就是通过FILE*的文件指针来维护流的各种操作的。
4.2 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE。
不同的C编译器的FILE类型包含的内容不完全相同,但是大同小异。
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件。
4.3 文件的打开和关闭
文件在读取之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,再打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSI C规定使用fopen函数来打开文件,fclose来关闭文件。
4.3.1 fopen函数
FILE* fopen(const char* filename, const char* mode);
功能:fopen函数是用来打开参数filename指定的文件,同时将打开的文件和一个流进行关联,后续对流的操作是通过fopen函数返回的指针来维护。具体对流(关联的文件)的操作是通过参数mode来指定的。
参数:
(1)filename:表示被打开的文件的名字,这个名字可以绝对路径,也可以是相对路径。
(2)mode:表示对打开的文件的操作方式。
返回值:
(1)若文件成功打开,该函数将返回一个指向FILE对象的指针,该指针可用于后续操作中标识对应的流。
(2)若打开失败,则返回NULL指针,所以一定要fopen的返回值做判断,来验证文件是否打开成功。
示例:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}printf("文件打开成功\n");//关闭文件return 0;
}
结果:
4.3.2 fclose函数
int fclose(FILE* stream);
功能:关闭参数stream关联的文件,并取消其关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃。
参数:
stream:指向要关闭的流的FILE对象的指针。
返回值:成功关闭stream指向的流会返回0,否则会返回EOF。
示例:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//写文件//关闭文件fclose(pf);pf = NULL;return 0;
}
5.文件的顺序读写
5.1 fputc函数
int fputc(int character, FILE* stream);
功能:将参数character指定的字符写入到stream指向的输入流中,通常用于向文件或标准输出流写入字符。在写入字符之后,还会调整指示器。字符会被写入流内部位置指示器当前指向的位置,随后该指示器自动向前移动一个位置。
参数:
character:被写入字符。
stream:是一个FILE*类型的指针,指向了输出流(通常是文件流或stdout)。
返回值:
1.成功时返回写入字符(以int形式)。
2.失败时返回EOF(通常是-1),错误指示器会被设置,可通过ferror()检查具体错误。
示例:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputc('a', pf);fputc('b', pf);fputc('c', pf);fputc('d', pf);fclose(pf);pf = NULL;return 0;
}
输出结果:
用循环的方式:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}int i = 0;for (i = 'a'; i <= 'z'; i++){fputc(i, pf);}fclose(pf);pf = NULL;return 0;
}
输出结果:
5.2 fgetc函数
int fgetc(FILE* stream);
功能:从参数stream指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符,读这个字符之后,文件指示器自动前进到下一个字符。
参数:
stream:FILE*类型的文件指针,可以是stdin,也可以是其他输入流的指针。如果是stdin就从标准输入流读取数据。如果是文件流指针,就从文件读取数据。
返回值:
1.成功时返回读取的字符(以int形式)。
2.若调用时流已处于文件末尾,函数返回EOF并设置流的文件结束指示器(feof)。
若发生读取错误,函数返回EOF并设置流的错误指示器(ferror)。
代码演示:
data.txt里保存有26个英文字母。
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int i = 0;for (i = 0; i < 10; i++){int ch = fgetc(pf);fputc(ch, stdout);}fclose(pf);pf = NULL;return 0;
}
输出结果:
5.3 feof和ferror函数
int feof(FILE* stream);
//检测stream指针指向的流是否遇到文件末尾。int ferror(FILE* stream);
//检测stream指针指向的流是否发生读/写错误。
如果在读取文件的过程中,遇到了文件末尾,文件读取就会结束。这时读取函数会在对应的流上设置一个文件结束的标识符,这个文件结束指示符可以通过feof函数检测到。如果feof函数检测到文件结束指示符已经被设置,则返回非0的值,如果没有设置则返回0。
如果在读/写文件的过程中,发生了读/写错误,文件读取就会结束。这是读/写函数会在对应的流上设置一个错误指示符,这个错误指示符可以通过ferror函数检测到。如果ferror函数检测到错误指示符已经被设置,则返回非0的值,如果没有设置则返回0。
测试feof函数:
假设data.txt文件里存放的abcdef。
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int i = 0;for (i = 0; i < 10; i++){int c = fgetc(pf);if (c == EOF){if (feof(pf)){printf("遇到文件末尾了\n");}else if (ferror(pf)){printf("读取发生错误\n");}}else{fputc(c, stdout);}}fclose(pf);pf = NULL;return 0;
}
输出结果:
测试ferror函数:
假设data.txt文件里存放的abcdef。
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}int c = fgetc(pf);if (c == EOF){if (feof(pf)){printf("遇到了文件末尾\n");}else if (ferror){printf("读文件发生错误\n");}}else{fputc(c, stdout);}fclose(pf);pf = NULL;return 0;
}
输出结果:
5.4 fputs函数
int fputs(const char* str, FILE* stream);
功能:将参数str指向的字符串写入到参数stream指定的流中(不包含结尾的\0),适用于文件流或标准输出(stdout)。
参数:
(1)str:str是指针,指向了要写入的字符串(必须以\0结尾)。
(2)stream:是一个FILE*的指针,指向了要写入字符串的流。
返回值:
(1)成功时返回EOF(-1),同时会设置流的错误指示器 ,可以使用ferror()检查错误原因。
代码演示:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputs("abc", pf);fputs("def", pf);fclose(pf);pf = NULL;return 0;
}
输出结果:
abcdef被写入文件data.txt。
5.5 fgets函数
char* fgets(char* str, int num, FILE* stream);
功能:从stream指定的输入流中读取字符串,至读取到换行符、文件末尾(EOF)或达到指定字符数(包含结尾的\0),然后将读取的字符串存储到str指向的空间中。
参数:
(1)str:是指向字符数组的指针,str指向的空间用于存储读取到的字符串。
(2)num:最大读取字符串(包含结尾的\0,实则最多读取num-1个字符)。
(3)stream:输入流的文件指针(如文件流或stdin)。
返回值:
(1)成功时返回str指针。
(2)若在尝试读取字符时遇到文件末尾,则设置文件结束指示器,并返回NULL,需通过feof()检测。
(3)若发生读取错误,则设置流错误指示器,并返回NULL,通过ferror()检测。
代码演示:
假设:data.txt中的保存的是
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}char arr[20] = "xxxxxxxxxx";fgets(arr, 5, pf);fclose(pf);pf = NULL;return 0;
}
输出结果:
使用细节:
(1)若读取到换行符(\n),会将其包含在字符串中(排除超出num-1限制),然后以\0结尾。
(2)文件末尾无换行符时,字符串以\0结尾,不包含\n。
5.6 fprintf函数
int fprintf(FILE* stream, const char* format, ...);
功能:fprintf是将格式化数据写入指定文件流的函数。它与printf类似,但可以输出到任意文件(如磁盘文件、标准输出、标准错误等),而不仅限于控制台。
参数:
(1)stream:指向FILE对象的指针,表示要写入的文件流(如stdout、文件指针等)。
(2)format:格式化字符串,包含要写入的文本和格式说明符(如%d、%s等)。
(3)...:可变参数列表,提供与格式化字符串中说明符对应的数据。
返回值:
(1)成功时,返回写入的字符总数(非负值)。
(2)失败时,先设置对应流的错误指示器,再返回负值,可以通过ferror()来检测。
代码演示:
#include<stdio.h>struct S
{char name[30];int age;float score;
};int main()
{struct S s = { "zhangsan",21,95.5f };FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fprintf(pf, "%s %d %f", s.name, s.age, s.score);fclose(pf);pf = NULL;return 0;
}
输出结果:
5.7 fscanf函数
int fscanf(FILE* stream, const char* format, ...);
功能:fscanf是从指定文件流中读取格式化数据的函数。它类似于scanf,但可以指定输入源(如文件、标准输入等),而非仅限于控制台输入。适用于从文件解析结构化数据(如整数、浮点数、字符串等)。
参数:
(1)stream:指向FILE对象的指针,表示要读取的文件流(如stdin、文件指针等)。
(2)format:格式化字符串,定义如何解析输入数据(如%s、%f、%d等)。
(3)...:可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。
返回值:
(1)成功时,函数返回成功填充到参数列表中的项数。该值可能与预期项数一致,也可能因以下原因少于预期(甚至为0):
· 格式和数据匹配失败;
· 读取发生错误;
· 到达文件末尾(EOF)。
(2)如果在成功读取任何数据之前发生:
· 发生读取错误,会在对应流上设置错误标识符,则返回EOF。
· 到达文件末尾,会在对应流上设置文件结束标识符,则返回EOF。
代码示例:
假设data.txt文件中保存了数据:
#include<stdio.h>struct S
{char name[30];int age;float score;
};int main()
{struct S s = { 0 };FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));printf("%s %d %f\n", s.name, s.age, s.score);fclose(pf);pf = NULL;return 0;
}
输出结果:
5.8 fwrite函数
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
功能:函数用于将数据块写入stream指向的文件流中,是以2进制的形式写入的。
参数:
(1)ptr:指向要写入的数据块的指针。
(2)size:要写入的每个数据项的大小(以字节为单位)。
(3)count:要写入的数据项的数量。
(4)stream:指向FILE类型结构体的指针,指定了要写入数据的文件流。
返回值:返回实际写入的数据项数量。如果发生错误,则返回值可能小于count。
使用注意事项:
(1)在使用fwrite()之前,需要确保文件已经以二进制可写方式打开。
(2)fwrite()通常用于二进制数据的写入,如果写入文本数据,请谨慎处理换行符和编码等问题。
代码演示:
#include<stdio.h>struct S
{char name[30];int age;float score;
};int main()
{struct S s = { "zhangsan",21,95.5f };FILE* pf = fopen("data.txt", "wb");if (pf == NULL){perror("fopen");return 1;}fwrite(&s, sizeof(struct S), 1, pf);fclose(pf);pf = NULL;return 0;
}
5.9 fread函数
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
功能:函数用于从stream指向的文件流中读取数据块,并将其存储到ptr指向的内存缓冲区中。
参数:
(1)ptr:指向内存区域的指针,用于存储从文件中读取的数据。
(2)size:要读取的每个数据块的大小(以字节为单位)。
(3)count:要读取的数据块的数量。
(4)stream:指向FILE类型结构体的指针,指定了要从读取数据的文件流。
返回值:返回实际读取的数据块数量。
使用注意事项:
(1)需要包含头文件stdio.h。
(2)在使用fread()之前,需要确保文件已经以二进制可读方式打开。
(3)ptr指向的内存区域必须足够大,以便存储指定数量和大小的数据块。
(4)如果fread()成功读取了指定数量的数据块,则返回值等于count;如果读取数量少于count,则可能已经到达文件结尾或者发生了错误。
(5)在二进制文件读取时,fread()是常用的函数,但对于文本文件读取,通常使用fgets()或者fscanf()函数。
代码演示:
假设有一个二进制文件“data.txt”,包含数据上面代码的数据。
#include<stdio.h>struct S
{char name[30];int age;float score;
};int main()
{struct S s = { 0 };FILE* pf = fopen("data.txt", "rb");if (pf == NULL){perror("fopen");return 1;}size_t r = fread(&s, sizeof(struct S), 1, pf);if (r != 1){if (feof(pf)){printf("遇到文件末尾了\n");}else if (ferror(pf)){printf("读取发生了错误\n");}}else{printf("%s %d %f\n", s.name, s.age, s.score);}fclose(pf);pf = NULL;return 0;
}
输出结果:
5.10 对比一组函数
scanf/printf:针对标准输入/标准输出的格式化的输入函数和输出函数。
fscanf/fprintf:针对所有输入流/所有输出流的格式化的输入函数和输出函数。
5.10.1 sprintf函数
int sprintf(char* str, const char* format, ...);
功能:将格式化的数据写入字符数据(字符串)。它类似于printf,但输出目标不是控制台或文件,而是用户指定的内存缓冲区。常用于动态生成字符串、拼接数据或转换数据格式。简而言之就是将格式化的数据转换成一个字符串。
参数:
(1)str:指向字符数组的指针,用于存储生成的字符串(需确保足够大以防止溢出)。
(2)format:格式化字符串,定义输出格式(如%d、%f、%s等)。
(3)...:可变参数列表,提供与格式字符串中说明符对应的数据。
返回值:
成功时:返回写入buffer的字符数(不包括结尾的\0)。
失败时:返回负值。
代码演示:
#include<stdio.h>struct S
{char name[30];int age;float score;
};int main()
{struct S s = { "zhangsan",21,95.5f };char buf[30] = { 0 };sprintf(buf, "%s %d %f", s.name, s.age, s.score);printf("%s\n", buf);return 0;
}
5.10.2 fscanf函数
int sscanf(const char* str, const char* format, ...);
功能:从字符串中读取格式化数据。它与scanf类似,但输入源是内存中的字符串而非控制台或文件。常用于解析字符串中的格式化数据(如提取数字、分割文本等)。
参数:
(1)str:要解析的源字符串(输入数据来源)。
(2)format:格式化字符串,定义如何解析数据(需与格式字符串中的说明符匹配)。
返回值:
成功时:返回成功解析并赋值的参数数量(非负值)。
失败或未匹配任务数据:若输入结束或解析失败,返回EOF(通常是-1)。
代码演示:
#include<stdio.h>struct S
{char name[30];int age;float score;
};int main()
{struct S s = { "zhangsan",21,95.5f };char buf[30] = { 0 };sprintf(buf, "%s %d %f", s.name, s.age, s.score);printf("%s\n", buf);struct S t = { 0 };sscanf(buf, "%s %d %f", t.name, &(t.age), &(t.score));printf("%s %f %d\n", t.name, t.score, t.age);return 0;
}
对比得出:
scanf:针对 标准输入(stdin)的格式化输入函数。
printf:针对标准输出(stdout)的格式化输出函数。
fscanf:针对所有输入流(可以是文件流,也可以是stdin)的格式化输入函数。
fprintf:针对所有输出流(可以是文件流,也可以是stdout)的格式化输出函数。
sprintf:将格式化的数据转换成字符串。
sscanf:从字符串中提取格式化数据。
6.文件的随机读写
6.1 fseek函数
根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)。
int fseek(FILE* stream, long int offset, int origin);
origin:
SEEK_SET:文件开始位置。
SEEK_CUR:文件指示器当前的位置。
SEEK_END:文件末尾。
代码示例:
读文件:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int ch = fgetc(pf);printf("%c\n", ch);//fseek(pf, 6, SEEK_SET);//ch = fgetc(pf);//printf("%c\n", ch);//g//fseek(pf, 5, SEEK_CUR);//ch = fgetc(pf);//printf("%c\n", ch);//gfseek(pf, -3, SEEK_END);ch = fgetc(pf);printf("%c\n", ch);//gfclose(pf);pf = NULL;return 0;
}
写文件:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputs("abcdefghi", pf);fseek(pf, -3, SEEK_END);fputc('x', pf);fclose(pf);pf = NULL;return 0;
}
6.2 ftell函数
返回文件指针相对于起始位置的偏移量
long int ftell(FILE* stream);
代码示例:
#include<stdio.h>
int main()
{FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}fputs("abcdefghi", pf);fseek(pf, -3, SEEK_END);fputc('x', pf);int ret = ftell(pf);printf("%d\n", ret);fclose(pf);pf = NULL;return 0;
}
6.3 rewind函数
让文件指针的位置回到文件的起始位置
void rewind(FILE* stream);
7.文件缓冲区
ANSI C标准采用“缓冲文件系统”处理数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。缓冲区的大小根据C编译系统决定的。
缓冲区包含:完全缓冲、行缓冲、无缓冲。
7.1 fflush函数
int fflush(FILE* stream);
功能:强制刷新参数stream指定流的缓冲区,确保数据写入底层设备。
对输出流:将缓冲区中未写入的数据立即写入文件。
对输入流:行为由具体实现决定,非C语言标准行为(可能清空缓冲区)。
参数为NULL时:刷新所有打开的输出流。
参数:
stream:指向文件流的指针(如stdout、文件指针等)。
返回值:成功返回0,失败返回EOF。
注意事项:
(1)仅对输出流或更新流(最后一次操作作为输出)有明确刷新行为。
(2)输入流的刷新行为不可移植(如清空输入缓冲区时非标准特性)。
(3)程序正常终止(exit)或调用fclose时会自动刷新,但程序崩溃时缓冲区数据可能丢失。
代码演示:
#include<stdio.h>
#include<windows.h>
int main()
{FILE* pf = fopen("data.txt", "w");fputs("abcdef", pf);//先将代码放到输入缓冲区printf("睡眠10秒-已经写数据了,打开data.txt文件,发现文件没有内容\n");Sleep(10000);printf("刷新缓冲区\n");fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件printf("再睡眠10秒-此时,再次打开data.txt文件,文件有内容了\n");Sleep(10000);fclose(pf);pf = NULL;return 0;
}
得出结论:
因为有缓冲区的存在,C语言在操作文件的时候,需要做刷新缓冲区或者在文件操作结束的时候关闭文件。
如果不做,可能导致读写文件的问题。
8.更新文件
在文件的打开模式中有三种方式值得注意,分别是:“r+”,“w+”,“a+”,分别是什么意思?
关键要点:
(1)在写完文件后,要继续读文件的时候,在读取之前一定要使用fflush()刷新文件缓冲区,或者使用fseek(),rewind()重新定位文件指示器的位置。
(2)在读完文件后,需要继续读写文件之前,在写文件之前,使用fseek(),rewind()重新定位文件指示器的位置。
代码演示:
#include<stdio.h>
int main()
{FILE* pf = fopen("test.txt", "w+");if (pf == NULL){perror("fopen");return 1;}fputs("abcdefghi", pf);fflush(pf);fseek(pf, 0, SEEK_SET);int ch = fgetc(pf);printf("%c\n", ch);fseek(pf, 2, SEEK_CUR);fputs("wyc", pf);fclose(pf);pf = NULL;return 0;
}
输出结果:
上面就是文件操作的内容,各位码友可以下去多试试函数来进行了解。