文章目录
- 1. 什么是文件?
- (1. 为什么使用文件?
- (2.什么是文件
- 2.1 程序文件
- 2.2 数据文件
- 2.3 文件名
- 2. 二进制文件和文本文件?
- 3. 文件的打开和关闭
- 3.1 流和标准流
- 3.2 文件指针(用来管理流的)
- 3.3 文件的打开与关闭
- 4. 文件的顺序读写
- 顺序读写函数介绍
- 4.1 fputc(字符输出函数)
- 4.2 fgetc(字符输入函数)
- 4.3 fputs(文本输出函数)
- 4.4 fgets(文本行输入函数)
- 4.5 fprintf(格式化输入函数)
- 4.6 fscanf(格式化输出函数)
- 对比一组函数
- 4.7 fwrit
- 4.7 fread
- 5. 文件的随机顺序读写
- 5.1 fseek
- 5.2 ftell
- 5.3 rewind
- 6. 文件读取结束的判定
- 7. 文件缓冲区
1. 什么是文件?
(1. 为什么使用文件?
int a = 0;
printf("a=%d\n", a);
scanf("%d\n", &a);
printf("a=%d\n", a);
在上面的代码中,运行一次之后,下次运行,a仍然先显示的是0。为什么还是0呢?因为我们这些数据是放在内存里的。
如果没有文件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失了,等再次运行程序,是看不到上次程序的数据的,如果要将数据进行持久化的保存,我们可以使用文件。
那为什么我们制作的表格和ppt能够一直存在?因为这些是放在电脑的硬盘上。
我们发现,数据放在文件中,文件再放在硬盘中,就可以将数据持久化的保存。
(2.什么是文件
磁盘(硬盘)上的文件是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。
2.1 程序文件
程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
2.2 数据文件
文件的内容不一定是程序,还有可能是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
本章讨论的是数据文件。
在以前各章所处理数据的输入输出都是以终端(就是vs的黑框框,屏幕)为对象的,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件
2.3 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径,文件名主干,文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。
2. 二进制文件和文本文件?
(1)根据数据的组织形式(即内容),数据文件被称为文本文件
或者二进制文件
。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
(2)一个数据在文件中是怎么存储的呢?
- 字符一律以
ASCII形式存储
, - 数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
如有整数10000
,如果以ASCII
码的形式输出到磁盘,则磁盘中占用5
个字节(每个字符一个字节),而二进制形式输出,则在磁盘上只占4
个字节。
3. 文件的打开和关闭
3.1 流和标准流
- 流
我们程序的数据需要输出到各种外部设备(文件,U盘,屏幕),也需要从外部设备获取数据。不同的外部设备的输入输出操作各不相同,为了方便程序员对各种设备进行方便的操作,我们抽象出了“流”的概念,我们可以把流想象成河(流淌着字符的河)。对于程序员来说,只要关注“流”就可以了。
C程序针对文件、画面、键盘等的数据输入输出操作都是通过流操作的。
一般情况下,我们要想 向“流”里写数据 或 从“流”中读取数据,都是(1)打开流,(2)操作[读 / 写]。(3)关闭流。
- 标准流
为什么我们从键盘输入数据(scanf),向屏幕上输出数据,并没有打开流呢?
那是因为C语言程序在启动的时候,默认打开3个流:
stdin
- 标准输入流,在大多数的环境中从键盘输入,scanf
函数就是从标准输入流中读取数据。(记忆方式:键盘从我们这里读取到了即将输入的数据)
stdout
- 标准输出流,大多数的环境中输出至显示器界面,printf
函数就是将信息输出到标准输出流中。
stderr
- 标准错误流,大多数环境中输出到显示器界面。
这是默认打开了这三个流,我们使用scanf、printf等函数就可以直接进行输入输出操作的。
stdin、stdout、stderr
这三个标准流的类型是:FILE *
,通常称为文件指针(指向文件信息区)。
C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。
3.2 文件指针(用来管理流的)
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区(每个文件都有文件信息区),用来存放 文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名 FILE。
例如,VS2013 编译环境提供的 stdio.h
头文件中有以下的文件类型申明(不同的C编译器的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*
的指针变量:FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf(FILE* 文件指针)指向某个文件的文件信息区
(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过(文件指针变量)能够间接找到(与它关联的文件)。
3.3 文件的打开与关闭
- 在之后经常说,读和写,那什么是读和写呢?
- 打开文件
ANSIC
规定使用fopen
函数来打开文件,fclose
来关闭文件。
//打开文件 文件名 打开方式
函数原型:FILE * fopen ( const char * filename, const char * mode );
//返回类型是文件指针FILE*
FILE* PF=fopen("test.txt","w");
在编写程序的时候,在打开文件的同时,都会返回一个FILE*
的指针变量(文件信息的地址)指向该文件,也相当于建立了指针和文件的关系。
如果文件打开成功,返回的是有效指针
如果文件打开失败,则返回NULL(是空指针的话,很危险,要记得判断)
’‘w’‘(写)的打开方式:
如果没有我们想打开的文件data.txt,而我们fopen这个文件,它会在这个项目下创立一个data.txt
如果有这个文件,不会新建。它不管里面有什么,它会将文件清空
“r”只读的打开方式:不会修改
- 打开的地点
第一种就在当前项目下:FILE* fopen ( test.txt,"w" );
第二种是打开桌面上的文件:这个需要使用绝对路径(右击文件,点击属性):路径\名字(双斜杠,防止变成转义字符)
FILE* fopen("C:\\Users\\20439\\Desktop\\wen.docx","w");
第三种:当前项目的上一级(相对路径)
.表示当前路径,…表示上一级路径
.\\..
表示这一级的上一级
.\\..\\..
表示这一级的上一级的上一级
FILE* fopen(".\\..\\dao.docx","w");
-
打开方式有哪些?
-
关闭文件
int fclose(FILE* stream);
在关闭文件之后还需要置为空指针
#include<stdio.h>
int main()
{
FILE* pf = fopen("data.text", "w");//打开文件,返回的是有效指针
//是否打开
if (pf == NULL)
{
perror("fopen");
return 1;
}
//.....
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
4. 文件的顺序读写
顺序读写函数介绍
4.1 fputc(字符输出函数)
fputc(只能写一个字符)
int fputc ( int character, FILE * stream );
参数说明:
character
:要写入的字符,该字符会被转换为unsigned char
类型,然后写入文件流。
stream
:指向要写入的文件流的指针。
返回值:
如果成功,fputc
返回写入的字符。
如果发生错误,fputc
返回EOF
(End Of File
,通常定义为-1
)。
1.黑框框里没有内容,说名打开文件没有问题
2.右击文件,点击“打开所在的文件夹”,点开刚刚打开以及输入的文件
3. 如果想输入很多个字符可以使用循环
4.2 fgetc(字符输入函数)
fgetc
int fgetc ( FILE * stream );
参数说明:
stream
:指向要从中读取字符的文件流的指针。
返回值:
如果成功, fgetc 返回读取的字符的ASCII
码(一个unsigned char
类型的值,被转换为int
类型)。
如果到达文件末尾或发生错误,fgetc
返回EOF
读取一次,光标就会往后移动一格
int main()
{
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//写文件
int i = 0;
for (i = 'a'; i < 'z'; i++)
fputc(i, pf);
//在读取之前要关闭写句柄
fclose(pf);
pf = NULL;
//简单一点,用循环
FILE* pf1 = fopen("test.txt", "r");
if (pf1 == NULL)
{
perror("fopen");
return 1;
}
int ch = 0;
while ((ch = fgetc(pf1)) != EOF)
printf("%c ", ch);
//读文件
FILE* pf1 = fopen("test.txt", "r");
int ch = fgetc(pf1);
printf("%c\n", ch);
ch = fgetc(pf1);
printf("%c\n", ch);
ch = fgetc(pf1);
printf("%c\n", ch);
ch = fgetc(pf1);
printf("%c\n", ch);
ch = fgetc(pf1);
printf("%c\n", ch);
//关闭
fclose(pf1);
pf1 = NULL;
}
stdin----输入流------即读的形式打开文件-----文件的输入流
stdout–输出流------即写的形式打开文件-----文件的输出流
int main()
{
//fgetc读字符,去哪里读呀?()需要的是流
int ch = fgetc(stdin);//从键盘(标准输入流)上读取
//fputc打印,第一个参数字符,第二个参数流
fputc(ch, stdout);//将字符输出(写)到屏幕(标准输出流)
return 0;
}
4.3 fputs(文本输出函数)
记得先打开文件(打开方式:写),才能在流里写字符串
fputs(把字符串写到一个流里)
返回类型int----字符串的地址-------文件指针(关联到一个文件)
函数原型:int fputs ( const char * str, FILE * stream );
参数说明:
str
:指向要写入的字符串的指针。
stream
:指向要写入的文件流的指针。
返回值:
如果成功,fputs
返回一个非负整数。
如果发生错误,fputs
返回 EOF (通常定义为 -1 )
如果写两行fputs,这两次的内容会放在同一行,因为我们没有加换行符\n
4.4 fgets(文本行输入函数)
打开方式:读
fgets
char * fgets ( char * str, int num, FILE * stream );
参数说明:
str
:指向用于存储读取数据的字符数组的指针。
num
:指定要读取的最大字符数,包括字符串结束符 \0 。(实际读取了num-1个字符,因为最后一个是\0)
stream
:指向要读取的文件流的指针。
返回值(接收与否均可):
如果成功,fgets
返回指向str
的指针。
如果到达文件末尾或发生错误,fgets
返回 NULL 。
如果文件的内容有好几行,fgets只读取一行,\n也读取了,之后就结束了
int main()
{
//打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//读文件
char arr[20] = { "xxxxxxxxxx" };
fgets(arr, 5, pf);
printf("%s\n", arr);
//关闭文件
fclose(pf);
pf = NULL;
}
我们将num改成10,再将文件的内容改成换行的:
int main()
{
char arr[20] = { 0 };
fgets(arr, 20, stdin);
fputs(arr, stdout);
return 0;
}
4.5 fprintf(格式化输入函数)
对比printf和fprintf
printf("%s %d\n", a, b);
//是将内容输出到屏幕上
fprintf(pf,"%s %d",a, b);
//是将内容写在文件里
fprintf
int fprintf ( FILE * stream, const char * format, ... );
int printf ( const char * format, ... );
参数说明:
stream
:指向要写入的文件流的指针。
format
:一个字符串,包含格式说明符和普通字符。格式说明符用于指定如何格式化后续的参数。
...
:可变参数列表,包含要写入的值,这些值将根据format
字符串中的格式说明符进行格式化。
fprintf 返回值:
如果成功,fprintf
返回写入的字符数。
如果发生错误,fprintf
返回一个负值。
printf 返回值:
printf
返回实际写入到标准输出的字符数。
int main()
{
char name[] = "houpp";
int age = 20;
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf, "%s %d\n", name,age);//内容写入文件
fclose(pf);
pf = NULL;
}
4.6 fscanf(格式化输出函数)
fscanf
int fscanf ( FILE * stream, const char * format, ... );
fscanf返回值:
fscanf
返回成功解析并赋值的输入项的数量。如果到达文件末尾或发生错误,则返回EOF
(End Of File
,通常定义为-1
)。
scanf返回值
scanf
返回成功解析并赋值的输入项的数量。如果到达输入流的末尾或发生错误,则返回EOF
(End Of File
,通常定义为-1
)
int main()
{
char name[] = "houpp";
int age = 20;
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
//从文件中读取内容
fscanf(pf, "%s %d", name, &age);
//打印在屏幕上
printf("%s %d", name, age);
//fprintf(pf, "%s %d\n", name,age);//内容写入文件
fclose(pf);
pf = NULL;
}
对比一组函数
scanf/fscanf/sscanf
printf/fprintf/sprintf
scanf/printf
:针对 标准输入流/标准输出流 的 格式化输入/输出函数
fscanf/fprintf
:针对 所有输入流/输出流 的 格式化输入/输出函数(也就是可以针对文件也可以针对标准输入/出流)
sscanf/sprintf
:将格式化的数据转换成字符串,或者从字符串中提取格式化的数据
scanf
int scanf ( const char * format, ... );
fscanf
int fscanf ( FILE * stream, const char * format, ... );
sprintf
int sprintf ( char * str, const char * format, ... );
sprintf
实际上就是将格式化的数据写到字符串中,可以理解为将格式化的数据转换成字符串
sprintf
是将格式化的数据放在数组里【格式化的数据---------写到---------->字符串】
int main()
{
char name[] = "houpp";
int age = 20;
FILE* pf = fopen("test.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
char arr[100] = { 0 };
sprintf(arr, "%s %d", name, age);
//看一下数组里有没有内容了
printf("%s\n", arr);
fclose(pf);
pf = NULL;
}
struct S {
char name[20];
int age;
float score;
};
int main() {
char arr[100] = { 0 };
struct S s = { "wangwu",23,66.6f };
//printf("%s %d %f", s.name, s.age, s.score);
sprintf(arr, "%s %d %f", s.name, s.age, s.score);
printf("%s\n", arr);
return 0;
}
struct S {
char name[20];
int age;
float score;
};
int main() {
char arr[100] = { 0 };
struct S s = { "wangwu",23,66.6f };
//创建临时变量
struct S tmp = { 0 };
//将S中的各个数据转换成字符串,存放在arr中
sprintf(arr, "%s %d %f", s.name, s.age, s.score);
//从字符串arr中提取格式化的数据,存放在tmp中
//scanf("%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
sscanf(arr, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));
printf("%s %d %f", tmp.name, tmp.age, tmp.score);
return 0;
}
4.7 fwrit
fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数说明:
ptr
:指向要写入数据的内存块的指针。
size
:每个数据项的大小(以字节为单位)。
count
:要写入的数据项的数量。
stream
:指向要写入数据的文件流的指针。
返回值:
fwrite
返回成功写入的数据项的数量。如果发生错误或到达文件末尾,则返回一个小于count
的值。
就是把ptr
指向空间里面,count
个大小为size
个字节的元素的数据,写到stream
里面去。
struct S {
char name[20];
int age;
float score;
};
int main() {
struct S s = { "cuihua", 25, 88.8f };
//以二进制形式写进文件
//1.打开文件
FILE* pf = fopen("test.txt", "wb");
if (pf == NULL) {
perror("fopen");
return 1;
}
//2.写文件
fwrite(&s, sizeof(struct S), 1, pf);
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
打开文件:乱码是因为写的是二进制字符的原因。
4.7 fread
fread
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数说明:
ptr
:指向用于存储读取数据的内存块的指针。
size
:每个数据项的大小(以字节为单位)。
count
:要读取的数据项的数量。
stream
:指向要从中读取数据的文件流的指针。
返回值:
fread
返回成功读取的数据项的数量。如果到达文件末尾或发生错误,则返回一个小于count
的值。
就是把steam
指向空间里面(也就是文件里面读取),count
个大小为size
个字节的元素的数据,写到ptr
里面去。
struct S {
char name[20];
int age;
float score;
};
int main() {
struct S s = { "cuihua", 25, 88.8f };
//读取二进制形式的信息写进文件中
//1.打开文件
FILE* pf = fopen("test.txt", "rb");
if (pf == NULL) {
perror("fopen");
return 1;
}
//2.读文件
fread(&s, sizeof(struct S), 1, pf);
printf("%s %d %f\n", s.name, s.age, s.score);
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
5. 文件的随机顺序读写
5.1 fseek
根据文件指针的位置和偏移量来定位文件指针(文件内容的光标)。
int fseek ( FILE * stream, long int offset, int origin );
参数说明:
stream
:指向要操作的文件流的指针。
offset
:要移动的字节数如果
origin
是SEEK_SET
,则offset
是从文件开始处的偏移量;
如果origin
是SEEK_CUR
,则offset
是从当前文件指针位置的偏移量;
如果origin
是SEEK_END
,则offset
是从文件末尾的偏移量。
origin
:指定offset
的基准位置,可以是以下三个值之一:
SEEK_SET
:从文件的开始位置移动。
SEEK_CUR
:从当前文件指针位置移动。
SEEK_END
:从文件的末尾位置移动。
返回值:
fseek
成功时返回 0 ,失败时返回非零值。
int main() {
//1.打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//2.读文件
int ch = 0;
ch = fgetc(pf);//a
printf("%c\n", ch);
ch = fgetc(pf);//b
printf("%c\n", ch);
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
我们要是想打印abe
呢?
这个时候就要用到fseek
函数了。
int main() {
//1.打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//2.读文件
int ch = 0;
ch = fgetc(pf);//a
printf("%c\n", ch);
ch = fgetc(pf);//b
printf("%c\n", ch);
//定位文件指针
fseek(pf, 4, SEEK_SET);
//fseek(pf, 2, SEEK_CUR);
//fseek(pf, -2, SEEK_END);
ch = fgetc(pf);//e
printf("%c\n", ch);
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
5.2 ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
参数说明:
stream
:指向要查询的文件流的指针。
返回值:
ftell
返回当前文件指针的位置(以字节为单位),如果发生错误,则返回-1L
。
int main() {
//1.打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//2.读文件
int ch = 0;
ch = fgetc(pf);//a
printf("%c\n", ch);
ch = fgetc(pf);//b
printf("%c\n", ch);
//定位文件指针
fseek(pf, 4, SEEK_SET);
ch = fgetc(pf);//e
printf("%c\n", ch);
//输出文件指针相较于文件起始位置的偏移量
printf("%d\n", ftell(pf));
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}
为什么是5呢?
5.3 rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
int main() {
//1.打开文件
FILE* pf = fopen("test.txt", "r");
if (pf == NULL) {
perror("fopen");
return 1;
}
//2.读文件
int ch = 0;
ch = fgetc(pf);//a
printf("%c\n", ch);
ch = fgetc(pf);//b
printf("%c\n", ch);
//定位文件指针
fseek(pf, 4, SEEK_SET);
ch = fgetc(pf);//e
printf("%c\n", ch);
//输出文件指针相较于文件起始位置的偏移量
printf("%d\n", ftell(pf));
//将文件指针重新定位到文件的起始位置
rewind(pf);
ch = fgetc(pf);//a
printf("%c\n", ch);
//3.关闭文件
fclose(pf);
pf = NULL;
return 0;
}