接上文,接下来学习文件的读写操作,不用想的太复杂,其实很简单,就是在学习函数的使用罢了。本文所涉及函数都是根据官网https://cplusplus.com/所得。
文件读/写操作,分为顺序读写和随机读写。
记得进行文件读写操作时,要先用fopen函数打开文件,之后要用fclose函数关闭文件。
我们先来学习文件的顺序读写。
目录
4. 文件顺序读写操作
文件顺序读写,所涉及的函数如下:
函数名 | 功能 | 适用于 |
fgetc | 字符输入函数 | 所有输入流 |
fputc | 字符输出函数 | 所有输出流 |
fgets | 文本行输入函数 | 所有输入流 |
fputs | 文本行输出函数 | 所有输出流 |
fscanf | 格式化输入函数 | 所有输入流 |
fprintf | 格式化输出函数 | 所有输出流 |
fread | 二进制输入 | 文本输入流 |
fwrite | 二进制输出 | 文本输出流 |
这些函数的头文件是<stdio.h>。
大家不要被这么多函数吓到了,其实大同小异,但是有些区别。
4.1 fgetc和fputc函数
这一组的两个函数用于单个字符。
4.1.1 getc函数
fgetc函数是字符输入函数。在使用输入函数时,打开文件用fopen函数,第二个参数要用读"r"。
该函数一般用来读取文件中单个字符。读取完一个字符后,文件指针(文件内容的光标)将会前进到下一个字符。
若读取成功,返回字符的ASCII码值;若读取到文件末尾或者读取失败时,返回EOF.
为什么它返回类型是int呢,是为了兼容EOF(-1)的情况。
我先在文件里放:
很显然刚开始文件指针在最初的位置,使用fgetc函数读一个字符,文件指针就往下一个位置。
运行代码,就会得到:
4.1.2 fputc函数
打开文件用fopen函数,第二个参数要用写"w"。
fputc函数一般作用是向文件写入单个字符。
注意,该函数第一个参数类型是int,用来接收想要写入文件的字符的ASCII码值;第二个参数是文件指针变量。
返回类型是int。如果成功,则返回该字符(第一个参数)的ASCII码值;如果失败,则返回EOF。
操作一下:
写一下26个字母。
4.2 fgets和fputs函数
前一组的两个函数只能单个字符,这一组的这两个函数用于字符串,可以更快一点。
4.2.1 fgets函数
fgets函数是文本行输入函数,在使用输入函数时,打开文件用fopen函数,第二个参数要用读"r"。
fgets函数作用是读取文件的字符串。
第一个参数类型是char*,是一个指向char类型的指针,是你读取数据后想要放入的地方。
第二个参数类型是int,是读取的最大字符数(包括最后的空字符’\0’),单位是字节。因为包括了空字符,所以你实际读取的数据是num-1,最后一个字符一定是放'\0'。
第三个参数是文件指针变量。
返回值是char*类型,若读取成功,则返回str;若读取失败,则返回NULL。
操作一下,我先在文件里放入:
现在我要读取文件的第一行字符串,把它放在str数组中,并打印到显示器上。
如果fgets函数第二个参数放的是26,那么就只会读到25个字母。
如果在读取中,提前遇到了\0,那么fgets函数不会再往下读了,它会停止读取字符,并在已读取字符的末尾添加空字符’\0’,作为字符串结束符。
下图,fgets函数第二个参数是30,按理应该读完26个字母,加上1个换行符,还有两个字母‘h’、‘e’;
但事实上我只读到了26个字母和一个换行符,就停止了,fgets函数就已经在末尾加上了\0。
这也是fgets函数叫做文本行输入函数的原因了。
4.2.2 fputs函数
fputs函数是文本行输出函数,在使用输出函数时,打开文件用fopen函数,第二个参数要用写"w"。
fputs函数一般作用是向文件写入指定的字符串。
第一个参数类型是char*,是一个指向char类型的指针,它也可以是字符串常量。它是你想写进文件里的字符串首字母地址。
第二个参数是文件指针变量。
返回类型是int,如果写入成功,会返回一个非负数值;如果写入失败,会返回EOF(-1)。
它直到遇到\0停下。
操作一下:
如果提前遇到\0,就会提前停下。
4.3 fscanf和fprintf函数
4.3.1 fscanf函数
fscanf函数是格式化输入函数。在使用输入函数时,打开文件用fopen函数,第二个参数要用读"r"。
其实fscanf函数和scanf函数很像,你会使用scanf函数其实也就是会使用fscanf函数。
对比一下两个函数,你会发现,其实fscanf函数也就比scanf函数多了第一个参数,文件指针变量。
两个函数后面的省略号参数,是接收所要输入的数据的变量。
两个函数返回类型是int,如果读取成功的话,都代表着读取数据的个数(大于等于0);若读取失败,则返回EOF(-1)。
注意:使用fscanf和scanf函数时,非字符串类型的所要储存输入数据的变量需要&(取地址)。
操作一下:
文件里放入:
分别使用一下scanf函数和fscanf函数。
其实你就会发现,他俩长得太像了,直接复制粘贴scanf函数,稍微改一下名字,再加上第一个参数pf,摇身一变就是fscanf函数。
运行代码:
struct Man
{
char name[20];//名字
char sex[10];//性别
int age;//年龄
};
int main()
{
//使用scanf函数
printf("使用scanf函数。\n");
struct Man man1;
scanf("%s %s %d", man1.name, man1.sex, &(man1.age));
printf("名字:%s 性别:%s 年龄:%d\n", (man1.name), (man1.sex), man1.age);
printf("\n");
//使用fscanf函数读
printf("使用fscanf函数。\n");
struct Man man2;
//打开文件
FILE* pf = fopen("test.txt", "r");
assert(pf);
//文件读操作
fscanf(pf,"%s %s %d", man2.name, man2.sex, &(man2.age));
printf("名字:%s 性别:%s 年龄:%d\n", (man2.name), (man2.sex), man2.age);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果:
既然这么像,那么如何让fscanf函数变成scanf函数呢?请大家参考我上篇博客文件操作全解(上)中提到的流和标准流的概念,这样就会清楚了解到scanf函数的逻辑。
我们只要把fscanf函数的第一个参数改为stdin,就是一模一样了这两个函数。
运行代码:
struct Man
{
char name[20];//名字
char sex[10];//性别
int age;//年龄
};
int main()
{
//使用scanf函数
printf("使用scanf函数。\n");
struct Man man1;
scanf("%s %s %d", man1.name, man1.sex, &(man1.age));
printf("名字:%s 性别:%s 年龄:%d\n", (man1.name), (man1.sex), man1.age);
printf("\n");
//使用fscanf函数
printf("使用fscanf函数。\n");
struct Man man2;
fscanf(stdin,"%s %s %d", man2.name, man2.sex, &(man2.age));
printf("名字:%s 性别:%s 年龄:%d\n", (man2.name), (man2.sex), man2.age);
return 0;
}
4.3.2 fprintf函数
fprintf函数是格式化输出函数。在使用输出函数时,打开文件用fopen函数,第二个参数要用写"w"。
我们一样对比一下printf函数。
跟fscanf函数一样,fprintf函数和printf函数很像,fprintf函数只比printf函数多了第一个参数,文件指针变量。
两个函数后面的省略号参数,是接收所要输入的数据的变量。
返回类型是int,若成功运行,会返回所输出的字符字节总数;若失败,会返回一个负数并报错。
操作一下(文件此时为空):
struct Man
{
char name[20];//名字
char sex[5];//性别
int age;//年龄
};
int main()
{
//使用printf函数
printf("使用printf函数\n");
struct Man man1 = {"张三","男",18};
printf("名字:%s 性别:%s 年龄:%d\n", (man1.name), (man1.sex), (man1.age));
printf("\n");
//使用fprintf函数
printf("使用fprintf函数\n");
struct Man man2 = { "李四","男",19 };
//打开文件
FILE* pf = fopen("test.txt", "w");
assert(pf);
//文件写操作
fprintf(pf,"名字:%s 性别:%s 年龄:%d\n", (man2.name), (man2.sex), (man2.age));
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
运行结果:
然后你也会发现,他俩长得太像了,直接复制粘贴printf函数,稍微改一下名字,再加上第一个参数pf,摇身一变就是fprintf函数。
一样的道理,fprintf函数也可以转化成printf函数,只要把第一个参数改为stdout就可以了。这样就可以直接打印在显示器上了。
运行代码:
//fprintf函数怎样变成printf函数
struct Man
{
char name[20];//名字
char sex[5];//性别
int age;//年龄
};
int main()
{
//使用printf函数
printf("使用printf函数\n");
struct Man man1 = {"张三","男",18};
printf("名字:%s 性别:%s 年龄:%d\n", (man1.name), (man1.sex), (man1.age));
printf("\n");
//使用fprintf函数
printf("使用fprintf函数\n");
struct Man man2 = { "李四","男",19 };
fprintf(stdout,"名字:%s 性别:%s 年龄:%d\n", (man2.name), (man2.sex), (man2.age));
return 0;
}
结果:
到这里,我们通过对比可以知道一件事情:
scanf函数 | 从标准输入流stdin上读取格式化的数据 |
fscanf函数 | 从指定的输入流上读取格式化的数据 |
printf函数 | 把数据以格式化的形式打印在标准输出流stdout上 |
fprintf函数 | 把数据以格式化的形式打印在指定的输出流上 |
这样我们的理解可以更深刻一些。
4.4 fread和fwrite函数
这一组函数用于以二进制的形式输入或输出文件(注意,这组函数只适用于文件!)。
4.4.1 fwrite函数
fwrite函数是二进制输出函数。在使用输出函数时,打开文件用fopen函数,第二个参数要用写"wb"。
该函数有四个参数。
第一个参数类型是void*(它用来接收任意类型,什么类型取决于程序员自己),是将要输出数据的变量的指针。
第二个参数类型是size_t(用来接收sizeof的返回值),以字节为单位,是文件读取数据基本单元字节的大小。
第三个参数类型是size_t,是读取的次数,读取数据的数量。
第四个参数是文件指针变量。
返回类型是size_t,若成功运行,会返回输出的数据总数。如果此数字与 count 参数不同,则会报错误来阻止函数完成。若大小为零,则该函数返回零,同时错误指示器ferror保持不变。
操作一下(此时文件为空):
运行代码:
int main()
{
//使用fwrite函数
//打开文件
FILE* pf = fopen("test.txt", "wb");
assert(pf);
//文件写操作
int str[10] = { 1,2,3,4,5 };
fwrite(&str,sizeof(int), 5, pf);
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果:
我们可以发现记事本文件中所读取的数据我们肉眼是看不懂的,因为我们写入文本的是二进制,而文件是以文本形式读取的,所以是乱码。
4.4.2 fread函数
fread函数是二进制输入函数。在使用输入函数时,打开文件用fopen函数,第二个参数要用读"rb"。
该函数有四个参数。
第一个参数类型是void*(它用来接收任意类型,什么类型取决于程序员自己),是将要放入数据的变量的指针。
第二个参数类型是size_t(用来接收sizeof的返回值),以字节为单位,是文件读取数据基本单元字节的大小。
第三个参数类型是size_t,是读取的次数,读取数据的数量。
第四个参数是文件指针变量。
返回类型是size_t,若运行成功,会返回读取数据的次数;若失败,返回0。
操作一下:
此时文本内容是刚才以二进制形式写入的数字12345,我们新建的str数组里面什么都没有。
运行代码:
int main()
{
//使用fwrite函数
//打开文件
FILE* pf = fopen("test.txt", "rb");
assert(pf);
//文件读操作
int str[10] = { 0 };
if ((fread(str, sizeof(int), 5, pf)))
{
for (int i = 0; i < 5; i++)
{
printf("%d ",str[i]);
}
}
//关闭文件
fclose(pf);
pf = NULL;
return 0;
}
结果:
还有文件的随机读写,文件读取结束的判定和文件缓冲区等知识,我们下一篇博客见~