目录
[1. 为什么使⽤⽂件?](#1. 为什么使⽤⽂件?)
[2. 什么是⽂件?](#2. 什么是⽂件?)
[3. ⼆进制⽂件和⽂本⽂件?](#3. ⼆进制⽂件和⽂本⽂件?)
[4. ⽂件的打开和关闭](#4. ⽂件的打开和关闭)
[4.3 ⽂件的打开和关闭](#4.3 ⽂件的打开和关闭)
[5.9、 sprintf函数](#5.9、 sprintf函数)
[6. ⽂件的随机读写](#6. ⽂件的随机读写)
[6.1.3、SEEK_END文件结束 *](#6.1.3、SEEK_END文件结束 *)
[6.3 rewind函数](#6.3 rewind函数)
[7. ⽂件读取结束的判定](#7. ⽂件读取结束的判定)
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 handling
if (feof(fp))
printf("Error reading test.bin: unexpected end of file\n");
else if (ferror(fp)) {
perror("Error reading test.bin");
}
}
fclose(fp);
}
8.、⽂件缓冲区
文件缓冲区的意思我们可以理解为,在内存中存放的数据,存放到一定数量的时候,再写入硬盘里,如果没有文件缓冲区那么就需要时时刻刻把文件数据放入到硬盘里,这样太浪费算力了。