C语言-文件

发布于:2022-11-28 ⋅ 阅读:(344) ⋅ 点赞:(0)

一,为什么使用文件

为什么使用文件呢?
如果我们在编写一个程序,程序中有很多数据需要保存下来,以便下次使用。
例如,我在之前投稿的一个通讯录的一个小程序,载每次使用,关闭程序后,
再次打开程序的时候发现之前存的练习人不见了,这是为什么呢?
是因为我们程序中的数据是放在内存上的,一旦我们程序关闭,内存上的数据就会被清理掉。

所以我们可以将之前联系人的信息在程序结束前,存放到文件中,文件时磁盘上的空间不会因为
程序的结束而导致数据的丢失。

二,文件的打开与关闭

那文件是如何使用的呢?
首先,我们要打开文件
打开文件后进行读写

在我们打开文件的时候,内存中就会自动产生一个文件信息区,这是一个结构体类型的变量
用来管理文件的,打开文件后fopen函数会一个文件类型的指针来指向文件信息区这块内存空间。

读写之后关闭文件
这是运用文件的一套流程

下面我们来介绍文件的打开与关闭

1,fopen

在这里插入图片描述

第一个参数是文件名,第二个参数文件的打开形式
下面介绍一下各种打开方式

(1)r w a

r(只读):为了输入数据打开一个文件,当文件不存在时就会出错
w(只写):为了输出数据而打开一个文件,当文件不存在时会创建一个新的文件
a(追加):向文件尾部追加数据,当文件不存在时会创建一个新的文件

(2)rb wb ab

rb(只读):为了输入数据打开一个二进制文件,当文件不存在时就会出错
wb(只写):为了输出数据而打开一个二进制文件,当文件不存在时会创建一个新的文件
ab(追加):向一个二进制文件尾部追加数据,当文件不存在时会创建一个新的数据

(3)r+ w+ a+

r+(读写):为了读写打开一个文件,当文件不存在时就会出错
w+(读写):为了读写打开一个文件,当文件不存在时会创建一个新的文件
a+(读写):打开一个文件在文件尾部进行读写,文件不存在时会创建一个新的文件

(4)rb+ wb+ ab+

r+(读写):为了读写打开一个二进制文件,当文件不存在时就会出错
w+(读写):为了读写打开一个二进制文件,当文件不存在时会创建一个新的文件
a+(读写):打开一个二进制文件在文件尾部进行读写,文件不存在时会创建一个新的文件

2,fclose

在这里插入图片描述

就一个参数,及指向文件流的指针变量
下面演示一下,文件打开关闭的具体流程
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

三,文件顺序读写函数

1,fgetc,fputc

在这里插入图片描述

以字符的模式输出数据
例如,我们可以将26个字母输出到文件中
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		fputc('a' + i, pf);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
在这里插入图片描述

参数只有一个指向文件流的指针变量
下面演示一下如何读取数据
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	char arr[27] = "";
	int i = 0;
	for (i = 0; i < 26; i++)
	{
		arr[i]=fgetc(pf);
	}
	printf("%s\n", arr);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

2,fgets,fputs

在这里插入图片描述

以文本行的方式输出数据
下面举个例子
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	char arr[] = "hello world";
	fputs(arr, pf);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述
在这里插入图片描述

以文本行的方式读入数据。
注意第二个参数num,是读到的字符个数(实际是num-1个,第num个字符是'\0'),
例如一行有20个字符但是num写的10,那么第一次读了九个字符,下次读的时候接上
上次的位置继续读

如果num大于一行的字符数,那么也只会读取一行的数据,不会在往下读了。
例如我们读取刚刚写入的hello world
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	char arr[12] = "";
	fgets(arr,20, pf);
	printf("%s\n", arr);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

3,fscanf,fprintf

在这里插入图片描述
在这里插入图片描述

