1、为什么使用文件?
我们前面学习结构体时,编写了通讯录的项目,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又要重新录入,如果使用这样的通讯录是非常难受的。
我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。而文件可以解决这个麻烦的问题。使用文件我们可以将数据直接存放在电脑上,做到了数据持久化。
2、什么是文件?
磁盘上的文件就是文件。
但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类)。
2.1 程序文件
包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
2.2 数据文件
文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
在以前的程序所处理的数据的输入输出都是以终端为对象,即从终端的键盘输入数据,运行结果显示到显示器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘的文件了。
2.3 文件名
一个文件要有一个唯一的文件标识,以便用户识别和引用。文件名包含3部分:文件路径+文件名主干+文件后缀例如:c:\code\test.txt,为了方便起见,文件标识被称为文件名。
3、文件的打开和关闭
3.1 文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。
例如,VS2013编译环境提供的stdio.h头文件中有以下的文件类型声明:
struct _iobuf
{
char *_ptr;
int _cnt;
char* _base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
}
typedef struct _iobuf FILE;
不同的C语言编译器的FILE类型包含的内容不完全相同,但是大同小异。
每打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节。
一般都是通过一个FILE的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:
FILE* pf;//文件指针变量
定义pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
比如:
当需要维护文件时,使用FILE类型的指针可以去访问维护。
3.2 文件的打开和关闭
文件打开的函数:
FILE * fopen ( const char * filename, const char * mode ); //filename-文件名,mode-文件打开模式
使用方式:
//文件的打开
int main()
{
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt","r"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读文件
//关闭文件
fclose(p);
p = NULL;
return 0;
}
4、文件的顺序读写
4.1 字符输出函数(fputc)
int fputc ( int character, FILE * stream ); //character-写入字符 stream-要写如的流的文件指针
fputc的使用:
int main()
{
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "w"); //打开文件,读取文件的方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读文件
int i = 0;
while (i < 5) {
fputc('a',p); //写入字符
i++;
}
//关闭文件
fclose(p);
p = NULL;
return 0;
}
效果:
4.2 字符输入函数(fgetc)
int fgetc ( FILE * stream ); //stream-读取的流的文件
fgetc的使用:
int main()
{
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "r"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读文件
int c = fgetc(p); //读一个字符
printf("%c", c);
//关闭文件
fclose(p);
p = NULL;
return 0;
}
效果:
4.3 字符串输出函数(fputs)
int fputs ( const char * str, FILE * stream ); //str-写入的字符串,stream-写入流的文件
fputs的使用:
int main()
{
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "a"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//写文件
fputs("hello world",p);
//关闭文件
fclose(p);
p = NULL;
return 0;
}
效果:
4.4 字符串输入函数(fgets)
char * fgets ( char * str, int num, FILE * stream ); //str-存放读取的数据,num-读取的个数包括结束符,stream-读取的字符串的文件。
fgets的使用:
int main()
{
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "r"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读取文件中的字符串
char arr[10];
fgets(arr,10,p); //读取10个字符,包括结束符
printf("%s",arr);
//关闭文件
fclose(p);
p = NULL;
return 0;
}
效果:
4.5 字符格式化输出函数(fprintf)
int fprintf ( FILE * stream, const char * format, ... ); //stream-输入的文件,format-格式,...参数
fprintf的使用:
struct S
{
char name[20];
int age;
float score;
}s;
int main()
{
struct S s = {"朱保利",17,68.8f};
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "w"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读取文件中的字符串
fprintf(p,"%s %d %.2f",s.name,s.age,s.score); //格式化写入结构体成员
//关闭文件
fclose(p);
p = NULL;
return 0;
}
效果:
4.5.1 流的概念
流的概念:
FILE* 类型,输入输出的缓冲区
printf 输出
scanf 输入
任何一个C语言,只要运行起来就会默认打开3个流:
FILE* stdin - 标准输入流(键盘)
FILE* stdout - 标准输出流(屏幕)
FILE* stderr - 标准错误流(屏幕)
文件操作函数:
特别的使用方法:
struct S
{
char name[20];
int age;
float score;
}s;
int main()
{
struct S s = {"朱保利",17,68.8f};
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "r"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读取文件中的字符串
fscanf(p, "%s %d %.2f", s.name, &(s.age), &(s.score));
fprintf(stdout,"%s %d %.2f",s.name,s.age,s.score);
//关闭文件
fclose(p);
p = NULL;
return 0;
}
4.6 字符格式化输入函数(fscanf)
int fscanf ( FILE * stream, const char * format, ... ); //stream-输入的文件,format-格式,...-参数
fscanf的代码:
struct S
{
char name[20];
int age;
float score;
}s;
int main()
{
struct S s = {"朱保利",17,68.8f};
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "r"); //打开文件,读取方式
if (p == NULL)
{
printf("%s", strerror(errno));
return 1;
}
//...
//读取文件中的字符串
fscanf(p, "%s %d %.2f", s.name, &(s.age), &(s.score));
fprintf(stdout,"%s %d %.2f",s.name,s.age,s.score);
//关闭文件
fclose(p);
p = NULL;
return 0;
}
4.7 文件二进制输入和输出
输入:
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
参数:
- ptr:指向缓冲区的指针,用于存储读取的数据。
- size:要读取的每个元素的大小(以字节为单位)。
- nmemb:要读取的元素数量。
- stream:指向要读取的文件流的指针。
返回值:
- 成功读取的元素数量。如果达到文件末尾,则返回 0。
fread的使用:
int main()
{
char str[20] = {0};
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt","r");
if (p == NULL)
{
printf("文件打开错误\n");
return 1;
}
else
printf("文件打开正确\n");
int len = fread(str,sizeof(str[0]),10,p);
if (len != 10)
printf("输入错误");
else
{
int i = 0;
for (i = 0; i < len; i++)
{
printf("%c ",str[i]);
}
}
return 0;
}
输出:
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
参数:
- ptr:指向要写入数据的缓冲区的指针。
- size:要写入的每个元素的大小(以字节为单位)。
- nmemb:要写入的元素数量。
- stream:指向要写入的文件流的指针。
返回值:
- 成功写入的元素数量。如果达到文件末尾,则返回 0。
fwrite的使用:
int main()
{
char str[10] = { 1,2,3,4,5,6,7,8,9,10 };
FILE* p = fopen("C:\\Users\\1\\Desktop\\test.txt", "w");
if (p == NULL)
{
printf("文件打开错误\n");
return 1;
}
else
printf("文件打开正确\n");
int len = fwrite(str, sizeof(str[0]), 10, p);
if (len != 10)
printf("输入错误");
else
{
int i = 0;
for (i = 0; i < len; i++)
{
printf("%d ", str[i]);
}
}
return 0;
}
效果:
存放的是二进制的东西看不懂很正常。
5、对比函数
分析成对的对应函数的区别和功能。
scanf 是针对标准输入的格式化输入语句
printf 是针对标准输出的格式化输出语句
fscanf 是针对所有输入流的格式化输入语句
fprintf 是针对所有输出流的格式化输出语句
sscanf 是从一个字符串中转化为一个格式化的数据
sprintf 是把一个格式化的数据转化成字符串
sscanf:
int sscanf ( const char * s, const char * format, ...);
参数:
- str:要从中读取数据的字符串。
- format:一个格式字符串,指定如何解释字符串中的数据。
- ...:要存储读取数据的变量的地址列表。
返回值:
- 成功读取的赋值数。如果未读取任何数据,则返回 0。
sprintf:
int sprintf ( char * str, const char * format, ... );
其中:
- str:要写入格式化数据的目标字符串。
- format:一个格式化字符串,指定如何格式化数据。
- ...:可变数量的参数,这些参数将根据格式化字符串中的占位符进行格式化并写入字符串中。
返回值:
sprintf 函数返回写入字符串中的字符数(不包括终止空字符 '\0')。如果写入失败,则返回一个负值。
sscanf和sprintf的使用:
//sscanf和sprintf的使用
typedef struct S
{
char name[20];
int age;
float score;
}s;
int main()
{
s s1 = {"zhubaoli",17,99.9f};
char buf[100];
//把结构体s1的格式化数据转化成字符串放到buf中
sprintf(buf,"%s %d %.2f",s1.name,s1.age,s1.score);
printf("%s\n",buf);
s s2 = {0};
//把buf字符串转化为格式化数据放到s2结构体中
sscanf(buf,"%s %d %.2f",s2.name,&(s2.age), &(s2.score));
printf("%s %d %.2f",s2.name,s2.age,s2.score);
return 0;
}