一、关于文件的基础知识
1.为什么要学习文件
我们在学习C语言的过程中会经常使用 printfscanf 等输入输出函数,但是我们深入思考就会发现:我们经常使用的输入输出函数都是从内存中拿数据,以及显示到显示器上,当我们关闭正在运行的程序时我们的数据就全部丢失了,然而有些时候我们并不想丢失我们的数据,那么我们就要想办法把数据保存起来了,对于我们来说保存数据的最好方式有两种一种是把数据存放在磁盘文件、另一种是存放到数据库。
使用文件让我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。
2.什么是文件
其实什么是文件并没有什么好说的,磁盘上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)
- 程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe) 。 - 数据文件
文件的内容不是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
3.文本文件与二进制文件
- 文本文件
基于字符编码,常见编码有ASCII、UNICODE等,一般可以使用文本编辑器直接打开。
数5678的以 ASCII存储形式(ASCII码)为:
00110101 00110110 00110111 00111000. - 二进制文件。
基于值编码,自己根据具体应用,指定某个值是什么意思,把内存中的数据按其在内存中的存储形式原样输出到磁盘上.
数5678的存储形式(二进制码)为:
00010110 00101110.
二、文件的打开和关闭
1. 文件指针
文件指针 又叫做 “文件类型指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名 FILE.
不同的C编译器的 FILE 类型包含的内容不完全相同,但是大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
FILE* fp;//创建一个文件指针
定义pf 是一个指向FILE类型数据的指针变量。可以使 pf 指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
2.文件的打开和关闭
文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*的指针变量指向该文件,也相当于建立了指针和文件的关系。
ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。
- 打开的文件会占用内存资源,如果总是打开不关闭,会消耗很多内存。一个进程同时打开的文件数是有限制的,超过最大同时打开文件数(65535),再次调用fopen打开文件会失败
- 如果没有明确的调用fclose关闭打开的文件,那么程序在退出的时候,操作系统会统一关闭。
打开方式如下:
//打开文件 FILE * fopen ( const char * filename, const char * mode );
- 第一个参数是 文件名称,第二个参数是 文件的打开方式
- 文件打开后会返回一个指向该文件的文件信息区的文件指针,如果打开失败则返回空指针
//关闭文件 int fclose ( FILE * stream );
- 第一个参数是你想要关闭文件的文件指针
- 如果文件成功关闭,则返回零值,失败时,将返回 EOF。
文件打开方式汇总
实例代码
#include<stdio.h> int main() { FILE* fp;//创建一个文件指针 fp = fopen("test.txt", "w");//以写的方式打开一个文件 if (NULL == fp)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } fclose(fp);//关闭文件 fp = NULL;//将文件指针置为空值,防止野指针的出现 }
我们执行完代码就会发现我们当前代码路径下就多出了一个 test.txt 的文件
如果我们在fopen函数中的第一个参数填入其它路径加文件名我们就可以在其他路径下创建文件了
例如:
fp = fopen("c:\\code\\test.c", "w");//注意 \是转义字符,需要\\才能打印出一个\
执行完这句代码就会在 c:\code\test.c 路径下创建一个.c文件
3.文件结尾
在C语言中,EOF表示文件结束符(end of file)。在while循环中以EOF作为文件结束标志,这种以EOF作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的ASCII代码值的形式存放。我们知道,ASCII代码值的范围是0~127,不可能出现-1,因此可以用EOF作为文件结束标志。
#define EOF (-1)
当把数据以二进制形式存放到文件中时,就会有-1值的出现,因此不能采用EOF作为二进制文件的结束标志。为解决这一个问题,ANSIC提供一个feof函数,用来判断文件是否结束。feof函数既可用以判断二进制文件又可用以判断文本文件。
- 参数:文件指针
- 返回值∶非0值:己经到文作结尾,0值没有到文件结尾
- 功能∶检是否读到了文件结尾。判断的是最后一次“读操作的内容”,不是当前位置内容(上一个内容)。
三、文件的顺序读写
文件的打开和关闭学习完毕之后,我们就要尝试进行读写文件了,读写文件的函数如下:
文件与内存的数据交换
1.fputc与fgetc
- 第一个参数是我们要写入的字符,第二个参数是文件指针
- 成功后,将返回所写字符的ASCII码值。如果发生写入错误,则返回 EOF 并设置错误指示器(ferror)。
实例代码
#include<stdio.h> #include<stdlib.h> int main() { FILE* fp;//创建一个文件指针 fp = fopen("test.txt", "w");//以写的方式打开一个文件 if (NULL == fp)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //写文件 int i = 0; for (i = 0; i < 26; i++) { fputc('a' + i, fp);//写入26个小写英文字母 } fclose(fp);//关闭文件 fp = NULL;//将文件指针置为空值,防止野指针的出现 }
- 参数:第一个参数是文件指针,
- 返回值:成功后,将返回字符ASCII码值(提升为 int 值),返回类型为 int 以适应特殊值 EOF,该值表示失败:
- 作用:从文件流中获取字符,返回指定流的内部文件位置指示符当前指向的字符。然后,内部文件位置指示器将前进到下一个字符。
- 如果调用时流位于文件末尾,则该函数返回 EOF 并为流设置 (feof) 的文件结束指示器。
- 如果发生读取错误,该函数将返回 EOF 并为流设置错误指示器 (ferror)。
实例代码
#include<stdio.h> #include<stdlib.h> int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "w");//以写的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //写文件 int i = 0; for (i = 0; i < 26; i++) { fputc('a' + i, pf);//写入26个小写英文字母 } fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 pf = fopen("test.txt", "r");//以读的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //读文件 int ch; while((ch=fgetc(pf))!=EOF) { ch = fgetc(pf); printf("%c ", ch); } fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
2.fputs与fgets
- 参数:第一个参数是字符串,第二个参数是文件指针
- 返回值:成功时,将返回非负值,出错时,该函数返回 EOF 并设置错误指示器(ferror)。
- 作用:将 str 指向流的 C 字符串写入流。
该函数从指定的地址 (str) 开始复制,直到到达终止空字符 (‘\0’)。此终止空字符不会复制到流中。
请注意,fput 与 put 的不同之处不仅在于可以指定目标流,而且 fput 不会写入其他字符,而 put 会自动在末尾附加换行符(\n)。
#include<stdio.h> #include<stdlib.h> int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "w");//以写的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //写文件 fputs("hello", pf); fputs("world!", pf);//写入 helloworld! fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
- 参数:第一个参数字符数组的指针,第二个参数要复制到 str 的最大字符数(包括终止空字符),第三个文件指针。
- 返回值:成功后,函数返回 str。
如果在尝试读取字符时遇到文件末尾,则设置 eof 指示器 (feof)。如果在读取任何字符之前发生这种情况,则返回的指针为空指针(str 的内容保持不变)。
如果发生读取错误,则设置错误指示器(ferror),并返回空指针(但str指向的内容可能已更改)。 - 作用:从流中获取字符串
从流中读取字符并将其作为 C 字符串存储到 str 中,直到读取 (num-1) 个字符或到达换行符或文件末尾,以先发生者为准。
换行符使 fgets 停止读取,但它被函数视为有效字符,并包含在复制到 str 的字符串中。
终止空字符(‘\0’)会自动附加到复制到 str 的字符之后。
请注意,fgets 与 get 完全不同:fgets 不仅接受流参数,还允许指定 str 的最大大小,并在字符串中包含任何结束换行符。
#include<stdio.h> #include<stdlib.h> int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "r");//以读的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //读文件 char str[10] = "xxxxxxxxx"; fgets(str,3, pf); fgets(str+3,3, pf); fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
3.fprintf与fscanf
- 参数:第一个参数是文件指针,第二个参数是格式化
- 返回值:成功后,将返回写入的字符总数。如果发生写入错误,则设置错误指示器(ferror)并返回负数。如果在写入宽字符时发生多字节字符编码错误,errno 将设置为 EILSEQ 并返回负数。
- 作用:将格式化数据写入流
将按格式指向流的 C 字符串写入流。如果 format 包含格式说明符(以 % 开头的子序列),则格式后面的其他参数将被格式化并插入到生成的字符串中,替换其各自的说明符。
在 format 参数之后,函数至少需要与格式指定的一样多的其他参数。
代码实例
#include<stdio.h> #include<stdlib.h> struct S { char name[10]; int age; float score; }; int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "w");//以写的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //写文件 struct S s = { "xiaoming",17,89 }; fprintf(pf, "%s %d %.1f", s.name,s.age,s.score); fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
- 参数:第一个参数是文件指针,第二个参数是格式化
- 返回值:成功后,该函数返回成功填充的参数列表的项数。此计数可以与预期的项目数匹配,也可以由于匹配失败、读取错误或文件末尾的到达而减少(甚至为零)。
如果发生读取错误或在读取时到达文件末尾,则会设置正确的指示器(feof 或 ferror)。并且,如果在成功读取任何数据之前发生任一情况,则返回 EOF。
如果在解释宽字符时发生编码错误,该函数会将 errno 设置为 EILSEQ。 - 作用:从流中读取格式化数据
从流中读取数据,并根据参数格式将其存储到附加参数指向的位置。
其他参数应指向格式字符串中相应格式说明符指定的类型的已分配对象。
//按格式化读文本 #include<stdio.h> #include<stdlib.h> struct S { char name[10]; int age; float score; }; int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "r");//以读的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } //读文件 struct S s = { 0 }; fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score)); printf("%s %d %.1f", s.name, s.age, s.score); fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
4.fwrite与fread
- 第一个参数是指向要写入的元素数组的指针,转换为 const void*。
- 第二个参数是要写入的每个元素的大小(以字节为单位)。size_t是无符号整数类型。
- 第三个参数是元素数,每个元素的大小为字节大小。size_t 是无符号整数类型。
- 第四个参数是指向指定输出流的 FILE 对象的指针。
- 返回值:返回成功写入的元素总数。
如果此数字与 count 参数不同,则写入错误阻止函数完成。在这种情况下,将为流设置错误指示器(ferror)。
如果大小或计数为零,则该函数返回零,错误指示器保持不变。 - 作用:写入要流式传输的数据块
将计数元素数组(每个元素的大小为字节)从 ptr 指向的内存块写入流中的当前位置。
流的位置指示器按写入的总字节数前进。
代码实例
//二进制写文本 #include<stdio.h> #include<stdlib.h> struct S { char name[10]; int age; float score; }; int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "wb");//以写的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } struct S s = { "xiaoming",17,89 }; //写文件 fwrite(&s, sizeof(s), 1,pf); fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
此时我们用我们的人类语言已经无法理解这些符号了。
- 第一个参数是指向大小至少为 (sizecount) 字节的内存块的指针,转换为 void。
- 第二个参数是要读取的每个元素的大小(以字节为单位)。size_t是无符号整数类型。
- 第三个参数是元素数,每个元素的大小为字节大小。size_t 是无符号整数类型。
- 第四个参数是指向指定输入流的 FILE 对象的指针。
- 返回值:返回成功读取的元素总数。
如果此数字与 count 参数不同,则表示读取时发生读取错误或到达文件末尾。在这两种情况下,都会设置正确的指标,可以分别用 ferror 和 feof 进行检查。
如果大小或计数为零,则该函数返回零,并且流状态和 ptr 指向的内容保持不变。
//二进制读文本 #include<stdio.h> #include<stdlib.h> struct S { char name[10]; int age; float score; }; int main() { FILE* pf;//创建一个文件指针 pf = fopen("test.txt", "rb");//以读的方式打开一个文件 if (NULL == pf)//判断文件打开是否成功 { perror("fopen fail");//打印错误信息 exit(-1);//退出程序 } struct S s ; //读文件 fread(&s, sizeof(s), 1, pf); printf("%s %d %f", s.name, s.age, s.score); fclose(pf);//关闭文件 pf = NULL;//将文件指针置为空值,防止野指针的出现 }
5.三个标准流
对任何一个c程序,只要运行起来就默认打开3个流:
这三个流的类型都是 FILE*类型
stdin - 标准输入流- 键盘
stdout- 标准输出流- 屏幕
stderr - 标准错误流- 屏幕
代码实例
//标准流 #include<stdio.h> int main() { //从键盘中读取数据,输出到屏幕 char ch=fgetc(stdin); fputc(ch, stdout); return 0; }