目录
1. 为什么使⽤⽂件?
2. 什么是⽂件?
2.1、数据文件
2.2、程序文件
3. ⼆进制⽂件和⽂本⽂件?
4. ⽂件的打开和关闭
4.1、流和标注流
4.1.1流
4.1.2标准流
4.2、文件指针
4.3 ⽂件的打开和关闭
5.⽂件的顺序读写
5.1、fgetc函数
5.2、fputc函数
5.3、fputs函数
5.4、fgets函数
5.5、fprintf函数
5.6、fscanf函数
5.7、fwrite函数
5.8、fread函数
5.9、 sprintf函数
5.10、sscanf函数
6. ⽂件的随机读写
6.1、fseek函数
6.1.1、SEEK_SET文件开头
6.1.2、SEEK_CUR文件指针的当前位置
6.1.3、SEEK_END文件结束 *
6.2、ftell函数
6.3 rewind函数
7. ⽂件读取结束的判定
7.1、字符串文本例子
7.2、二进制文本例子
8.、⽂件缓冲区
1. 为什么使⽤⽂件?
我们所写的数据是保存在内存中的,并不是直接在磁盘保存,通过文件可以保存我们在内存中所需要的内容,没用文件的话,我们就无法保存文件。也就是说我们要将数永久保存,就可以使用文件。
2. 什么是⽂件?
文件分为两种:1、数据文件。2、程序文件
2.1、数据文件
数据文件是我们所需要读取的内容,在一个程序中,我们会读取里面的数据,读取出来的内容是数据文件
2.2、程序文件
比如:可执行文件(.exe),源程序文件(.c),目标文件(.obj)
有这些后缀的都是程序文件
3. ⼆进制⽂件和⽂本⽂件?
文本文件:把内容用字符来保存到文件当中,使用ascll字符的形式进行存储的,计算机保存数据的时候要进行转译
二进制文件:内容直接用二进制形式保存到文件当中,计算机可以认识并且直接读取的,不利于人类看
例题:目前大家无需知道代码是什么意思,这只是演示把二进制文件打开方式跟我们看见的内容是什么样子的。
int main()
{int a = 1000;FILE* pf = fopen("data.txt", "wb");//打开data.txt,如果目录没用就新建一个fwrite(&a, 4, 1, pf);fclose(pf);pf = NULL;return 0;
}
4. ⽂件的打开和关闭
4.1、流和标注流
4.1.1流
程序需要使用各种的外部设备,也需要输出给外部设备,在与不同的设备进行互相传输的时候,为了方便程序员对各种设备进行调试,我们抽象出流这个概念,我们可以把流想象处流淌着字符的河
C程序针对文件,画面,键盘等的数据输入输出都是通过流来进行操作的
一般情况下,我们想要向流写入数据,或者从流中读取数据,然后都是要打开流,然后操作
4.1.2标准流
标准流是我们C语言启动时候默认打开的
stdin-标准输入流,绝大情况从键盘输入,scanf函数就是从标准输入流进行读取数据的
stdout-标准输出流,绝大情况输出至显示器上,printf函数就是讲信息输出到标准输出流上
stderr-标准错误流,绝大情况输出至显示器上
三个流的类型是FILE*,通常指文件指针,通过FILE*来维护流的各种操作
4.2、文件指针
FILE*用与维护各种流,我们需要使用哪种流,通过创建FILE*指针我们就可以实现我们所要的效果,是输出屏幕还是从各种外部设备输入,还是打开关闭文件,写入文件等等操作
在使用每个文件的时候,就会创建一个文件信息区,用于存放文件的相关内容,这些内容都会保存到一块结构体中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*指针
FILE* pf;//⽂件指针变量
pf是指向FILE类型数据的变量,可以使pf指向某个文件的文件信息区,从而让我们可以使用这个文件,也就是说FILE* 可以帮我我们找到我们需要使用的文件
4.3 ⽂件的打开和关闭
fopen是一个打开文件的函数
fclose是一个关闭文件的函数
fopen
FILE * fopen ( const char * filename, const char * mode );
fclose
int fclose ( FILE * stream );
mode的意思是我们是读还是写,怎么读,怎么写。
文件使用方式 | 含有 | 如果指定文件不存在 |
“r” (只读) | 为了输入,打开一个存在的文本文件 | 出错 |
“w” (只写) | 为了输出,打开一个文本文件 | 创建新文件 |
“a” (追加) | 向文本文件的末尾添加数据 | 创建新文件 |
“rb” (只读) | 输入文件,打开一个二进制文件 | 出错 |
“wb”(只写) | 输出文件,输出一个二进制文件 | 创建新文件 |
“ab” (追加) | 向一个二进制文件的末尾添加数据 | 创建新文件 |
“r+” (读写) | 为了读和写,打开一个文件 | 错误 |
“w+”(读写) | 为了读和写,新建一个文件 | 创建新文件 |
“a+” (读写) | 打开一个文件,在末尾进行读写 | 创建新文件 |
“rb+”(读写) | 读写一个二进制文件 | 错误 |
“wb+” (读写) | 创建一个读写的二进制文件 | 创建新文件 |
“ab+” (读写) | 打开一个二进制文件,在末尾进行读写 | 创建新文件 |
例子:
int main()
{FILE* pf = fopen("data.txt", "w"); //通过指针FILE* pf找到并打开data.txt文件if (pf==NULL) //判断pf是否指向NULL{perror("fopen");//perror可以把错误的信息打印到屏幕上return 1; //在主函数中返回1表示不正常,返回0才正常}fclose(pf);//通过指针FILE* pf找到data.txt文件并关闭pf = NULL;//已经关闭了data.txt文件,创建的pf指针不能为空,否则就变为一个野指针return 0;
}
5.⽂件的顺序读写
输入流:磁盘中的文件数据源输入到内存里
输出流:内存中的内容存入硬盘中的文件里
以下函数默认读取与写入的内存地址是跟” .c”文件同一个目录下.
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 所有输入流 |
fwrite | 二进制输出 | 所有输出流 |
5.1、fgetc函数
int fgetc ( FILE * stream );
这个函数是用于打开文件并输出第一个字符,每输出一个字符光标会往前移动1
我们先创建一个文本文件.txt
与.c文件同一个目录创建一个data.txt文件,data.txt文件中写入abcdef这几个字符。
int main()
{//打开文件FILE* pf = fopen("data.txt", "r");if (pf==NULL){perror("fopen");return 1;}//读取文件int ch = 0; //因为文件存储的是ASCII码值,需要使用int类型的变量来接收ASCII码值ch = fgetc(pf); //读取文件中一个字符,转换为ASCII码值,printf("%c\n", ch);//关闭文件fclose(pf);pf = NULL;return 0;
}
输出:
那么如何打印出整个data.txt文件中的字符。
int main()
{//打开文件FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int ch = 0;//读取文件while ((ch = fgetc(pf)) != EOF) //EOF的意思是当文件读取都末尾的空值时,会停止读取{printf("%c ", ch);}//关闭文件fclose(pf);pf = NULL;return 0;
}
输出:
5.2、fputc函数
int fputc ( int character, FILE * stream );
这个函数可以把存储在内存上的内容写入到我们指定的文件当中
int main()
{//打开文件FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//输出字符fputc('a', pf);//把字符a写入data.txt文件中//关闭文件fclose(pf);pf = NULL;return 0;
}
成功写入data.txt文件
5.3、fputs函数
int fputs ( const char * str, FILE * stream );
这个函数可以把字符串给保存到文本文件中
int main()
{//打开文件FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//输出字符fputs("hello world", pf);//把字符串写入data.txt文件中//关闭文件fclose(pf);pf = NULL;return 0;
}
5.4、fgets函数
char * fgets ( char * str, int num, FILE * stream );
这个函数是读取文本文件中字符,str表示字符串大小,num表示读取多少字符
//事先创建了一个data.txt文件,里面存入hello world字符串
int main()
{//打开文件FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//输入字符char str[12]; fgets(str, 12, pf); //把data.txt文件中的内容前12个字符存入str中printf("%s", str);//关闭文件fclose(pf);pf = NULL;return 0;
}
输出:
5.5、fprintf函数
int fprintf ( FILE * stream, const char * format, ... );
这个函数的使用方法跟printf有相同之处,这个函数输出文本文件的里面的内容。
struct S
{char name[20];int age;float score;
};
int main()
{struct S s = { "lihua",18,97.3f };//结构体s中存放的内容//打开文件FILE* pf = fopen("data.txt", "w");if (pf == NULL){perror("fopen");return 1;}//输出fprintf(pf, "%s %d %f", s.name, s.age, s.score);//把结构体中的内容存放到pf中//关闭文件fclose(pf);pf = NULL;return 0;
}
成功保存
5.6、fscanf函数
int fscanf ( FILE * stream, const char * format, ... );
这个函数可以读取文件中的内容,我们就使用上一个函数中存放在文本文件中的内容做示范
struct S
{char name[20];int age;float score;
};
int main()
{struct S s = { 0 };//结构体s中存放的内容//打开文件FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}//输入fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score)); //字符串无需取地址,因为会自动取首地址,把data.txt文件中的内容取出来放入结构体s中printf("%s %d %f", s.name, s.age, s.score);//打印存放在结构上s中的内容//关闭文件fclose(pf);pf = NULL;return 0;
}
输出:
浮点数无法做到精确取值,所有有余数是很正常的事情。
5.7、fwrite函数
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
这个函数把内容输出到文本文件中,使用二进制保存
int main()
{//打开文件FILE* pf = fopen("data.txt", "wb");//wb是二进制输出if (pf == NULL){perror("fopen");return 1;}//二进制输出int arr[5] = { 1,2,3,4,5 };fwrite(arr, sizeof(int), 5, pf);//把arr大小的文件,以int类型大小,保存5个字符/数字到pf中//关闭文件fclose(pf);pf = NULL;return 0;
}
可以看见,我们保存成功
5.8、fread函数
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
这个函数可以读取二进制文本文件,我们读取上一个函数创建的文本文件内容
int main()
{//打开文件FILE* pf = fopen("data.txt", "rb");//wb是二进制输出if (pf == NULL){perror("fopen");return 1;}//二进制输出int arr[5] ; //创建一个字符串fread(arr, sizeof(int), 5, pf);//读取pf指向的二进制文件,保存5个以int大小到arr数组中for (int i = 0; i < 5; i++){printf("%d ", arr[i]);}//关闭文件fclose(pf);pf = NULL;return 0;
}
输出:
5.9、 sprintf函数
int sprintf ( char * str, const char * format, ... );
这个函数是将数组,字符串,浮点型,变为一个字符串进行输出
struct S
{char name[20];int age;float score;
};int main()
{char str[100]; //用于存储结构体s中的内容struct S s = { "xiaoming" ,18,95.5 };sprintf(str, "%s %d %f", s.name, s.age, s.score); //把结构体s中的内容放入str中printf("%s ", str);return 0;
}
输出:
5.10、sscanf函数
int sscanf ( const char * s, const char * format, ...);
这个函数是将变量取出来,放入到另一个空变量中(类型与大小要相同)
struct S
{char name[20];int age;float score;
};int main()
{char str[100];struct S s = { "xiaoming" ,18,95.5 };struct S tmp = { 0 };sprintf(str, "%s %d %f", s.name, s.age, s.score); //把结构体s中的内容放入str中sscanf(str, "%s %d %f", tmp.name, &(tmp.age),&(tmp.score)); //把str中的内容放入tmp结构体中printf("%s %d %f", tmp.name, tmp.age, tmp.score); //打印结构体tmp中从值return 0;
}
输出:
6. ⽂件的随机读写
上面讲了顺序读写,我们这一节来讲随机读写
6.1、fseek函数
int fseek ( FILE * stream, long int offset, int origin );
这个函数是使用光标来定位的,并不是从初始位置开始
offset是我们需要移动几个光标,origin移动光标的时候想从什么地方开始移动,上图中已经写了,SEEK_SET光标在文件开头,SEEK_CUR文件指针的当前位置,SEEK_END光标在文件的末尾
6.1.1、SEEK_SET文件开头
//在data.txt中存入abcdef内容
int main()
{FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}int ch = 0;fseek(pf, 4, SEEK_SET); //从起始位置开始便便宜4个光标ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
输出:
便宜四个位置就是e
6.1.2、SEEK_CUR文件指针的当前位置
//在data.txt中存入abcdef内容
int main()
{int ch = 0;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}ch = fgetc(pf); //fgetc运行完以后,光标会自动偏移一个位置fseek(pf, 4, SEEK_CUR); //从当前位置开始偏移4个光标ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
输出:
6.1.3、SEEK_END文件结束 *
//在data.txt中存入abcdef内容
int main()
{int ch = 0;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}fseek(pf, -2, SEEK_END); //光标从结尾开始运行,移动-2个位置ch = fgetc(pf);printf("%c", ch);fclose(pf);pf = NULL;return 0;
}
输出:
6.2、ftell函数
long int ftell ( FILE * stream );
这个函数是计算光标距离起始位置有多长
//在data.txt中存入abcdef内容
int main()
{int ch = 0;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}fseek(pf, 4, SEEK_SET); //从起始位置开始偏移4个光标ch = fgetc(pf);printf("%c\n", ch);printf("%d\n", ftell(pf)); //计算当前光标到起始位置的距离 fclose(pf);pf = NULL;return 0;
}
输出:
6.3 rewind函数
这个函数可以将光标恢复到起始位置
//在data.txt中存入abcdef内容
int main()
{int ch = 0;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}fseek(pf, 4, SEEK_CUR); //从当前位置开始偏移4个光标ch = fgetc(pf);printf("%c\n", ch);printf("%d\n", ftell(pf));rewind(pf); //光标恢复到起始位置printf("%d\n", ftell(pf));fclose(pf);pf = NULL;return 0;
}
输出:
7. ⽂件读取结束的判定
1、文本文件读取
我们可以使用feof这个函数来检查文件是否被正常读取
fgetc是否为EOF
fgets是否为NULL
如果是的话,那么文件读取没用文件,否则就有问题
2、二进制文件读取
二进制读取结束判断,判断返回值是否小于实际要读的数
7.1、字符串文本例子
//在data.txt中存入abcdef内容
int main()
{int ch = 0;FILE* pf = fopen("data.txt", "r");if (pf == NULL){perror("fopen");return 1;}while ((ch = fgetc(pf)) != EOF) // 标准C I/O读取⽂件循环{putchar(ch );//putchar也是打印的意思,这个是打印变量}//判断是什么原因结束的if (ferror(pf))puts("\nI/O error when reading"); //puts是打印的意思else if (feof(pf))puts("\nEnd of file reached successfully");fclose(pf);pf = NULL;return 0;
}
7.2、二进制文本例子
enum { SIZE = 5 };
int main(void)
{double a[SIZE] = { 1.,2.,3.,4.,5. };FILE* fp = fopen("test.bin", "wb"); // 必须⽤⼆进制模式fwrite(a, sizeof * a, SIZE, fp); // 写 double 的数组fclose(fp);double b[SIZE];fp = fopen("test.bin", "rb");size_t ret_code = fread(b, sizeof * b, SIZE, fp); // 读 double 的数组if (ret_code == SIZE) {puts("Array read successfully, contents: ");for (int n = 0; n < SIZE; ++n)printf("%f ", b[n]);putchar('\n');}else { // error handlingif (feof(fp))printf("Error reading test.bin: unexpected end of file\n");else if (ferror(fp)) {perror("Error reading test.bin");}}fclose(fp);
}
8.、⽂件缓冲区
文件缓冲区的意思我们可以理解为,在内存中存放的数据,存放到一定数量的时候,再写入硬盘里,如果没有文件缓冲区那么就需要时时刻刻把文件数据放入到硬盘里,这样太浪费算力了。