C语言文件操作 文件的打开、关闭、读取
在C语言学习过程中,对于数据的存储是无法避免的问题之一。
我们学习了用数组来存储某一种类型数据,用结构体来存储多种不同类型数据,但是这些都是程序中的容器,程序退出后这些数据也就随之消失了。
所以我们尝试寻找一种方式,能够将数据存储与程序分离开来。在 linux 中有这么一句话——一切皆文件。所以我们尝试将数据存储到文件之中,那么这样一来就无可避免的要学习文件操作了,本文尽量详细的把常见文件操作列举出来,希望能够帮到小萌新。
关于文件操作函数接口我有整理过,→戳这里
1. 头文件
当然了,在写代码之前先要考虑头文件,以防码代码过程中出现的函数未声明等错误提示影响思路与心情。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
其实只要标准IO头文件就好了,不过一般还会涉及到字符串处理等操作,索性再加上他们好了。
2. 打开与关闭文件
函数原型
FILE *fopen(const char *pathname, const char *mode);
@param pathname : 要操作的文件名,可以是相对路径也可以是绝对路径。
@param mode : 操作权限,常用的有 r(read), w(write), a(append)
@return values : 成功返回 文件流指针, 失败返回 NULL;
int fclose(FILE *stream);
@param stream : 文件操作流指针
这两个操作是成对出现的哦,既然打开了文件,操作完成之后就要好好关上它的哦,以防止其他地方误操作导致不经意之间改变文件内容。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
// 指定文件名,不想修改它,故用 const 修饰
const char *file_name = "a.txt";
// 定义一个文件操作指针,我们操作就靠这个指针啦
FILE *fp = NULL; // 定义指针赋值为 NULL 是个好习惯,避免野指针
fp = fopen(file_name, "r");
// 打开文件失败会返回NULL的哦,所以打开后要判断,打开失败就不能操作啦
if(fp == NULL)
{
printf("Open file failed!\n");
return -1;
}
// doing sth.
fclose(fp); // 关闭文件
return 0;
}
以上就是打开与关闭文件的操作啦,是不是很简单?哈哈,虽然我们只是打开然后又关上,感觉写了个寂寞,但这只是开始嘛,慢慢来。
3. 从文件中读取
首先,我要说明一下 2 中的代码里的 a.txt 这个文件,它里面存储着 a-z 这26和英文字母。
然后呢,从文件中读取有很多方法,在这里呢我只介绍用的比较多的,相对安全的几个函数。
3.1 fread 函数
函数原型
size_t fread(void *ptr, size_t size,
size_t nmemb, FILE *stream);
// 这个函数参数好像变多了呢,我们一起好好看一下它要什么吧。
@param ptr : 缓冲区指针,也就是说我们读出来的数据放在什么位置。
@param size : 一次读多大呢?
@param nmember : 读几次呢?
@param stream : 从哪里读呢?
// 这样一分析好像也不难,我们只要根据函数要求给定参数就可以操作了。
@return values : 成功返回读取的次数,失败返回0
既然了解完了这个函数的参数要求,那我们来尝试一下,把我存在 a.txt 文件中的26个字母读取出来吧。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
const char *file_name = "a.txt";
FILE *fp = NULL;
fp = fopen(file_name, "r"); // 打开文件
if(fp == NULL)
{
printf("Open file failed!\n");
return -1;
}
// 打开文件成功之后呢,这个 fp 就是指那个文件啦,
// 我们的操作也是建立在它的基础之上的。
// fread 需要一个缓冲区,用于存放读取出来的数据,那我们就先准备好
// 因为要读取 26 个字,所以多申请一个字符空间用来放'\0'
char read_buffer[27] = {0};
//万事具备,直接调用函数
size_t read_ret = fread(read_buffer, 26, 1, fp);
if(read_ret == 0) // 保险起见,判断一下是否读取成功
{
printf("Read error!\n");
return -1;
}
// 成功读取,打印一下读到了啥
printf("Read successed, the contain is : %s\n", read_buffer);
fclose(fp); // 关闭文件
return 0;
}
好啦,这样就把26个字母读取出来了,不知道你们有没有成功呢?
3.2 fscanf
还记得我们用的最多的输入函数吗?对的,就是 scanf 函数,我们可以来回顾一下它的函数原型:
int scanf(const char *format, ...);
嘿嘿,是不是有些童鞋觉得有点奇怪,这是个啥,还有那3个点是什么鬼?
相信学霸同学已经认出来了,这是一个变参函数,具体怎么实现我们这里就不介绍了,有兴趣的同学可以去查一查,不过我们可以简单说说。
还记得我们怎么使用么?是不是%?的形式呀,简单来说就是根据这个格式控制符的个数以及对应格式来确定这后面到底有几个,是什么类型的参数。这样一说应该就有点了解了吧。
我们这里说的是它的另一个兄弟,fscanf,按照惯例,先看看函数原型:
int fscanf(FILE *stream, const char *format, ...);
em。。。咋那么眼熟是吧,就比scanf多了一个参数,stream:文件流指针。
那它怎么用呢?用在哪儿呢?
现在我又有了个新文件b.txt,里面存储着一个同学的简单基本信息:
姓名 年龄 性别 所在学校
张三 22 男 csdn大学
那如果我有一个学生结构体,刚好就是这4项,我该如何用这个给结构体赋值呢?
typedef struct
{
int stu_age;
char stu_name[6];
char stu_sex[2];
char stu_coll[20];
} stu ;
方法1:以字符串形式读取一行,然后按要求切割后,分别赋值即可。
方法2:以格式读取,直接获取对应数据。
这里呢就不演示方法1了,字符串处理虽然很重要但不是本文要讨论的内容,我们来看看如何按格式读取。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int stu_age;
char stu_name[10];
char stu_sex[4];
char stu_coll[20];
} stu ;
int main()
{
const char *file_name = "a.txt";
stu student;
FILE *fp = NULL;
fp = fopen(file_name, "r"); // 打开文件
if(fp == NULL)
{
printf("Open file failed!\n");
return -1;
}
int ret = fscanf(fp, "%s %d %s %s\n", student.stu_name, &student.stu_age,
student.stu_sex, student.stu_coll);
if(ret == 0)
{
printf("fscanf error!\n");
}
printf("%s %d %s %s\n", student.stu_name, student.stu_age,
student.stu_sex, student.stu_coll);
fclose(fp); // 关闭文件
return 0;
}
这样就可以按照相应的格式读取出相应的数据了,是不是很简单方便?
其实到这里就应该有同学会发现一件事情了,为什么你都是文件开头开始读取啊,要是我想要的是在文件中间部分怎么办?全部读取出来再处理字符串吗?文件小当然可以,但是文件大了这样操作肯定是不现实的啊。
是的,这个问题发现的很及时,要解决这个问题就要涉及到文件操作指针位置偏移的操作了,感觉本文凑字数凑得差不多了,那就下次再说吧,如果有兴趣的话可以先去查一查资料哦。