今天给大🔥带来文件操作的讲解,虽然这些内容好像实际上用的不多,但是技多不压身嘛,
多学点知识总是好的!
目录
1. 为什么使用文件?
我们知道,电脑的存储分为内存和磁盘空间,内存是在通电(程序运行)的时候才能够存储东西,一旦断电(程序关闭)就会导致存储的数据丢失,我们的机器一般不会永久不停地运行,这时就需要用到文件了,文件可以永久地保存数据,并且将数据存储到磁盘空间内,这些数据即使断电了也不会消失,下次启动程序时数据会得到保存。
2. 什么是文件?
2.1程序文件
2.2数据文件
2.3文件名
⽂件名包含3部分:⽂件路径+⽂件名主⼲+⽂件后缀例如: c:\code\test.txt
3. 二进制文件和文本文件
根据数据的组织形式,数据⽂件被称为⽂本⽂件或者⼆进制⽂件。
对于同样一份数据:
①以二进制的形式写入数据的文件被称为二进制文件。
②将二进制(根据ASCII码)转化成字符的形式再写入数据的文件被称为文本文件
举个例子:
同样一份数据:阿拉伯数字10000,分别在文本文件和二进制文件中是如何存储的?
如上图
我们打开txt文件时默认的打开方式是打开文本文件的打开方式。
思考:如果用打开文本文件的打开方式来打开二进制文件会发生什么?
测试代码:
#include<stdio.h>
int main()
{
int a = 10000;
//打开文件
FILE* pf = fopen("test.txt", "wb");//为了写数据,打开一个二进制文件
//若打开文件失败,则退出程序
if (pf == NULL)
{
perror(fopen);
return 1;
}
//写入
fwrite(&a, 4, 1, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
会出现乱码。
那么要如何正确的打开二进制文件呢?
以下是VS打开二进制文件的方法:
打开文件结果为:
4. 文件的打开和关闭
4.1流和标准流
4.1.1流
4.1.2标准流
既然说输入输出数据都要用到“流”,那么问题来了:咱写C程序时用的scanf和printf函数可以从键盘输入数据,可以像屏幕输出数据,这段过程中有打开过“流”吗?
有!
C程序在启动的时候,就会默认打开三个流:
• stdin - 标准输⼊流,在大多数的环境中从键盘输⼊,scanf函数就是从标准输⼊流中读取数据。• stdout - 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中。• stderr - 标准错误流,大多数环境中输出到显示器界⾯。
4.2文件指针
每个被使⽤的⽂件都在内存中(自动)开辟了⼀个相应的文件信息区,⽤来存放⽂件的相关信息(如⽂件的名字,⽂件状态及⽂件当前的位置等)。这些信息是保存在⼀个结构体变量中的。该结构体类型是由系统声明的,取名 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;//文件指针变量
![](https://i-blog.csdnimg.cn/blog_migrate/374a63adadd7bba9aff2593ec969de8f.png)
4.3文件的打开和关闭
⽂件在读写之前应该先打开⽂件,在使⽤结束之后应该关闭⽂件。在编写程序的时候,在打开⽂件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建⽴了指针和⽂件的关系。ANSI C 规定使用 fopen 函数来打开文件, fclose 来关闭文件。
//打开⽂件
FILE * fopen ( const char * filename, const char * mode );
//关闭⽂件
int fclose ( FILE * stream );
打开文件的时候需要确认两个参数:
①filename为文件名,例如:"test.txt"
②mode为文件的打开模式
以下都为文件的打开模式
文件使用方式 | 含义 | 如果指定文件不存在 |
"r"(只读) | 为了输入数据,打开一个已存在的文件 | 出错 |
"w"(只写) | 为了输出数据,打开一个文件 | 建立一个新文件 |
"a"(追加) | 在文本末尾追加数据 | 建立一个新文件 |
"rb"(只读) | 为了输入数据,打开一个已存在的二进制文件 | 出错 |
"wb"(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新文件 |
"ab"(追加) | 在一个二进制文件末尾追加数据 | 建立一个新文件 |
"r+"(读写) | 为了读和写,打开一个已存在的文件 | 出错 |
"w+"(读写) | 为了读和写,打开一个文件 | 建立一个新文件 |
"a+"(读写) | 打开一个文件,在文件尾读和写 | 建立一个新文件 |
"rb+"(读写) | 为了读和写,打开一个已存在的二进制文件 | 出错 |
"wb+"(读写) | 为了读和写,打开一个二进制文件 | 建立一个新文件 |
"ab+"(读写) | 打开一个二进制文件,在文件尾读和写 | 建立一个新文件 |
实例代码:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "w");//用只写的模式打开文件test.txt
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
fputs("Hello World", pf);//往文件输出字符串数据
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
实现效果:
5. 文件的顺序读写
5.1顺序读写函数介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本输入函数 | 所有输入流 |
fputs | 文本输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文件输入流 |
fwrite | 二进制输出 | 文件输出流 |
上⾯说的适⽤于所有输⼊流⼀般指适⽤于标准输入流和其他输入流(如文件输⼊流);所有输出流⼀般指适⽤于标准输出流和其他输出流(如文件输出流)。
5.2对比两组函数
①printf / fprintf / sprintf
②scanf / fscanf / sscanf
5.2.1第一组
①printf:通过标准输出流(stdout),向屏幕输出格式化的数据。
摘自网站cplusplus:printf - C++ Reference (cplusplus.com)
int printf ( const char * format);
ps:格式化就是数据要按照某种格式输出或输入
例如:使用printf时用的%d,这就是整形输出的格式;scanf用的%d就是整形输入格式。
②fprintf:通过选择输出流(可选标准输出流或者文件输出流或者其他),向屏幕或者文件输出格式化数据。
函数定义部分:int fprintf ( FILE * stream, const char * format);
摘自cplusplus:fprintf - C++ Reference (cplusplus.com)
参数分析:
第一个参数的类型是FILE*,即选择流:若要打印到屏幕,则使用标准输出流(stdout),若要输出到文件,则使用文件输出流,传入文件指针pf。
第二个参数为格式化的数据,例如: "%d %s",10,"I am a student"
实例代码:
以下为:使用文件输出流的fprintf
#include<stdio.h>
typedef struct student
{
char name[20];
int age;
}S;
int main()
{
S s = { "张三",20 };
//打开文件
FILE* pf = fopen("test.txt", "w");//用只写的模式打开文件test.txt
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
fprintf(pf, "%s %d", s.name, s.age);//使用文件输出流
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
以下为使用标准输出流(stdout)的fprintf:
#include<stdio.h>
typedef struct student
{
char name[20];
int age;
}S;
int main()
{
S s = { "张三",20 };
fprintf(stdout, "%s %d", s.name, s.age);//使用标准输出流
return 0;
}
③sprintf:将格式化的数据转化成字符串。
函数定义: int sprintf ( char * str, const char * format);
摘自cplusplus:sprintf - C++ Reference (cplusplus.com)
参数分析:
第一个参数是char型指针,即将格式化的数据转化成字符串,并将字符串的首地址赋给str。
第二个参数是格式化的数据。
代码实例:
#include<stdio.h>
typedef struct student
{
char name[20];
int age;
}S;
int main()
{
S s = { "张三",20 };
char str[200] = { 0 };
sprintf(str,"%s %d", s.name, s.age);//将结构体成员转化成字符串
printf("%s\n", str);
return 0;
}
5.2.2第二组
①scanf:使用标准输入流(stdin),将格式化的数据输入(通过地址)并赋给相应变量。
int scanf ( const char * format);
摘自:scanf - C++ Reference (cplusplus.com)
②fscanf:选择输入流(可选标准输入流或者文件输入流或者其他),将格式化数据输入(通过地址)并赋给相应变量。
int fscanf ( FILE * stream, const char * format);
摘自:fscanf - C++ Reference (cplusplus.com)
参数分析:
第一个参数:选择流,若选标准输入流则使用stdin,若选文件输入流则使用pf(文件指针)。
第二个参数:格式化数据(注意:记得加取地址符&)
代码实例:
-使用文件输入流:
#include<stdio.h>
typedef struct student
{
char name[20];
int age;
}S;
#include<stdio.h>
int main()
{
S s = { 0 };
//打开文件
FILE* pf = fopen("test.txt", "r");//用只读的模式打开文件test.txt
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
fscanf(pf, "%s %d", &s.name, &(s.age));//使用文件输入流读取数据并赋值给结构体成员
printf("%s %d\n", s.name, s.age);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
文件test的内容如下:
程序运行结果如下:
-若使用标准输入流(效果与scanf相同):
#include<stdio.h>
typedef struct student
{
char name[20];
int age;
}S;
#include<stdio.h>
int main()
{
S s = { 0 };
fscanf(stdin, "%s %d", &s.name, &(s.age));//使用标准输入流读取数据并赋值给结构体成员
printf("%s %d\n", s.name, s.age);
return 0;
}
③sscanf:将字符串数据转化成格式化数据,并赋值给相应变量。
int sscanf ( const char * s, const char * format);
摘自cplusplus:sscanf - C++ Reference (cplusplus.com)
参数分析:
第一个参数为字符串的首地址,用于转化成格式化数据。
第二个参数为格式化数据的输入,并赋值给相应的变量。
实例代码如下:
#include<stdio.h>
typedef struct student
{
char name[20];
int age;
}S;
int main()
{
S s = { "张三",20 };
char str[200] = { 0 };
sprintf(str, "%s %d", s.name, s.age);//将结构体成员转化成字符串
S t = { 0 };
sscanf(str, "%s %d", t.name, &(t.age));//将字符串str按照格式化输入并赋值给结构体成员
printf("%s %d\n",t.name,t.age );
return 0;
}
6. 文件的随机读写
6.1fseek
int fseek ( FILE * stream, long int offset, int origin );
功能:该函数能够将文件的光标指向设置好的位置。
以下为每个参数的含义:
①FILE*stream为文件指针变量名
②long int offset为偏移量(光标移动几下)
③int origin光标的起始位置(从哪里开始偏移)
对于参数origin有三种形式:
以下摘自cplusplus:fseek - C++ Reference (cplusplus.com)
实例代码:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt","r");
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
fseek(pf, 5, SEEK_SET);//将光标从文件开头处向右移动5位
char ch1=fgetc(pf);//获取光标处的字符
printf("ch1:%c\n", ch1);//打印该字符
fseek(pf, 5, SEEK_CUR);//将光标从指针(pf)当前位置向右移动5位
char ch2=fgetc(pf);
printf("ch2:%c\n", ch2);//打印该字符
fseek(pf, -2, SEEK_END);//将光标从文件末尾处向左移动2位
char ch3 = fgetc(pf);//获取光标处的字符
printf("ch3:%c\n", ch3);//打印该字符
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
txt文本中的数据:
运行结果:
分析过程如下图:
要注意,SEEK_END指向的是文件末尾处,此时光标处的数据为空,如果直接打印此处数据会得出乱码。
6.2ftell
功能:返回⽂件指针相对于起始位置的偏移量函数: long int ftell ( FILE * stream );
代码实例:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");//用只读的模式打开文件test.txt
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
fseek(pf, 0, SEEK_END);//将光标置到末尾处
long size = ftell(pf);//返回光标相对于文件开头的偏移量
printf("%d\n", size);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
txt文件内容:
运行结果:
6.3rewind
功能:让⽂件指针的位置回到⽂件的起始位置函数:void rewind ( FILE * stream );
代码实例:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r+");//用读写的模式打开文件test.txt
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
char ch = 0;
for (ch = 'A'; ch <= 'Z'; ch++)
{
fputc(ch, pf);//将26个字符:A-Z 依次输入到文件test内
}
rewind(pf);//让光标回到文件最开头处
ch = 0;
while ((ch = fgetc(pf)) != EOF)//将文件数据从头到尾打印出来
{
putc(ch, stdout);
}
printf("\n");
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
7. 文件读取结束的判定
7.1文本文件读取结束的判定
⽂本⽂件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )例如:• fgetc 判断是否为 EOF .• fgets 判断返回值是否为 NULL .
7.2二进制文件读取结束的判定
2. ⼆进制⽂件的读取结束判断,判断返回值是否⼩于实际要读的个数。例如:• fread判断返回值是否⼩于实际要读的个数。(fread会返回其读到数据的个数)
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
7.3被错误使用的feof
牢记:在⽂件读取过程中,不能⽤feof函数的返回值直接来判断⽂件的是否结束。feof 的作⽤是:当⽂件读取结束的时候,判断是读取结束的原因是否是:遇到⽂件尾结束。
int feof ( FILE * stream );
结论:如果是读到文件末尾结束,就返回非0值,若是途中发生错误而结束,则返回0。
代码实例:
#include<stdio.h>
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r+");//用只读的模式打开文件test.txt
if (pf == NULL)//如果打开失败就报错
{
perror("fopen");
return 1;
}
//读写文件
char ch = 0;
for (ch = 'A'; ch <= 'Z'; ch++)
{
fputc(ch, pf);//将26个字符:A-Z 依次输入到文件test内
}
rewind(pf);//让光标回到文件最开头处
ch = 0;
while ((ch = fgetc(pf)) != EOF)//将文件数据从头到尾打印出来
{
putc(ch, stdout);
}
printf("\n");
//判断是什么原因结束的
if (ferror(pf))
{
puts("I/O error when reading\n");
}
else if (feof(pf))
{
puts("End of file reached successfully\n");
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
8. 文件缓冲区
ANSIC 标准采⽤“缓冲⽂件系统” 处理的数据⽂件的,所谓缓冲⽂件系统是指系统⾃动地在内存中为程序中每⼀个正在使⽤的⽂件开辟⼀块“⽂件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才⼀起送到磁盘上。如果从磁盘向计算机读⼊数据,则从磁盘⽂件中读取数据输⼊到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的⼤⼩根据C编译系统决定的。
完
看完觉得有用的请点个免费的赞和关注,这对我真的很重要!非常感谢大家!