一.为什么使用文件
对数据进行持久化的保存
二.什么是文件
1.文件的分类
(1)磁盘(硬盘 )上的文件是文件
(2)程序设计中说的文件一般包含:程序文件,数据文件
2.文件的介绍
(1)程序文件:源程序文件(xxx.c);目标文件(xxx.obj);可执行程序(xxx.exe)
(2)数据文件:
文件的内容不一定是程序,而是程序运行时读写的数据;
有时我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这处理的就是磁盘上文件;
我们大多数时候处理数据的输入和输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上
(3)文件名
一个文件的唯一的文件标识常被称为文件名
三.二进制文件和文本文件
1.分类
(1)二进制文件:数据在内存中以二进制的形式存储,不加转换地输出到外存的文件中
(2)文本文件:在存储前转换,在外存上以ASCII码的形式存储的文件
2.数据在文件中的存储
字符一律以ASCII形式存储;
数值型数据可以用ASCII/二进制形式存储
#include<stdio.h>
int main() {
int i = 10000;
//ASCII码的形式输出到磁盘,磁盘中占用5个字节(每个字符一个字节)
//00110001 00110000 00110000 00110000 00110000
//二进制形式输出到磁盘,磁盘中占用4个字节
//00000000 00000000 00100111 00010000
//内存中:
//00000000 00000000 00100111 00010000
FILE* pf = fopen("test.txt", "wb");
fwrite(&i, 4, 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
四.文件的打开和关闭
1.流和标准流
可以吧流想象成流淌着字符的河;
c程序针对文件,画面,键盘等 数据输入输出操作都是通过流操作的;
一般情况下,我们嫩想要向流里写数据,或者从流中读取数据,都是要打开流,然后操作;
2.标准流
从键盘输入数据,向屏幕上输出数据,并没有打开流,原因在于:C语言程序在启动的时候,默认打开了3个流:
stdin--标准输入流;大多数环境中从键盘输入,scanf函数就是从标准输入流中读取数据
stdout--标准输出流;大多数环境中输出至显示器界面,printf函数就是将信息输出到标准输出流中
stderr--标准错误流;大多数环境中输出到显示器界面
默认打开了这3个流,我们使用scanf,printf等函数就可以直接进行输入和输出操作;
这3个流的类型都是FILE*(文件指针)
C语言就是通过FILE类型的文件指针来维护流的各种操作
3.文件指针
FILE* pf = fopen("test.txt", "wb");
含义:定义一个指针变量pf,指向FILE类型的数据,指向该文件的文件信息区(一个结构体变量),通过改文件信息去区的信息,可以访问该文件;即通过文件指针可以找到与他关联的文件。
4.文件的打开和关闭
(1)文件在读写之前应该打开文件,在使用结束后应该关闭文件;
(2)在编写程序的时候,在打开文件的同时都会返回一个FILE*类型的指针变量,指向该文件,也相当于建立了指针和文件的关系;
(3)ANSIC规定用fopen函数打开文件,fclose函数关闭文件;
FILE* fopen(const char* filename,const char* mode);
FILE* fclose(FILE* stream);
(4)mode表示的文件打开模式
文件使用方式 | 含义 | 如果指定文件不存在 |
"r"(只读) | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
"w"(只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
"a"(追加) | 向一个文本文件末尾添加数据 | 建立一个新的文件 |
"rb"(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
"wb"(只写) | 为了输出数据,打开一个二进制文件 | 建立一个新的文件 |
"ab"(追加) | 向一个二进制文件末尾添加数据 | 建立一个新的文件 |
"r+"(读写) | 为了读和写,打开一个文本文件 | 出错 |
"w+"(读写) | 为了读和写,建立一个文本文件 | 建立一个新的文件 |
"a+"(读写) | 打开一个文件,在文件末尾进行读写 | 建立一个新的文件 |
"rb+"(读写) | 为了读和写,打开一个二进制文件 | 出错 |
"wb+"(读写) | 为了读和写,新建一个二进制文件 | 建立一个新的文件 |
"ab+"(读写) | 打开一个二进制文件,在文件末尾进行读和写 | 建立一个新的文件 |
(5)举例
#include<stdio.h>
int main() {
//如果存在文件,且文件中有内容的话,文件内容会被清空
//相对路径:代码在哪,文件在哪
//.表示当前目录;..表示上一级路径
FILE* pf = fopen("test.txt", "w");
if (!pf) {
perror("fopen");
return 1;
}
fclose(pf);
pf = NULL;
return 0;
}
五.文件的顺序读写
1.顺序读写函数的介绍
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
sprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入函数 | 文件输入流 |
fwrite | 二进制输出函数 | 文件输出流 |
2.函数的对比
(1)scanf/printf
scanf:针对标准输入(键盘)的格式化输入函数
int scanf(const char* format,......);
printf:针对标准输出(屏幕)的格式化输入函数
int printf(const char* format,......);
(2)fscanf/fprintf
fscanf:针对所有输入流的格式化输入函数
int fscanf(FILE* stream,const char* format,......);
fprintf:针对所有输出流的格式化输出函数
Int fprintf(FILE* stream,const char* format,......);
(3)sscanf/sprintf
sscanf:从一个字符串中读取与指定格式相符的数据的函数
int sscanf(const char* s,const char* format,......);
sprintf:用于将格式化输出写入字符串
int sprintf(char* s,const char* format,.....);
#include<stdio.h>
struct S {
char name[20];
int age;
double score;
};
int main() {
struct S s = { "lisi", 20,98.5 };
struct S tmp = { 0 };
char arr[100] = { 0 };
sprintf(arr, "%s,%d,%lf\n", s.name, s.age, s.score);
printf("%s", arr);
sscanf(arr, "%s,%d,%lf", tmp.name, &(tmp.age), &(tmp.score));
printf("%s,%d,%lf\n", tmp.name, tmp.age, tmp.score);
return 0;
}
3.举例(文件拷贝)
//完成一份将data1.txt文件的内容拷贝一份生成data2.txt的代码
#include<stdio.h>
int main() {
FILE* pfread = fopen("data.txt", "r");
if (pfread == NULL) {
perror("fopen");
return 1;
}
FILE* pfwrite = fopen("data_copy.txt", "w");
if (pfread == NULL) {
fclose(pfwrite);
pfwrite = NULL;
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pfread)) != EOF)
fputc(ch, pfwrite);
fclose(pfread);
pfread = NULL;
fclose(pfwrite);
pfwrite = NULL;
return 0;
}
六.文件的随机读写
1.fseek
(1)根据文件指针的位置和偏移量定位文件指针(文件内容的光标)
(2)格式:int fseek(FILE* stream,long int offset,int origin);
origin的可能值 | 含义 |
SEEK_SET | 文件开头(beginning of file) |
SEEK_END | 文件结尾(end of file*) |
SEEK_CUR | 文件指针当前位置(current position of the line pointer) |
(3)举例
#include<stdio.h>
int main() {
FILE* pf = fopen("test.txt", "r");
if (!pf) {
perror("fopen");
return 1;
}
int ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
fseek(pf, -4, SEEK_CUR);
fseek(pf, -6, SEEK_END);
fseek(pf, 0, SEEK_SET);
ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
2.ftell
(1)返回指针相对于起始位置的偏移量
(2)格式:long int ftell(FILE* stream);
(3)举例://计算数组的大小
#include<stdio.h>
int main() {
FILE* pf;
long size;
pf = fopen("test.txt", "rb");
if (!pf) {
perror("fopen");
return 1;
}
fseek(pf, 0, SEEK_END);
size = ftell(pf);
printf("size of the file test.txt is %ld bytes\n", size);
return 0;
}
3.rewind
(1)让文件指针的位置回到文件的起始位置
(2)格式:void rewind(FILE* stream);
(3)举例://打印字母表
#include<stdio.h>
int main() {
int n;
FILE* pf;
char buffer[27];
pf = fopen("test.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, pf);
rewind(pf);
fread(buffer, 1, 26, pf);
fclose(pf);
buffer[26] = '\0';
printf(buffer);
fclose(pf);
pf = NULL;
return 0;
}
七.文件读取结束的判定
被错误使用的feof
1.牢记:在文件读取过程中,不能用feof函数的返回值直接判断文件是否结束
2.feof的作用:当文件读取结束时,判断读取结束的原因是否是:遇到文件末尾结束
3.举例:
(1)文本文件结束,判断返回值是否为EOF/NULL;fgetc判断是不是EOF,fgets判断是不是NULL
#include<stdio.h>
int main() {
int c;//int ,非char要求处理EOF
FILE* pf = fopen("test.txt", "r");
if (!pf) {
perror("fopen");
return 1;
}
//fgetc读取失败/遇到文件结束,返回EOF
while ((c = fgetc(pf)) != EOF)//标准C,I/0读取文件循环
putchar(c);
//判断结束原因
if (ferror(pf))
puts("I/0 error when reading\n");
else
puts("End of file reached successfully\n");
fclose(pf);
pf = NULL;
return 0;
}
(2)二进制文件的读取结束,判断返回值是否小于实际要读的个数
#include<stdio.h>
enum{SIZE=5};
int main() {
double a[SIZE] = { 1.,2.,3.,4.,5. };
FILE* pf = fopen("test.txt", "wb");
if (!pf) {
perror("fopen");
return 1;
}
fwrite(0, sizeof * a, SIZE, pf);
fclose(pf);
pf = NULL;
double b[SIZE];
pf = fopen("test.bin", "rb");
size_t ret_code = fread(b, sizeof * b, SIZE, pf);
if (ret_code == SIZE) {
puts("Array read successfully contents\n");
for (int n = 0; n < SIZE; n++)
printf("%f ", b[n]);
putchar('\n');
}
else {
if (feof(pf))
printf("Error reading test.bin:unexcepted end of file\n");
else if (ferror(pf))
printf("Error reading test.bin\n");
}
fclose(pf);
pf = NULL;
return 0;
}
八.文件缓冲区
ANSIC标准采用缓冲文件系统处理数据;
缓冲文件系统就是系统自动地在内存中为每一个正在使用的文件开辟一块文件缓冲区,从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上,如果磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等);
缓冲区的大小根据c编译系统决定;
#include<stdio.h>
#include<windows.h>
//VS202,WIN11环境测试
int main() {
FILE* pf = fopen("test.txt", "w");//先将代码放在输出缓冲区
fputs("abcdef", pf);
printf("睡眠10秒-已经改写数据了,打开test.txt文件发现没有内容\n");
sleep(10000);
//fflush在高版本的VS上不能使用了
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
printf("再睡眠10秒-此时打开test.txt文件发现有内容\n");
sleep(10000);
fclose(pf);//fclose在关闭文件的时候也会刷新缓冲区
pf = NULL;
return 0;
}
总结:因为有缓冲区的存在,C语言在操作文件时,需要在刷新缓冲区域或文件操作结束时关闭文件;若不做,可能导致读写文件的问题