文件和文件操作

文件是我们使用电脑时接触最多的存储信息的东西,那么我们在学习C语言时为什么要用到文件呢?文件有什么用呢?我们怎么使用代码对文件进行各种操作呢?接下来,我会用最通俗的语言告诉你,一起来思考吧~!

一、文件

(一)、文件的概述

数据存入文件可以保存的更久,可以持久的保存我们需要的数据。文件有两种:程序文件和数据文件。
1.程序文件:程序文件指:源文件(.c)以及程序在编译过程产生的文件。
2.数据文件:提供数据、保存数据。⽐如程序运⾏需要从中读取数据的⽂件,或者输出内容的⽂件。在这里我们主要讨论的是数据文件。

(二)、二进制文件和文本文件

二进制文件是指内存二进制的数据不加转换直接存入文件中。(乱码)
文本文件是指存储的是ASCII码。
举例:在这里插入图片描述

二、文件的打开和关闭

我们对文件的操作如下:
在这里插入图片描述

1.流和标准流

(1).流

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输⼊输出
操作各不相同,为了⽅便程序员对各种设备进⾏⽅便的操作,我们抽象出了流的概念,我们可以把流
想象成流淌着字符的河。(个人理解:把数据写到外部设备上,把外部设备的数据获取到内存里)
在这里插入图片描述
如果从定义来构思应该是这样的,如果没有流的话,我们可能需要很多流程,但是有了流存在中间的操作就省去了,我们直接把数据写/读给流,也就是简化了流程

(2).标准流

我们还有一个疑问:每次我们编程时为什么没有打开流呢?而是直接运行?那是因为我们C语言程序在启动时就默认打开三个流

  • stdin:标准输入流,在大多数的环境中从键盘中输入。
  • stdiout标准被初流,大多数的环境中输出到显示器界面。
  • stderr标准错误流,大多数韩静中输出到显示器界面。
    默认打开这三个流,我们就可以直接使用我们的“printf”、"scanf"等函数了。

(二)、文件指针

c语言用FILE*文件指针类型的文件来维护各种操作的,所以文件指针对于我们来说及其重要。
使用文件的时候,我们要先打开文件—>内存中创建了一个和文件相关的文件信息区。这个文件信息区保存了很多类型的信息,并且保存在一个被命名问FILE的结构体中。

struct _iobuf {
 char *_ptr;
 int _cnt;
 char *_base;
 int _flag;
 int _file;
 int _charbuf;
 int _bufsiz;
 char *_tmpfname;
 };
typedef struct _iobuf FILE;

上述代码是在VS2013中stdio.h头文件中观看的,可以很清晰的看到,有各种类型的信息,被封装在FILE的结构体中。

--------------------------我是一条普通的分割线QWQ----------------------------------------------------------------------------------
我们一般用FILE的指针类型来维护这个FILE结构的变量,所以当我们定义一个FILE*类型的指针时,通过⽂件指针变量能够间接找到与它关联的⽂件。

(三)、fopen函数

我们要想文件里面读取内容或者写入内容时就需要先打开文件,才能进行操作,那么我们就要从打开文件开始学习。
fopen()函数的功能是打开文件。
在这里插入图片描述
我们可以看这个函数的参数类型有两个:一个是文件的名字,另一个是打开方式,和参数返回值是一个文件指针类型。

那么打开方式有哪几种呢?在这里我列一些常用。
在这里插入图片描述
扩展:有时候我们可能很难区分读和写的含义,读是指从硬盘(文件)输入/读到内存中,写是指从内存中输出/写到硬盘(文件)上。

