- 一、为什么使用文件
- 二、什么是文件
- 2.1 程序文件
- 2.2 数据文件
- 2.3 文件名
- 三、文件的打开和关闭
- 3.1 文件指针
- 3.2 文件的打开与关闭
- 四、文件的顺序读写
- fputc
- fputs
- fgetc
- fgets
- fscanf 格式化输入函数
- fprintf 格式化输出函数
- fwrite 二进制输出
- fread 二进制输入
- sprintf函数
- sscanf函数
- 五、文件的随机读写
- 5.1 fseek
- 5.2 ftell
- 5.3 rewind
- 六、文本文件和二进制文件
- 七、文件读取结束的判定
- 7.1 被错误使用的 feof
- 八、文件缓冲区
我们平时写的程序,都是加载到内存中,当下一次再运行时,数据又全部丢失了,这并不是我们期望看到的!
所以要将数据放在文件中,存储在磁盘上,达到持久化的效果!
磁盘上的文件就是文件
在程序设计中:文件分为程序文件和数据文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)
2.2 数据文件 文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件
在以前所处理数据的输入输出都是以终端为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。
一个文件的唯一标识,以便用户识别和使用
文件名包含3部分:文件路径+文件名主干+文件后缀
例如:
C:\code\test.txt
为了方便起见,文件标识常被称为文件名
最关键的 *** 作文件的三个步骤:
- 文件的打开
- 读/写文件
- 关闭文件
在文件的打开过程中,每个被使用的文件都会在内存中开辟一个相应的文件信息区,用来存放文件的相关信息。这些信息被保存在一个结构体变量中。该类型是由系统声明的,取名FILE
。
先来看看VS中提供的stdio.h头文件中有以下文件类型声明:
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又是指向该文件信息区的,也就是说,我们可以通过文件指针变量pf能够找到与它关联的文件data.txt
最主要的两个函数fopen
和fclose
,f代表file文件的意思,让我们能够见名知意。
在编写程序的时候,在打开文件的同时,都会返回一个FILE*
的指针变量,指向改文件,也相当于建立了指针和文件之间的联系,即可通过pf *** 纵文件。
函数名称 | fopen |
---|---|
功能 | 打开一个文件 |
头文件 | #include |
函数原型 | FILE *fopen( const char *filename, const char *mode ); |
参数 | 第一个参数:要打开文件的名称,用双引号括起来,所以传入参数为指针类型, 默认是当前路径,也可以传入绝对路径 第二个参数:打开文件的方式 |
返回值 | 返回一个即将打开文件的文件信息区的指针 |
常用的打开方式:
文件打开方式 | 含义 | 如果指定文件不存在 |
---|---|---|
"r"只读 | 为了输入数据,打开一个已经存在的文本文件 | 出错 |
"w"只写 | 为了输出数据,打开一个文本文件 如果原来的文件中已经有内容,那么下次以"w"方式打开,原来的内容会被清空 | 新建一个文件 |
"a"追加 | 向文本文件末尾追加数据 | 新建一个文件 |
rb只读 | 为了输入数据,打开一个二进制文件 | 出错 |
wb只写 | 为了输出数据,打开一个二进制文件 | 建立新文件 |
ab追加 | 向一个二进制文件尾追加数据 | 出错 |
r+ | 为了读和写,打开一个文本文件 | 出错 |
w+ | 为了读和写,新建一个文件 | 新建一个文件 |
a+ | 打开一个文件,在文件尾进行读写 | 新建一个文件 |
rb+ | 为了读和写,打开一个二进制文件 | 出错 |
wb+ | 为了读和写,新建一个二进制文件 | 新建一个文件 |
ab+ | 打开一个二进制文件,在文件尾进行读和写 | 新建一个文件 |
函数名称 | fclose |
---|---|
函数功能 | 关闭一个文件 |
头文件 | #include |
函数原型 | int fclose( FILE *stream ); |
参数 | 要关闭的文件的文件指针 |
返回值 | 成功关闭则返回0 |
举个例子:
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt","w");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//读写文件
//...
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
四、文件的顺序读写
首先理解一下输入输出:我们讨论的主语是“数据”,都是以它为中心展开的 *** 作。
函数名 | 功能 | 适用于 |
---|---|---|
字符输入函数 | fgetc | 所有输入流 |
字符输出函数 | fputc | 所有输出流 |
文本行输入函数 | fgets | 所有输入流 |
文本行输出函数 | fputs | 所有输出流 |
格式化输入函数 | fscanf | 所有输入流 |
格式化输出函数 | fprintf | 所有输出流 |
二进制输入 | fread | 文件 |
二进制输出 | fwrite | 文件 |
写入数据举例
fputc单个字符写入:
#include
int main()
{
//打开文件
FILE* pf = fopen("data.txt","w");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//读写文件
fputc('h', pf);
fputc('e', pf);
fputc('l', pf);
fputc('l', pf);
fputc('o', pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
我们打开我们的data.txt文件可以观察一下:
行字符写入:
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//写文件
fputs("six\n",pf);//可以加入换行
fputs("six\n", pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果展示:
流——一种高度抽象的概念
生活中存在多种外部设备,比如:文件、屏幕、磁盘等等,每种设备输入输出数据的方式都不一样,为了统一,我们用“流”的方式,来输入输出数据。我们只需要关注流就可以了,不需要关注每种外设的读写,因为底层都是大同小异,经过封装之后显得不同而已,同时也减少了程序猿的负担。
C语言程序只要运行起来, *** 作系统就默认打开了三个“流”:
分别对应的物理设备有这些
标准输出流:stdout
->屏幕
标准输入流:stdin
->键盘
标准错误流:stderr
->屏幕
它们三个的类型都是FILE*
所以我们可以把信息打印到屏幕上:
//也可以直接输出到屏幕上
int main()
{
//打印在屏幕上
fputc('h', stdout);
fputc('e', stdout);
fputc('l', stdout);
fputc('l', stdout);
fputc('o', stdout);
printf("\n");
return 0;
}
读取数据举例
单个字符读取:
//读取数据
int main()
{
//打开文件
FILE* pf = fopen("data.txt","r");//修改为r,只读的方式
if (pf == NULL)
{
perror("fopen");
return -1;
}
//读写文件
char ch = fgetc(pf);
printf("%c\n",ch);
ch = fgetc(pf);
printf("%c\n", ch);
ch = fgetc(pf);
printf("%c\n", ch);
//将结果打印打屏幕上面去
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果展示:
行字符读取:
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//写文件
char arr[20] = { 0 };
fgets(arr,5,pf);
printf("%s\n",arr);
fgets(arr, 5, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
奇怪的现象:
奇怪的现象:就是本来读取5个字符,但是却只打印了4个?
原因:我们第五个位置默认填上\0,所以相当于只有4个字符被读了
ps:原来的arr被第二次的读取给覆盖了!
它只不过比scanf多了一个文件指针参数
//函数原型
int fscanf( FILE *stream, const char *format [, argument ]... );
//fscanf
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//写文件
int a =0;
double d =0;
fscanf(pf, "%d %lf", &a, &d);
printf("%d\n",a);
printf("%lf\n",d);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果展示:
它只不过比printf多了一个文件指针参数
//函数原型
int fprintf( FILE *stream, const char *format [, argument ]...);
//fscnaf
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "w");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//写文件
int a = 100;
double d = 3.14;
fprintf(pf, "%d %lf", a, d);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
上面我们展示的全部都是文本文件,数据文件分为文本文件和二进制文件
文本文件:就是我们能够看懂的
二进制文件:就是计算机能够看懂的,我们在电脑上显示出来是看不懂的
下面来介绍一下二进制文件的读写:
//函数原型
size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream );
fwrite 二进制输出
//fwrite 二进制写
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,4,5,pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果展示:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream );
虽然二进制文件我们看不懂,但是可以把刚刚写入的数据读出来
//fread 二进制写
int main()
{
//打开文件
FILE* pf = fopen("data.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return -1;
}
int buf[20] = { 0 };
fread(buf,sizeof(buf),1,pf);
for (int i = 0; i < sizeof(buf) / sizeof(int); i++)
{
printf("%d ",buf[i]);
}
printf("\n");
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果展示:
//sprintf
struct S
{
int a;
double d;
char name[10];
};
int main()
{
char arr[100] = { 0 };
struct S s = { 100, 3.14,"zhangsan" };
//把一个格式化的数据转换为字符串
sprintf(arr,"%d %lf %s",s.a,s.d,s.name);
printf("%s\n",arr);
return 0;
}
sscanf函数
//sscanf
struct S
{
int a;
double d;
char name[10];
};
int main()
{
char arr[100] = { 0 };
struct S s = { 100, 3.14,"zhangsan" };
//把一个格式化的数据转换为字符串
sprintf(arr, "%d %lf %s", s.a, s.d, s.name);
printf("%s\n", arr);
struct S tmp = { 0 };
//把一个字符串的数据(arr中)转换为格式化的数据
sscanf(arr, "%d %lf %s", &(tmp.a), &(tmp.d), tmp.name);
printf("%d %lf %s\n",tmp.a,tmp.d,tmp.name);
return 0;
}
一共有两次打印,第一次打印是按照字符串打印的,第二次是按照结构体数据化的格式打印的。
对比这两组函数:
输入
函数名称 | 功能 |
---|---|
scanf | 从标准输入流(键盘)读取格式化的数据 |
fsacnf | 从所有的输入流读取格式化的数据 |
sscanf | 从字符串中读取一个格式化的数据 或者说把一个字符串转换成一个格式化的数据 |
输出
函数名称 | 功能 |
---|---|
printf | 把格式化的数据输出到标准输出(屏幕)上 |
fprintf | 把格式化的数据输出到所有输出流(屏幕/文件)上 |
sprintf | 把格式化的数据转换成对应的字符串 |
根据文件指针的位置和偏移量来定位文件指针
//函数原型
int fseek( FILE *stream, long offset, int origin );
函数名称 | fseek |
---|---|
函数功能 | 新定位文件指针的位置 |
头文件 | #include |
函数原型 | int fseek( FILE *stream, long offset, int origin ); |
参数 | 第一个参数:文件指针 第二个参数:偏移量(这个要看origin) 第三个参数:起始位置(设置了三个宏 SEEK_CUR SEEK_END SEEK_SET) |
返回值 | 如果成功,fseek 返回 0。否则,它将返回非零值。 |
int main()
{
FILE* pf = fopen("data.txt","r");//先人为写入hello数据
if (pf == NULL)
{
perror("fopen");
return -1;
}
//随机读写
fseek(pf,4,SEEK_CUR);//定位文件指针
int ch=fgetc(pf);//读取偏移量为4的字符
printf("%c\n",ch);
fclose(pf);
pf = NULL;
return 0;
}
结果展示:
返回文件指针相对于起始位置的偏移量
//函数原型
long ftell( FILE *stream );
int main()
{
FILE* pf = fopen("data.txt","r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//随机读写
fseek(pf,4,SEEK_CUR);//定位文件指针
int ch=fgetc(pf);//读取偏移量为4的字符
printf("%c\n",ch);
int a=ftell(pf);//返回偏移量
printf("%d\n",a);
fclose(pf);
pf = NULL;
return 0;
}
5.3 rewind
让文件指针的位置回到起始位置
void rewind( FILE *stream );
int main()
{
FILE* pf = fopen("data.txt","r");
if (pf == NULL)
{
perror("fopen");
return -1;
}
//随机读写
fseek(pf,4,SEEK_CUR);//定位文件指针
int ch=fgetc(pf);//读取偏移量为4的字符
printf("%c\n",ch);
int a=ftell(pf);//返回相对于起始位置的偏移量
printf("%d\n",a);
rewind(pf);//让文件指针回到起始位置
a = ftell(pf);//返回相对于起始位置的偏移量
printf("%d\n", a);
fclose(pf);
pf = NULL;
return 0;
}
六、文本文件和二进制文件
根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在内存中是怎么存储的呢?
字符一律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储
如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4个字节
七、文件读取结束的判定 7.1 被错误使用的 feof字符1的ASCII码49,对应的码值是00110001
字符0的ASCII码是48,对应的码值是00110000
牢记:在文件读取过程中,不能使用feof的返回值来直接判断文件是否结束
而是用于当文件读取结束时,判断文件结束的原因:是读取失败(NULL),还是遇到EOF文件尾结束。
1.文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
- fgetc 判断是否为 EOF .
- fgets 判断返回值是否为 NULL .
2.二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
- fread判断返回值是否小于实际要读的个数
#include
#include
int main(void)
{
int c; // 注意:int,非char,要求处理EOF
FILE* fp = fopen("test.txt", "r");
if (!fp) {
perror("File opening failed\n");
return EXIT_FAILURE;
}
//fgetc 当读取失败的时候或者遇到文件结束的时候,都会返回EOF
while ((c = fgetc(fp)) != EOF) // 标准C I/O读取文件循环
{
putchar(c);
}
printf("\n");
//判断是什么原因结束的
if (ferror(fp))
puts("I/O error when reading\n");
else if (feof(fp))
puts("End of file reached successfully\n");
fclose(fp);
}
八、文件缓冲区
ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。
谢谢观看!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)