1.文件的基础知识
⚀文件
▶︎概念◀︎
文件是指存储在外部存储器上的数据集合。
常见的有:磁盘、U盘等等。
▶︎作用◀︎
保存数据
▶︎文件名◀︎
文件是指文件的标识符号,每个文件都有一个文件名。
文件名主要由三部分组成:文件路径+文件名字+文件后缀。
▶︎格式◀︎
文件的格式就是文件的后缀,只表示文件的打开方式,并不代表所存数据的类型。
比如:.docx是文档文件、.jpg是图片等等。
⚁文件的分类
在C语言中文件被分为两大类:程序文件和数据文件。
▶︎程序文件◀︎
程序文件就是用来存放文件代码的文件,根据程序的不同时期分为以下三类:
- 源程序文件(.c):代码哥编写的C语言程序文件,里面由敲的代码。
- 目标文件(.obj):源程序文件经过编译器编译后生成的文件。
- 可执行程序(.exe):目标文件进行链接后生成的文件,可以直接在操作系统上运行。
▶︎数据文件◀︎
数据文件就是给人们直接观看的、用户需要使用的,
这里对于C语言而言,就是程序运行时用于存储和检索数据的文件。
数据文件又分为文本文件和二进制文件:
- 文本文件:数据是以字符进行存储的文件,不需要进行转换,都是我们能读懂的字符,这种文件通常就是给我们看的。
- 二进制文件:数据是以二进制形式进行储存的文件,需要将原来的字符转换成对应的ASCII码值,再转换成对应的二进制数字进行储存,这种文件一般打开了是个正常都看不懂的。
⚁流
▶︎概念◀︎
我的理解
· 在写程序时,通常我们需要在键盘、鼠标和显示器上读取/输入数据,由于每种设备的操作方式都不同,那就意味着我们需要掌握每一种外部设备的读取数据/写入数据的方式,这就搞得很复杂,那么怎么办呢?
这个解决方法就是“流”,“流”是一个抽象出来的概念【记住这句话就行,不是重点】,“流”可以将不同的设备的操作方式进行简化统一。
具体实现可以理解为:将所有的外部设备都放入“流”中,我们只需要从“流”中就可以拿取/写入你想要的数据,至于“流”是如何和这些外部设备打交道的我们无需关心,要操作时只需要对流进行操作即可。
而对“流”进行操作,通常都需要先打开“流”,再进行操作,最后关闭“流”。
▶︎分类◀︎
“流”是一种拿数据写数据的传输方式,它可以看作是一条通道,需要进行数据传输的地方称为流的起源或者终点,“流”的起源/终点往大的讲可以是从外部设备,也可以是文件,也可以是一块内存空间,反正流就是用来帮助数据传输,下面介绍以下三种特殊流:
- stdio:标准输入流,该条流是指从键盘输入数据到程序中去,键盘是流的起源,而程序则是流流向的终点;scanf函数则是偷偷的使用了这个流,进行数据读取。
- stdout:标准输出流,该条流是指将程序中的数据打印到屏幕上,可以认为程序是流的起源,而屏幕则是流流向的终点;scanf函数则是偷偷的使用了这个流,进行数据打印。
- stderr:标准错误输出流,该条流则是将程序的错误信息打印到屏幕上,可以认为程序的错误信息是流的起源,而屏幕则是流流向的终点。
【补充】
流(Stream)作为一个抽象概念,可以被视为一个连续的字节序列。
在计算机科学中,流通常指的是一种数据结构,它能够表示数据的流动。这里的“连续”意味着数据是顺序排列的,形成一个序列,这个序列在逻辑上是连续的,尽管在物理层面上可能分散存储在不同的位置。
3.文件操作步骤
⚀大致操作
对于文件的操作,实际也是进行文件中的数据操作,前面我们讲过要进行对应的操作,只需找到对应的流即可,那就是文件流,而对于文件流进行就满足打开流,读写流和关闭流,这三大步骤,就是打开文件,读写文件和关闭文件这三大步骤。【知道文件的大致操作即可,前面的不懂没关系】。
而对于文件的打开、读写和关闭,C语言官方又为我们提供一系列的函数,再介绍这些函数之前现为大家介绍一下文件指针。
⚁文件指针
文件指针是一个指针,指向的是对应的文件
在文件被使用时,就会将对应的文件信息存放在一个结构体中,这个特殊的结构体被称为文件信息,它的指针被称为文件指针,该指针的关键字被系统命名为FILE*。
⚂打开操作
fopen函数
🟡函数作用
Open file.
fopen函数是通过文件以某种方式打开文件,文件的文件指针。
🟢函数定义
FILE * fopen ( const char * filename, const char * mode );
- const char * filename 文件名,接收文件名的起始地址,文件名可以包含文件路径。
- const char * mode 文件的打开方式,函数会提供一些方式,接收的是对应方式名字的地址。
- FILE* 返回类型为该文件的文件指针,失败返回NULL。
函数提供的打开方式如下表:
名称 | 作用 | 文件不存在时 |
'r' | read,只能向文件读取数据,只读 | 打开失败,报错 |
'w' | 创建新的文件,如有已存在的同名文件,同名文件的数据被丢弃,新的文件替换原来的文件。 只能向文件写入数据,只写。 | 创建新文件,成功打开 |
'a' | append,只能向文件的末尾附加数据 | 打开失败,报错 |
'r+' | 向文件读取数据或写入数据,读写 | 打开失败,报错 |
'w+' | 创建新的文件,如有已存在的同名文件,同名文件的数据被丢弃,新的文件替换原来的文件。 向文件读取数据或写入数据,读写 | 创建新文件,成功打开 |
'a+' | 向文件的末尾附加数据或者读取数据 | 打开失败,报错 |
上面是以文本文件操作的形式进行打开,类似的,可以在上面的基础上再追加字符b,那就表示通过二进制的方式打开,可以在后面追加字符b(rb,r+b等等),也可以在字符r/w/a和'+'之间插入字符b(rb+,wb+等等),具体如下:
名称 | 作用,binary——二进制 | 文件不存在时 |
"rb" | read,二进制形式打开,只能向文件读取数据,只读 | 打开失败,报错 |
"wb" | 创建新的文件,如有已存在的同名文件,同名文件的数据被丢弃,新的文件替换原来的文件。 write,二进制形式打开,只能向文件写入数据,只写 | 创建新文件,成功打开 |
"ab" | append,二进制形式打开,只能向文件的末尾附加数据 | 打开失败,报错 |
"rb+"("r+b") | 二进制形式打开,向文件读取数据或写入数据,读写 | 打开失败,报错 |
"wb+" ("w+b") | 创建新的文件,如有已存在的同名文件,同名文件的数据被丢弃,新的文件替换原来的文件。 二进制形式打开,向文件读取数据或写入数据,读写 | 创建新文件,成功打开 |
"ab+" ("a+b") | 二进制形式打开,向文件的末尾附加数据或者读取数据 | 打开失败,报错 |
🟣函数使用
//假设存在test.txt的文本文件,里面存有"abcdef" FILE* p1 = fopen("test.txt","r"); FILE* p2 = fopen("test.txt","w"); FILE* p3 = fopen("test.txt","a");
- p1,p2,p3都是test.txt文件的文件指针,但是打开方式不同,创建的文件信息不同,文件指针不同。
- p1是只读的方式打开,文件存在不报错。
- p2是以只写的方式打开,每次打开时,会创建一个新的文件,如果存在同名文件,会将原文替换掉,访问光标是指着新文件的数据的起始位置。
- p3是以追加的方式打开,每次打开时,文件的访问光标是指着数据的末尾后的位置。
🟤注意事项
- 函数参数的文件名,可以是单独的文件名字,也可是包含文件路径的文件名
- 文件以"w"/"w+"/"wb"/"wb+"方式打开时,会创建对应的新文件,如果原来含有同名文件,则新文件将同名文件替换掉,那么原文件的数据就会发生丢失。
- 当以不同方式打开文件时,意味着文件的属性不同,自然文件信息不同,文件指针就不同。
- 文件打开可能失败,在打开后的下一步应该先判断指针为否为NULL,再进行读写操作。
【补充1】
打开方式新的C标准(C2011,不是c++的一部分)添加了一个新的标准子说明符("x"),可以附加到任何"w"说明符(形成"wx", "wbx", "w+x"或"w+bx"/"wb+x")。如果文件存在,此子指定符强制函数失败,而不是覆盖它。
【补充2】
文件路径
文件路径主要分为两种:绝对路径和相对路径
- 绝对路径:绝对路径是文件在硬盘上存储的真实路径,它包括了从根目录开始到文件所在位置的所有目录名称。
- 相对路径:相对路径则是基于当前工作目录(程序)或目标文件所在位置的路径,它会随着当前工作目录(程序)的改变而改变;相对路径的表示可以使用
./
来表示当前目录,或者使用../
来表示上一级目录。- 路径分隔符:主要分为两类' \ '和' / ',在不同的操作系统中,这两者可能有不同的含义,在Windows系统中,通常使用
\
作为路径分隔符,而在Unix或Linux系统中,则使用/
。
⚃关闭操作
fclose函数
🟡函数作用
Close file.
fclose函数是用来关闭文件的,通过文件指针对指定文件进行关闭。
🟢函数定义
int fclose ( FILE * stream );
- FILE * stream 文件指针,要关闭的文件的指针。
- int 返回类型为int,如果关闭成功返回0,失败则返回EOF。
🟣函数使用
//接着上面的p1,p2,p3,进行关闭 fclose(p1); fclose(p2); fclose(p3);
- fclose函数很简单,传要关闭的文件的指针进行关闭就可。
🟤注意事项
- 打开文件在使用完一定要又文件的关闭。
- 文件关闭后记得,将文件指针置为NULL,规避野指针。
【补充1】
- 关闭与流关联的文件并解除关联。
- 所有与流关联的内部缓冲区都将与流解除关联并刷新:任何未写入的输出缓冲区的内容将被写入,而任何未读的输入缓冲区的内容将被丢弃。
- 即使调用失败,作为参数传递的流也不再与文件及其缓冲区相关联。
函数 | 作用 |
fopen | 以某种方式打开文件,并返回文件指针 |
fclose | 关闭文件指针所指向的文件 |
4.文件的读写操作
C语言官方主要提供了以下几个文件读写操作的函数:
函数 | 作用 | 使用范围 |
fgetc | 从指定流中读取一个字符 | 所有流 |
fputc | 向指定流中写入一个字符 | 所有流 |
fgets | 从指定流中读取字符串 | 所有流 |
fputs | 向指定流中写入字符串 | 所有流 |
函数 | 作用 | 使用范围 |
getchar | 从标准输入流中读取一个字符 | 标准输入流 |
putchar | 向标准输出流中写入一个字符 | 标准输出流 |
gets | 从标准输入流中读取字符串 | 标准输入流 |
puts | 向标准输出流中写入字符串 | 标准输出流 |
从上面的两张表格可与看出,fgets/fputs和gets/puts十分相似,只不过前者在前面加了f,这个f就是form的意思,指定了操作的对象,就是指定流输入输出函数,而对于没有f的,则是默认标准输入输出流函数;而对于fgetc/fputc和getchar/putchar,这里对应的却没有类似,事实上getc/putc和fgetc/fputc的作用相同,只不过内部实现的方法不同。
下面为大家重点介绍以下指定流的输入输出函数,以文件流为例:
⚀fgetc和fputc
🟡函数作用
- fgetc:从指定流中读取(输出)字符。Get character from stream.
- fputc:向指定流中写入(输入)字符。Write character to stream.
🟢函数定义
int fgetc ( FILE * stream );
- FILE * stream 指定流的文件指针,对文件而言就是文件的文件指针
- int 返回类型为int,读取成功返回读取字符的ASCII码值,失败返回EOF
int fputc ( int character, FILE * stream );
- int character 传入字符,接收时会转为写入字符的ASCII码值
- FILE * stream 指定流的文件指针,对文件而言就是文件的文件指针
- int 返回类型为int,写入成功写入读取字符的ASCII码值,失败返回EOF
🟣函数使用
fgetc函数使用
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { //1.打开文件,只读的方式 FILE* p = fopen("test1.txt", "r"); //判断是否打开失败 if (p == NULL) { perror("fopen"); } //进行读取操作,使用fgetc函数 int c = 0;//存放函数返回值 while (c != EOF)//多次读取,读取失败时返回EOF { c = fgetc(p);//每次读取一个字符后,文件的位置指示符将移动到下一个字符处 printf("%d ", c); } //3.关闭文件 fclose(p); //指针置为NULL,规避野指针 p = NULL; }
fgetc函数使用
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { //1.打开文件,只写的方式 FILE* p = fopen("test2.txt", "w"); //判断是否打开失败 if (p == NULL) { perror("fopen"); } //进行写入操作,使用fputc函数 for (char c = 'a';c<='f';c++)//写入6个字符 { int ret = fputc(c,p);//每次读取一个字符后,文件的位置指示符将移动到下一个字符处 printf("%d ", ret);//打印函数返回值 } //3.关闭文件 fclose(p); //指针置为NULL,规避野指针 p = NULL; }
🟤注意事项
- 这两个函数的返回值都是int类型,都是返回int类型,为啥不直接返回字符呢?是为了兼容EOF的值,EOF是设置为-1,是int类型,所以返回类型就都设置为int。
- fgetc函数读取一个字符后文件的指示光标移动到下一个字符位置下面,fputc也是写入一个字符后移动到下一个字符位置下面。
- fgetc读取成功返回读取到的字符的ASCII码值,fputc写入成功返回写入字符的ASCII码值
- 对于fgetc读取失败,一是打开文件后没有数据,那就已经到达文件末尾,文件文件光标指向的就是EOF,二是文件读取到了数据的末尾,此时文件光标指向的就是EOF,EOF的意思就是End Of File,意思是文件末尾的标志。
⚁fgets和fputs
🟡函数作用
- fgets:从指定流中读取(输出)字符串(文本)。Get string from stream.
- fputs:向指定流中写入(输入)字符串(文本)。Write string to stream.
函数作用与fgetc和fputc相似,只不过fgets和fputs是字符串,c就是character,字符的意思,s就是string,字符串的意思。
🟢函数定义
char * fgets ( char * str, int num, FILE * stream );
- char * str str指向读取到的字符串存入的空间
- int num 要读取的字符的个数,只读取num-1个有效字符;遇到EOF或换行符结束
- FILE * stream 指定流的文件指针,对文件而言就是文件的文件指针
- char * 返回类型为char*,如果成功返回str的值,读取失败则返回NULL
int fputs ( const char * str, FILE * stream );
- const char * str str指向要写入字符串所在空间
- FILE * stream 指定流的文件指针,对文件而言就是文件的文件指针
- int 返回类型为int,写入成功返回一个非负数,失败返回EOF
🟣函数使用
fgets函数使用
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> void test1() { //1.打开文件 FILE* p = fopen("test.txt", "r"); //判断打开是否失败 if (p == NULL) { perror("fopen"); } //接受的文本的数组 char arr[30]; //2.fgets函数读取数据 char* c = fgets(arr, 5, p); //打印 printf("%s %p\n", arr, c); //3.关闭文件 fclose(p); //指针置为空 p = NULL; c = NULL; } void test2() { //1.打开文件 FILE* p = fopen("test1.txt", "r"); //判断打开是否失败 if (p == NULL) { perror("fopen"); } //接受的文本的数组 char arr[30]; //2.fgets函数读取数据 char* c = fgets(arr, 5, p); //打印 printf("%s %p\n", arr, c); //3.关闭文件 fclose(p); //指针置为空 p = NULL; c = NULL; } void test3() { //1.打开文件 FILE* p = fopen("test2.txt", "r"); //判断打开是否失败 if (p == NULL) { perror("fopen"); } //接受的文本的数组 char arr[30]; //2.fgets函数读取数据 char* c = fgets(arr, 5, p); //打印 printf("%s %p\n", arr, c); //3.关闭文件 fclose(p); //指针置为空 p = NULL; c = NULL; } int main() { //都是从文件中读取5个字符 test1();//文件中字符长度>5 test2();//文件中字符长度<5,会读取到文件终止符EOF test3();//文件中字符长度>5,但是有换行符 return 0; }
fputs函数使用
#include<stdio.h> int main() { FILE* p = fopen("test3.txt", "w"); if (p == NULL) { perror("fopen"); } int ret = fputs("abcdef", p); printf("%d\n", ret); fclose(p); p = NULL; return 0; }
🟤注意事项
- fgets函数读取过程中,只读取num-1个有效字符,剩余的一个位置被设置为NUL('\0');如果读取过程中遇到文件结尾(EOF)或者换行符,那么读取就结束,同时该字符也会被读取进去。
- fgets如果读取成功,返回的是str的值;如果读取失败,返回的值为NULL。
- fputs如果读取成功,返回的是一个非零数;如果读取失败,返回的值为EOF(-1)。
- fgets和fputs函数在使用完,文件的指示光标都会移动到使用后的对应位置。
⚂scanf家族和printf家族
🟡函数作用
函数 | 作用 | 使用范围 |
scanf | 从标准输入流中读取数据 | 标准输入流 |
sscanf | 从字符流中读取数据 | 字符流 |
fscanf | 从指定流中读取数据 | 所有流 |
函数 | 作用 | 使用范围 |
printf | 将数据写入到标准输出流中 | 标准输出流 |
sprintf | 将数据写入到字符流中 | 字符流 |
fprintf | 将数据写入到指定流中 | 所有流 |
🟢函数定义
scanf家族
int scanf ( const char * format, ... );
int sscanf ( const char * s, const char * format, ...);
int fscanf ( FILE * stream, const char * format, ... );
- const char * format 格式说明符,与后面的变量类型相对应,用于占位和表明格式
- ... 输入的参数,类型与前面的格式说明符有一套对应的法则,对于scanf来说这里是取的变量的地址,因为函数只有传址调用才能对指定变量的值进行改变
- 对于scanf是从标准流中输入到指定变量,则不需要传流,已经隐藏实现了
- 对于sscanf,是从指定的存放字符变量的空间,将字符输入到指定变量
- 对于fscanf,是指定的流,使用与之对应的文件指针
printf家族
int printf ( const char * format, ... );
int sprintf ( char * str, const char * format, ... );
int fprintf ( FILE * stream, const char * format, ... );
- const char * format 格式说明符,与后面的变量类型相对应,用于占位和表明格式
- ... 输入的参数,类型与前面的格式说明符有一套对应的法则
- 对于printf是输出到标准流中,则不需要传流,已经隐藏实现了
- 对于sprintf,是将字符输出到对应的空间的地址
- 对于fprintf,是指定的流,使用与之对应的文件指针
🟣函数使用
scanf家族
//} #include<stdio.h> int main() { int a; char arr1[10]="abcedf"; char arr2[10]; int b; scanf("%d", &a);//将在输入流中读取的数据存放在变量a中 sscanf(arr1, "%s",&arr2);//将在arr中读取的数据存放在变量arr2中 fscanf(stdin, "%d", &b);//将在stdin(标准输入流)中读取的数据存放在变量b中 return 0; }
printf家族
#include<stdio.h> int main() { int a = 10; int b = 2; char arr1[10]; char arr2[10] = "abcdef"; printf("%d\n", a);//将变量a的值输出到标准输出流上(屏幕) sprintf(arr1, "%s", arr2);//将字符数组arr2的值输出到字符数组arr1中 fprintf(stdout, "%d\n", b);//将变量b的值输出到stdout流上(标准输出流) return 0; }
🟤注意事项
- sscanf函数读取的是缓冲区的数据内容,sprintf函数是将数据写入/存放/输出到缓冲区,而其他的函数则是读写流的数据。
- 对于scanf家族函数的返回值,如果写入成功,则返回写入成功的项数,如果失败则返回EOF(-1)。
- 对于printf家族函数的返回值,如果输出成功,则返回输出字符的个数,如果失败则返回负数
本章内容结束,下章见,拜拜!!!