目录
文件名
文件类型
文件指针
文件的打开和关闭
文件打开模式
文件操作函数(顺序)
0、“流”
1、字符输出函数fputc
2、字符输入函数fgetc
3、字符串输出函数fputs
4、 字符串输入函数fgets
5、格式化输入函数fscanf
6、格式化输出函数fprintf
7、二进制输入函数fread
8、二进制输出函数fwrite
几种输入输出
文件随机读写函数
1、fseek:定位指针
2、ftell:返回指针位置
3、rewind:重定位指针
4、实例
文本文件和二进制文件
文件读取结束的判定
1、被错误使用的feof函数
2、判断文件是否读取结束:
3、实例
文件缓冲区
改造通讯录(可保存信息):month_11/test_12 · Hera_Yc/bit_C_学习 - 码云 - 开源中国
本文相关代码:month_11/test_16/main.c · Hera_Yc/bit_C_学习 - 码云 - 开源中国
文件:即硬盘上的文件,文件将数据直接存放在电脑的硬盘上,做到了数据的持久化。
在程序设计当中,程序员所说的文件一般分为两种:程序文件、数据文件。
- 程序文件:包括源程序文件(后缀为 .c )、目标文件(windows环境后缀为 .obj )、可执行程序文件(windows环境后缀为 .exe )。
- 数据文件:文件的内容不一定是程序,而是程序运行时读写的数据。
个人对文件操作的理解:
- 文件操作这里的本质就是一堆库函数,懂得这些库函数就可以对函数进行操作了。
- 文件的使用与动态内存类似,需要“开辟”和“释放”,即文件的打开和关闭。
- 相对于内存来说,文件(硬盘)是一种外部设备。
文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含三部分:文件路径+文件名主干+文件后缀
如:"C:\Users\Desktop\newc++file.cpp"
文件标识通常称为文件名。
windows和Linux允许没有后缀名的文件。
文件类型
包含在头文件<stdio.h>中
Q:什么是文件类型?
A:每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(文件名、文件状态、文件当前位置等)。这些信息是保存在一个结构体变量当中的,该结构体类型是由系统声明的,取名FILE。
文件类型声明:
struct _iobuf {
char* _ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charnuf;
int _butsiz;
char* _tmpfname;
};
typedef struct _iobuf FILE;
文件类型并不是真正文件的类型,而是将硬盘文件的一些信息作为一组数据存放在内存中,CPU通过这组数据来对文件进行操作。
在对文件进行操作时,可以将文件看作一个FILE类型的变量,FILE类型的变量是在使用文件时,系统自动创建的。
文件指针
FILE* pf;
文件指针:定义pf是一个指向FILE类型的数据变量。通过文件指针变量能够找到与它关联的文件。
文件的打开和关闭
文件读写之前先打开文件,在使用结束之后要关闭文件。
文件的使用:打开文件--->读出\写入数据--->关闭文件。
ANSIC规定使用fopen打开文件,fclose关闭文件。
函数声明:
FILE* fopen(const char* filename, const char* mode);
//filename :文件名
//mode :打开模式
int fclose(FILE* stream);
//stream :文件指针
文件打开模式
-
'r'模式:这是“只读”模式。如果文件不存在,尝试打开将失败。文件指针位于文件开头,不会清空文件原有内容。
-
'w'模式:这是“只写”模式。如果文件存在,它将被清空并从头开始写入;如果文件不存在,将创建一个新文件。文件指针位于文件开头。
-
'a'模式:这是“追加”模式。如果文件存在,写入的数据将添加到文件末尾,不会清空原有内容;如果文件不存在,将创建一个新文件。文件指针位于文件结尾。
-
'r+'模式:这是“读写”模式。文件必须存在,文件指针位于文件开头。可以在文件任意位置读取或写入内容,写入操作会覆盖原有位置的内容。
-
'w+'模式:这也是“读写”模式。它类似于'w'模式,但是它允许读取操作。打开文件后,会清空文件内原有的内容。
-
'a+'模式:这同样是“读写”模式。它类似于'a'模式,但是它允许读取操作。写入内容时,只会追加在文件尾部。
-
还有很多操作模式,感兴趣读者自行查阅。
实例:
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//fopen的参数是:路径 、操作
if (pf == NULL)
{
perror("fopen");
//perror等价于printf("open:%s\n",strerror(errno));
return 1;
}
//文件操作
//...
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
文件操作函数(顺序)
头文件<stdio.h>
0、“流”
文件操作函数中的输入输出是相对于内存而言的:
屏幕和键盘也算一种外部设备(通过流),为什么C程序不用主动的去打开或关闭这些外部设备?
1、任何一个C程序会默认打开3个流
- FILE* stdin - 标准输入流 (键盘)
- FILE* stdout - 标准输出流 (屏幕)
- FILE* stderr - 标准错误流 (屏幕)
int main()
{
struct S s = { 0 };
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//把文件看作命令行
//在命令行里面输入程序数据。输入到内存
//因此是“输入”函数
fscanf(pf, "%s %d %f", s.arr, &(s.age), &(s.score));
//printf("%s %d %f", s.arr, s.age, s.score);
//等价于
fprintf(stdout, "%s %d %f", s.arr, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
1、字符输出函数fputc
函数声明:
int fputc( int c, FILE *stream );
使用:
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");// w:写入模式
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
char i = 0;
for (i = 'a'; i < 'f'; i++)
{
fputc(i, pf);
}
fclose(pf);
pf = NULL;
return 0;
}
2、字符输入函数fgetc
函数声明:
int fgetc( FILE *stream );
使用:
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");// r:读取模式
if (pf == NULL)
{
perror("fopen");
return 1;
}
int ch = '\0';
while ((ch = fgetc(pf)) != EOF)
{
printf("%c\n", ch);
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
3、字符串输出函数fputs
声明:
int fputs( const char *string, FILE *stream );
使用:
4、 字符串输入函数fgets
char *fgets( char *str, int n, FILE *stream );
//str:数据的存储位置
//n:要读取的最大字符数
//stream:文件指针
使用:
5、格式化输入函数fscanf
int fscanf( FILE *stream, const char *format [, argument ]... );
使用:
int main()
{
struct S s = { 0 };
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//把文件看作命令行
//在命令行里面输入程序数据
//因此是“输入”函数
fscanf(pf, "%s %d %f", s.arr, &(s.age), &(s.score));
//printf("%s %d %f", s.arr, s.age, s.score);
//等价于
fprintf(stdout, "%s %d %f", s.arr, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
6、格式化输出函数fprintf
int fprintf( FILE *stream, const char *format [, argument ]...);
使用:
7、二进制输入函数fread
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
使用:
int main()
{
struct S s = { 0};
FILE* pf = fopen("test.txt", "rb");//rb:二进制读模式
if (pf == NULL)
{
perror("fopen");
return 1;
}
//以二进制形式写入到文件中
fread(&s, sizeof(struct S), 1, pf);//读二进制数据,放在s中
printf("%s %d %f", s.arr, s.age, s.score);
fclose(pf);
pf = NULL;
return 0;
}
8、二进制输出函数fwrite
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
//buffer:待写入数据的地址
//size:待写入数据的大小
//count:最大写入项数
//stream:指向 FILE 结构的指针
使用:
几种输入输出
int sprintf( char *buffer, const char *format [, argument] ... );
int sscanf( const char *buffer, const char *format [, argument ] ... );
使用:
int main()
{
struct S s = { "zhangsan",20,50.5f };
struct S tmp;
char buf[100] = { 0 };
//把s中的格式化数据转化成字符串放到buf中
sprintf(buf, "%s %d %f", s.arr, s.age, s.score);
printf("字符串:%s\n", buf);//这里打印的就是一个字符串了
//从字符串buf中获取一个格式化数据到tmp中
sscanf(buf, "%s %d %f", tmp.arr, &(tmp.age), &(tmp.score));
printf("格式化:%s %d %f \n", tmp.arr, tmp.age, tmp.score);
return 0;
}
补充:动态内存和文件、输入输出、流
文件随机读写函数
1、fseek:定位指针
int fseek( FILE *stream, long offset, int origin );
//offset:偏移量
//origin:起始位置
fseek通过起始位置+偏移量的方法定位指针 。
origin由三种取值:
- SEEK_SET:文件起始位置
- SEEK_CUR:当前指针位置
- SEEK_END:文件末尾
2、ftell:返回指针位置
long ftell( FILE *stream );
//返回指针相对于起始位置的偏移量
3、rewind:重定位指针
void rewind( FILE *stream );
//使指针返回到起始位置
4、实例
int main()
{
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("Fopen");
return 1;
}
//当前文件中存放:abcdef
//定位文件指针
fseek(pf, -2,SEEK_END );
//当前pf位置 = -2 + SEEK_END
printf("%d\n", ftell(pf));//4
//相对于文件起始位置的偏移量
int ch = fgetc(pf);
printf("%c\n", ch); // 打印的是e
rewind(pf);//将指针返回到文件起始位置
printf("%d\n", ftell(pf));//0
fclose(pf);
pf = NULL;
}
文本文件和二进制文件
根据数据的组织形式,数据文件分为:
- 二进制文件:把内存中的二进制数据,不加任何转化输出到外存中去,就是二进制文件。
- 文本文件:在外存中以ASCII码的形式存放,在存储前需要转换。
文件读取结束的判定
1、被错误使用的feof函数
feof函数:在文件读取的过程中,不能用feof函数的返回值直接用来判断文件是否结束。而是应用于当文件读取结束时,判断读取结束的原因(是遇到文件尾部结束,还是读取失败?)。
int ferror( FILE *stream );
//ferror函数用于检测文件读写过程中是否产生了错误。
int feof( FILE *stream );
//feof函数用于检测是否已到达文件末尾(EOF,End Of File的缩写)
2、判断文件是否读取结束:
- 文本文件读取是否结束,判断返回值是否为EOF(fgetc函数),或者为NULL。
- 二进制文件读取结束判断,判断返回值是否小于实际要读取的个数。
对于文本文件:
fgetc:返回值为NULL或者EOF,表示文件读取结束,可以利用feof来对fgetc的返回值进行判断,来确定fgetc为什么读取结束。
对于二进制文件:
fread:返回值是实际读到的元素的个数,参数count是要求读到的个数。比较返回值和count的关系,就可以判断二进制文件读取是否结束。
3、实例
ferror和feof两者经常搭配使用:(以二进制读写为例)
enum { SIZE = 5 };
int main()
{
double a[SIZE] = { 1,2,3,4,5 };
FILE* fp = ("test.txt", "wb");
if (fp == NULL)
{
perror("Fopen");
return 1;
}
fwrite(a, sizeof(*a), SIZE, fp);
fclose(fp);
double b[SIZE];
fp = fopen("test.txt", "rb");
if (fp == NULL)
{
perror("Fopen");
return 1;
}
size_t ret_code = fread(b, sizeof(*b), 8, fp);
if (ret_code == SIZE){
puts("数组读取成功:");
int i = 0;
for (i = 0; i < SIZE; i++)printf("%f ", b[i]);
putchar('\n');
}
else{
if (!feof(fp))
printf("未达到文件末尾\n");
else if (ferror(fp)){
printf("读取错误\n");
}
}
fclose(fp);
fp = NULL;
return 0;
}
文件缓冲区
(操作系统会对缓冲区进行详细的讲解,缓冲区也有相应的库操作函数)
文件缓冲区是内存区预留的一定空间,用以暂时存放读写期间的文件数据。其主要目的是减少读取硬盘的次数,因为硬盘的读写速度相对较慢,而内存的读写速度较快。通过缓冲区,系统可以先将数据读入内存,然后再从内存中读取或写入数据,从而减少了对硬盘的直接访问,提高了数据处理的效率。
缓冲区存在的意义
- 提高数据读写效率:通过缓冲区,系统可以减少对硬盘的直接访问次数,从而提高数据读写的效率。
- 保护硬盘:频繁的硬盘访问会加速硬盘的磨损和老化。通过缓冲区,系统可以减少对硬盘的访问次数,从而延长硬盘的使用寿命。
- 提高系统稳定性:缓冲区可以平滑数据传输过程中的波动和延迟,提高系统的稳定性和可靠性。
因为有缓冲区的存在,C语言在操作文件的时候,需要刷新文件缓冲区或在文件操作结束时关闭文件。(fclose,也会自动刷新缓冲区)。
---------------------------------------------------文件到此结束------------------------------------------------------------
文件了解即可。其实文件没有那么重要,知道最初识得文件操作函数即可,因为在实际项目开发的时候,不可能直接对文件来进行读写,而是利用数据库来存取数据,更加高效。