这两个函数是按照一定的格式,输入/输出的函数与scanf printf 非常类似
下面我们以输出和输入一个结构体的数据为例
struct stu
{
	char name[20];
	int age;
	float score;
};
int main()
{
	//打开文件
	struct stu s1 = { "zhangsan",20,98.5f };
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	fprintf(pf, "%s %d %f", s1.name, s1.age, s1.score);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

下面,我们再用sscanf从文件中把这个结构体的数据读入到内存中

int main()
{
	//打开文件
	struct stu s2 = { 0 };
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	fscanf(pf, "%s %d %f", s2.name, &(s2.age), &(s2.score));
	printf("%s %d %f", s2.name, s2.age, s2.score);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

4,fread,fwrite

在这里插入图片描述
在这里插入图片描述

这两个函数适用于二进制文件的读写
第一个参数是指向存放读入/读出数据空间的指针变量,第二个参数是这种数据的大小(字节)
第三个数据是要读入/读出多少个这种数据
第四个参数是指向文件流的指针变量
我们依然以这个结构体为例子将它读出到二进制文件中,
并且将它从二进制文件中读取到内存中
int main()
{
	//打开文件
	struct stu s1 = { "zhangsan",20,98.5f };
	FILE* pf = fopen("test.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	fwrite(&s1, sizeof(s1), 1, pf);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

int main()
{
	//打开文件
	struct stu s2 = { 0 };
	FILE* pf = fopen("test.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	fread(&s2, sizeof(s2), 1, pf);
	printf("%s %d %f", s2.name, s2.age, s2.score);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

四,文件的随机读写

上面介绍了文件的顺序读写,是因为我们以何种形式打开文件时,文件指针就会处在某种位置,
例如我们以r的形式打开文件,那么文件指针就在文件的首部,
以w的形式打开文件,那么文件指针就在文件的尾部
以a的形式打开文件,那么文件的指针就在文件的尾部

但是我们以读 或者 写的方式打开文件,可以通过下面的函数来调整文件指针的位置,
以追加的形式不可以哟。

1,fseek

在这里插入图片描述
在这里插入图片描述

起始位置,可以时文件首部,文件尾部,或者是当前位置
第二个参数是偏移量,是指据起始位置的偏移量
我们先将文件的内容写成abcdef
例如我们用fgetc函数读入数据是第一次读的是a
那么第二次读的就是b,但是我们可以通过改变文件指针的位置,使它读到的不是b
下面看例子
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	int ret = fgetc(pf);
	printf("%c\n", ret);
	fseek(pf, 1, SEEK_CUR);
	ret = fgetc(pf);
	printf("%c\n", ret);
	

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

可以看到我们跳过了b,直接读到了c

2,ftell

在这里插入图片描述

这个函数的作用是告诉我们文件指针当前的位置(及相对于文件首部的偏移量)
以上段代码为例子,读取完c之后指针自动向后跳一步,及偏移量是3,
我们来验证一下
int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	int ret = fgetc(pf);
	printf("%c\n", ret);
	fseek(pf, 1, SEEK_CUR);
	ret = fgetc(pf);
	printf("%c\n", ret);
	int pose=ftell(pf);
	printf("%d\n", pose);
	

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

在这里插入图片描述

3,rewind

在这里插入图片描述

这个函数的作用是将文件指针调整到指向文件首部的位置。
例如,我们继续上述代码将指针rewind后再读取一个字符

int main()
{
	//打开文件
	FILE* pf = fopen("test.txt", "r");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	int ret = fgetc(pf);
	printf("%c\n", ret);
	fseek(pf, 1, SEEK_CUR);
	ret = fgetc(pf);
	printf("%c\n", ret);
	int pose=ftell(pf);
	printf("%d\n", pose);
	rewind(pf);
	ret = fgetc(pf);
	printf("%c\n", ret);
	

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
可以看到依然读取的是a这个字符,充分说明文件指针又回到了文件首部的位置

五,文件读取结束的判定

我们再从文件中读取数据时,难免会发生某些错误是读取暂停,
我们可以通过下面的两个函数,来表明究竟是遇到IO错误,读取到了文件尾部的EOF标志
导致读取结束的

1,feof

在这里插入图片描述

当这个函数的返回值为非零数时,表示遇到EOF导致读取暂停
而返回零时表示,遇到错误导致读取暂停

2,ferror

在这里插入图片描述

这个函数的返回值与feof返回值恰好相反
下面我们举个例子,来说明一下这两个函数的运用场景

#include <stdio.h>
enum { SIZE = 5 };
int main(void)
{
    double a[SIZE] = {1.,2.,3.,4.,5.};
    FILE *fp = fopen("test.bin", "wb"); // 必须用二进制模式
    fwrite(a, sizeof *a, SIZE, fp); // 写 double 的数组
    fclose(fp);
    double b[SIZE];
    fp = fopen("test.bin","rb");
    size_t ret_code = fread(b, sizeof *b, SIZE, fp); // 读 double 的数组
    if(ret_code == SIZE) {
        puts("Array read successfully, contents: ");
        for(int n = 0; n < SIZE; ++n) printf("%f ", b[n]);
        putchar('\n');
   } else { // error handling
       if (feof(fp))
          printf("Error reading test.bin: unexpected end of file\n");
       else if (ferror(fp)) {
           perror("Error reading test.bin");
       }
   }
    fclose(fp);
}

六,二进制文件与文本文件

我们知道,数据在内存中的存储都是以二进制的方式存储的
如果我们不加转换的输出到外存,那么就是二进制文件
如果要求外存上以ASCLL码的形式存储,那么需要在存储前转换。这样得到的文件就是文本文件
我们拿10000举个例子

在这里插入图片描述

我们验证一下,看看二进制文件里储存的是不是二进制码
int main()
{
	FILE* pf = fopen("test.txt", "w");
	if (pf == NULL)
	{
		perror("fopen:");
		return 1;
	}
	//文件读写
	int a = 10000;
	fwrite(&a, sizeof(a), 1, pf);

	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}
我么看下文件中的内容

在这里插入图片描述

我们发现不对劲啊,这是因为。txt文件只能以文本的形式打开文件,所以看到的是乱码,
我们可以借助VS ,它可以打开二进制文件

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到 10 27 00 00 就是10000 这是用十六进制表示的。

七,输出缓冲区

在这里插入图片描述

设置缓冲区是为了提高操作系统的效率,因为我们每次将数据传到硬盘的时候都会麻烦操作系统一次,
所以当缓冲区中的数据达到一定量的时候一起传到硬盘上。

但是也有其他刷新缓冲区的方法。
例如我们fclose(pf)关闭文件的时候就会刷新一次缓冲区,
也可以用fflush(pf)强制刷新一下缓冲区,将其中的内容放到硬盘上去

在这里插入图片描述
本文中,可以看到我引用了大量上上面那样的函数信息
下面给大家推荐哥网站,可以用来平时查询函数用的网站

链接: link

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到