那么现在我们就用几个实例来带大家试试fopen()函数吧。
如果我们要先用“只读”®的形式打开一个文件,当我没有在当前路径下创建是,我们可以观察是否会返回空指针,我们可以用assert或者if语句进行判断,这里我们用了assert。注:包含头文件(#include<assert.h>)

#include<assert.h>
int main()
{
	FILE* p = fopen("data.txt", "r");
	assert(p);
	return 0;
}

在这里插入图片描述
当我使用fopen的"r"时,程序并没有在我当前文件夹中发现名叫data.txt的文件,所以直接报警,也就是说,打开失败返回了NULL。那么我们如果现在时用"w"时,他会不会自动创建呢?

int main()
{
	FILE* p = fopen("data.txt", "w");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	return  0;
}

在这里插入图片描述

最后发现我们创建成功了,所以我们也就验证了"r"和"w"的区别,如果你想在某个路径下建立,将路径写的更完整。

(四)、fclose函数

有打开肯定就会有关闭,所以fopen要和fclose函数配合使用,**当我们不用指针的时候及时关闭文件指针所指向的内存,并且置空。**即:
在这里插入图片描述
这样可以是代码更加健壮,也不会出现后续的问题,避免了野指针的存在!!

三、文件的顺序读写

当我们打开某个文件时,我们希望将某个内容或数据从内存输出(写)到文件中保存,或者将文件保存的数据输入(读)到内存里提供我们使用。所以我们就需要一个顺序读写的函数帮助我们去获得或储存数据。

(1)、fputc函数

在这里插入图片描述
通过图中我们可以看出fputc函数的参数类型和返回值,他会返回一个整型,并且一次只能写一个字符。

int main()
{
	FILE* p = fopen("data.txt", "w");
	if (p==NULL)
	{
		perror("fopen");
		return 1;
	}
	fputc('A', p);
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述

最后会发现我们写入成功,注意只能一次写一个字符!!!!

(2)、fgetc函数


fgetc函数的参数类型是文件指针类型,返回值是一个整型(字符的ASCII码值),也就是从文件里输入到内存中,如果读取失败则会返回EOF(-1)。

int main()
{
	FILE* p = fopen("data.txt", "r");
	if (p==NULL)
		{
			perror("fopen");
			return 1;
		}
		int ch=fgetc( p);
		printf("%c", ch);
		fclose(p);
		p = NULL;
		return 0;
}

在这里插入图片描述
扩展:读取失败有两种情况:1.遇到了文件末尾读取失败 2.遇到错误会读取失败。 无论是遇到文件末尾还是遇见错误,都会返回EOF(-1)。

(3)、fputs函数

如果我们要输出一句话/一串字符串时,可以用fputs他可以将字符串输出到文件里。
在这里插入图片描述
这个函数的返回类型是一个整型,参数类型有两个:一个是指向字符串的指针,一个是文件指针类型。

int main()
{
	FILE* p = fopen("data.txt", "w");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputs("hellow  world!!",p);
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
从上述例子看出,我们确实将一句话/一段字符串输出到了文件中。

(4)、fgets函数

在这里插入图片描述
从这个函数的返回类型和参数类型来看,比较复杂,可以通过图来理解。返回了str的地址,注:其实并没有读取num个,而是num-1个,因为他会自动补充\0。

int main()
{
	int arr[20] = { 0 };
	FILE* p = fopen("data.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	int *ptr=fgets(arr, 6,p);
	printf("%s", ptr);
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
如果我要取出num个,实际上只拿出了num-1个字符,这就是fgets的特殊性,他会自动补充\0。
注:一定要把接收数据的存储空间(str)设置大,否则会出现栈溢出错误。

(5)、fwrite函数

在这里插入图片描述
fwrite表示以二进制从内存中输出到文件中,即:乱码

int main()
{
	int arr[] = {1,2,3,4,5};
	FILE* p = fopen("data.txt", "wb");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	fwrite(arr, sizeof(arr[0]), 3, p);
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述

(5)、fread函数

fread与fwrite的函数相似,只是第一个参数不同。
在这里插入图片描述

int main()
{
	int arr[20] = {0};
	FILE* p = fopen("data.txt", "rb");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	fread(arr, sizeof(arr[0]), 2, p);
	for (int i = 0; i < 2; i++)
	{
		printf("%d", arr[i]);
	}
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
会发现我们刚开始将123存入后,通过fread函数读出了二进制文件的内容。

四、文件的随机读写

刚刚我们讲了顺序读写,是为了让数据按照顺序来进行读数据和写数据,那么如果我们想要从某个段数据中间开始读写呢?这个时候就需要随机读写的存在了。(随机读写要配合顺序读写使用)

(1)、fseek函数

在你使用fseek函数时,必须要了解文件的内容,再能惊醒随机读写,因为通过函数的参数我们可以发现,他需要我们提供偏移量,也就是要从哪里开始读取,而第三个参数取值有特定的模式(三个)。如
在这里插入图片描述
在这里插入图片描述

int main()
{
	int arr[20] = { 0 };
	FILE* p = fopen("data.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	fseek(p, 1, SEEK_SET);
	int ch = fgetc(p);
	printf("%c", ch);
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
注意如果我要从文档结尾插入的话,一定要是负数。
如图:
在这里插入图片描述

(2)、ftell函数

**ftell函数的作用是告诉当前文件指针距离开始的偏移量多少。**ftell的函数和返回值也很容易理解,这里就不和大家过多赘述了,如图:
在这里插入图片描述
在这里插入图片描述

(2)、rewind函数

rewind函数是让文件指针回到其实的位置。如图所示:
在这里插入图片描述
在这里插入图片描述

五、文件读取结束的判定

(一)、被错误使用的feof函数

feof函数的作用当文件读取结束时,判断是读取结束的原因是否遇到文件结尾!
牢记:在文件读取过程中,不能用feof函数的返回值直接来判断文件的是否结束。

判断方式:
①文本文件:判断EOF(fgetc)/NULL(fgets)、
②二进制文件:判断返回值是否小于实际要读的格式,即最后一次读取。

如果ferror函数表示是遇到错误后结束!

int main()
{
	FILE* p = fopen("data.txt", "r");
	if (p == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = 0;
	while ((ch = fgetc(p)) != EOF)
	{
		putchar(ch);
	}
	if (feof(p))
	{
		printf("\n遇到结尾正常结束");
	}
	else if (ferror(p))
	{
		printf("\n遇到错误结束");

	}
	fclose(p);
	p = NULL;
	return 0;
}

在这里插入图片描述
证明本代码是正常遇到结尾结束的。

以上就是我对文件和文件操作的个人理解和知识点整理,欢迎大家观看以及指出错误,谢谢~!

  • 5
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值