C语言-> 文件操作(函数满屏)

2023-12-19 23:30:05

系列文章目录


前言

?作者简介:大家好,我是橘橙黄又青,一个想要与大家共同进步的男人😉😉

🍎个人主页:橘橙黄又青_C语言,数据结构,函数-CSDN博客

目的:学习文件操作,即文件相关函数的学习

在这里首先放置我个人认为好的学习c语言的网站

:cplusplus.com:https://legacy.cplusplus.com/reference/clibrary/

好了我们现在开始吧?

1. 为什么使??件?

如果没有?件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失 了,等再次运?程序,是看不到上次程序的数据的,如果要将数据进?持久化的保存,我们可以使??件。

2. 什么是?件?

?磁盘上的?件是?件
但是在程序设计中,我们?般谈的?件有两种: 程序?件、数据?件 (从?件功能的?度来分类
的)。

2.1 程序?件

程序?件包括源程序?件(后缀为.c),?标?件(windows环境后缀为.obj),可执?程序(windows 环境后缀为.exe)。

2.2 数据?件

?件的内容不?定是程序,?是程序运?时读写的数据,?如程序运?需要从中读取数据的?件,或者输出内容的?件本章讨论的是数据?件。
在以前各章所处理数据的输?输出都是以终端为对象的,即从终端的键盘输?数据,运?结果显?到 显?器上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使?,这?处 理的就是磁盘上?件。

2.3 ?件名 ?

?个?件要有?个唯?的?件标识,以便??识别和引?。
?件名包含3部分:?件路径+?件名主?+?件后缀
比如:
c:\ code \ test . txt

3. ?进制?件和?本?件?

根据数据的组织形式,数据?件被称为?本?件或者?进制?件
?进制?件:数据在内存中以?进制的形式存储,如果不加转换的输出到外存,就是?进制?件。
?本?件: 以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的?件就是?
本?件。
那?个数据在内存中是怎么存储的呢?
字符?律以ASCII形式存储,数值型数据既可以?ASCII形式存储,也可以使??进制形式存储,什么意思比如说:
如有整数10000,如果以ASCII码的形式输出(把10000当作字符)到磁盘,则磁盘中占?5个字节(每个字符?个字节),? ?进制形式输出,则在磁盘上只占4个字节(VS2019测试)。
展示:
测试代码:
#include <stdio.h>
int main()
{
     int a = 10000;
     FILE* pf = fopen("test.txt", "wb");
     fwrite(&a, 4, 1, pf);//?进制的形式写到?件中
     fclose(pf);
     pf = NULL;
     return 0;
}

在vs2022上 以二进制的方式打开,我们来看看:
那我们以 文本的形式打开:
好啦,理解二进制文件和文本文件之后,接下来我们学习文件的打开和关闭。

4. ?件的打开和关闭

在此之前先了解一个抽象的概念:

4.1 流和标准流

4.1.1

我们程序的数据需要输出到各种外部设备,也需要从外部设备获取数据,不同的外部设备的输?输出 操作各不相同,为了?便程序员对各种设备进??便的操作,我们抽象出了流的概念,我们可以把流 想象成流淌着字符的河。
C程序针对?件、画?、键盘等的数据输?输出操作都是通过流操作的。
?般情况下,我们要想向流?写数据,或者从流中读取数据,都是要打开流,然后操作。

4.1.2 标准流

那为什么我们从键盘输?数据,向屏幕上输出数据,并没有打开流呢?

那是因为C语?程序在启动的时候,默认打开了3个流:
? stdin - 标准输?流,在?多数的环境中从键盘输?,scanf函数就是从标准输?流中读取数据。
? stdout - 标准输出流,?多数的环境中输出?显?器界?,printf函数就是将信息输出到标准输出流中。
? stderr - 标准错误流,?多数环境中输出到显?器界?。
这是默认打开了这三个流,我们使?scanf、printf等函数就可以直接进?输?输出操作的。

stdin、stdout、stderr 三个流的类型是: FILE* ,通常称为?件指针?

C语?中,就是通过 FILE* 的?件指针来维护流的各种操作的
这怎么理解呢?来看图:
相当于中间商处理翻译客户的文件,再把以客户熟知方式的输出还给客户。

4.2 ?件指针

缓冲?件系统中,关键的概念是“?件类型指针”,简称“?件指针”。
每个被使?的?件都在内存中开辟了?个相应的?件信息区,?来存放?件的相关信息(如?件的名 字,?件状态及?件当前的位置等)。这些 信息是保存在?个结构体变量中的。该结构体类型是由系 统声明的,取名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* pf;//?件指针变量
定义pf是?个指向FILE类型数据的指针变量。可以使pf指向某个?件的?件信息区(是?个结构体变 量)。通过该?件信息区中的信息就能够访问该?件。 也就是说,通过?件指针变量能够间接找到与 它关联的?件
可以这样理解:
好了,接下来我们学校有关文件的函数。

4.3 ?件的打开和关闭

?件在读写之前应该先打开?件,在使?结束之后应该关闭?件。
在编写程序的时候,在打开?件的同时,都会返回?个FILE*的指针变量指向该?件,也相当于建?了 指针和?件的关系。
ANSIC 规定使? fopen 函数来打开?件, fclose 来关闭?件
//打开?件
FILE * fopen ( const char * filename, const char * mode );//两个参数:一个是文件名,一个是文件使用方式
//关闭?件
int fclose ( FILE * stream );
mode表??件的打开模式,下?都是?件的打开模式下的操作:
?件使??式
含义
如果指定?件不存在
“r”(只读)
为了输?数据,打开?个已经存在的?本?件
出错
“w”(只写)
为了输出数据,打开?个?本?件
建??个新的?件
“a”(追加)
向?本?件尾添加数据
建??个新的?件
“rb”(只读)
为了输?数据,打开?个?进制?件
出错
“wb”(只写)
为了输出数据,打开?个?进制?件
建??个新的?件
“ab”(追加)
向?个?进制?件尾添加数据
建??个新的?件
“r+”(读写)
为了读和写,打开?个?本?件
出错
“w+”(读写)
为了读和写,建议?个新的?件
建??个新的?件
“a+”(读写)
打开?个?件,在?件尾进?读写
建??个新的?件
“rb+”(读写)
为了读和写打开?个?进制?件
出错
“wb+”(读
写)
为了读和写,新建?个新的?进制?件
建??个新的?件
“ab+”(读
写)
打开?个?进制?件,在?件尾进?读和写
建??个新的?

我们怎么理解读和写看图:

这里我们演示一下打开关闭,实战代码:

/* fopen fclose example */
#include <stdio.h>
int main ()
{
     FILE * pFile;
     //打开?件
     pFile = fopen ("myfile.txt","w");
     //?件操作
     if (pFile!=NULL)
     {
         fputs ("fopen example",pFile);
         //关闭?件
         fclose (pFile);
     }
 return 0;
}

5. ?件的顺序读写

5.1 顺序读写函数介绍

函数名
功能
适?于
fgetc
字符输?函数
所有输出流
fputc
字符输出函数
所有输出流
fgets
?本?输?函数
所有输出流
fputs
?本?输出函数
所有输出流
fscanf
格式化输?函数
所有输出流
fprintf
格式化输出函数
所有输出流
fread
?进制输?
?件
fwrite
?进制输出
?件

那好我们来一个一个介绍:打开网站:

https://legacy.cplusplus.com/reference/clibrary/

(1)fputc

参数是:文件名和流

代码实现:

#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pf = fopen("data.txt", "w");//写入文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fputc('a', pf);//写入一个字符'a'
	fputc('\n', pf);
	fputc('b', pf);
	fclose(pf);
	return 0;
}

fputc只能一个一个字符的输入,也可以写一个循环输入多个字符。

	for (i = 0; i < 26; i++) {
		fputc('a' + 1, pf);
		fputc('\n', pf);//写完换行
	}


(2)fgetc

现在先我们输入字符进“data.txt”文件中,比如说输入:“abcdefg",然后实现代码读操作

代码:

#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");//写入文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = fgetc(pf);//读取一个字符
	printf("%c", ch);//打印
	fclose(pf);
	pf = NULL;
	return 0;
}

具体操作和输出结果如下:

当然也可以写成循环的形式,但是要注意什么时候结束,比如说:

下面代码:

#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");//写入文件
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	int ch = 0;

	while ((ch = fgetc(pf)) != ' ') {

		printf("%c", ch);//打印
	}

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

两种情况:

这就要看文件文本句末了,避免造成死循环。

接下来我们学习一个复制文件内容的操作:

假设文件里面有代码

代码实现:

//从data.txt中读取数据
//写到data2.txt的文件中

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pfread = fopen("data.txt", "r");//读
	if (pfread == NULL)
	{
		perror("fopen->data1.txt");//报错详细一点
		return 1;
	}
	FILE* pfwrite = fopen("data2.txt", "w");//写
	if (pfwrite == NULL)
	{
		fclose(pfread);//如果出现错误先关闭打开文件
		pfread = NULL;
		perror("fopen->data2.txt");
		return 1;
	}
	//数据的读写(拷贝)
	int ch = 0;
	while ((ch = fgetc(pfread)) != EOF)
	{
		fputc(ch, pfwrite);//把ch写入pfwrite
	}

	fclose(pfread);
	fclose(pfwrite);

	return 0;
}

输出结果:

(3)fputs

代码实现:

#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		return 1;
	}
	//写文件 - 写一行
	fputs("abcdef\n", pf);
	fputs("abcdef\n", pf);
	fputs("abcdef\n", pf);
	fputs("abcdef\n", pf);


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

输出结果:

(4)fgets

三个参数:

1. 输入地的指针

2.输入个数,但是要-1因为最后要输入\0

3.流

代码实现:


#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		return 1;
	}
	//读取
	char arr[20] = "xxxxxxxxxxxxxxx";
	fgets(arr, 10, pf);//从流中读取9个字符(有一个放入\0)放入arr中


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

输出结果:

当然也可以输出到屏幕上:

看代码:

#include<stdio.h>
#include<math.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		return 1;
	}
	//读取
	char arr[20] = "xxxxxxxxxxxxxxx";
	fgets(arr, 10, stdin);//标准输入流
	fputs(arr, stdout);//标准输出流

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

输出结果:

这里要注意的是输入流和输出流在函数的位置。

(5)fscanf和fprintf

fprintf代码实现:

#include<stdio.h>
#include<math.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = { "zhangsan", 20, 90.5f };
	FILE*pf = fopen("data.txt", "w");
	if (pf == NULL)
	{
		return 1;
	}
	//写文件
	//printf(""%s %d %.1f", s.name, s.age, s.score");//很相似
	fprintf(pf, "%s %d %.1f", s.name, s.age, s.score);

	//
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

同理:fscanf

假设文件里有:

代码:

#include<stdio.h>
#include<math.h>
struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = {0};
	FILE* pf = fopen("data.txt", "r");//读
	if (pf == NULL)
	{
		return 1;
	}
	//写读文件
	fscanf(pf, "%s %d %f", s.name, &(s.age), &(s.score));

	fprintf(stdout, "%s %d %.1f\n", s.name, s.age, s.score);//输出屏幕
	//
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

6.?fwrite二进制输出

首先我们先来学习一下这个函数:

代码实现:

#include<stdio.h>
#include<math.h>

struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = {"zhangsan", 20, 90.5};

	FILE* pf = fopen("data.txt", "wb");//wb是以二进制的方式写入
	if (pf == NULL)
	{
		return 1;
	}
	//二进制的形式写文件
	fwrite(&s, sizeof(s), 1, pf);
	
	fclose(pf);
	pf = NULL;

	return 0;
}

输出:

这里是以二进制输入的文本翻译成这样,是因为文本不具备二进制翻译,但是如果以二进制输出是和输入一样的结果,下面我们把刚刚以二进制输入文件以二进制输出看看。

7.fread以二进制输出

这里我们可以看到,参数和上面的fwite是一样的。

代码实现:

#include<stdio.h>
#include<math.h>

struct Stu
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct Stu s = {0};

	FILE* pf = fopen("data.txt", "rb");//rb以二进制形式读进
	if (pf == NULL)
	{
		return 1;
	}
	//二进制的形式du文件
	fread(&s, sizeof(s), 1, pf);
	printf("%s %d %lf", s.name , s.age, s.score );
	
	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

在这里我们在认识两个函数sscanf和sprintf.

8.sscanf和sprintf.

对比:

使用:

#include <stdio.h>

struct S
{
	char name[20];
	int age;
	float score;
};

int main()
{
	struct S s = { "zhangsan", 20, 85.5f };
	struct S tmp = { 0 };

	char arr[100] = { 0 };
	sprintf(arr, "%s %d %f", s.name, s.age, s.score);//把结构体s的数据输入arr
	printf("%s\n", arr);

	//
	sscanf(arr, "%s %d %f", tmp.name, &(tmp.age), &(tmp.score));//把arr结构体数据输入tmp
	printf("%s %d %f\n", tmp.name, tmp.age, tmp.score);

	return 0;
}

这里要慢慢理解,

6. ?件的随机读写

6.1 fseek和ftell

根据?件指针的位置和偏移量来定位?件指针。

什么意思,来

6.2 ftell

返回?件指针相对于起始位置的偏移量。

函数内容:

long int ftell ( FILE * stream );

现在我们来实现fseek和ftell

代码展示:

#include <stdio.h>

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

	int ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);//让光标指向d

	int n = ftell(pf);//计算相对起始位置的偏移量并保存
	printf("%d\n", n);

	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

再看看

这个:

#include <stdio.h>

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

	int ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);//让光标指向d

	int n = ftell(pf);//计算相对起始位置的偏移量并保存
	printf("%d\n", n);

	fseek(pf, -4, SEEK_CUR);//当前位置
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

怎么理解:

6.3 rewind

作用:让?件指针的位置回到?件的起始位置

代码展示:

#include <stdio.h>
//data.txt里面有abcdefg
int main()
{
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}

	int ch = fgetc(pf);
	printf("%c\n", ch);

	ch = fgetc(pf);
	printf("%c\n", ch);//光标指向c

	rewind(pf);//让?件指针的位置回到?件的起始位置

	ch = fgetc(pf);
	printf("%c\n", ch);

	fclose(pf);
	pf = NULL;

	return 0;
}

输出结果:

7. ?件读取结束的判定

7.1 被错误使?的 feof

牢记:在?件读取过程中,不能?feof函数的返回值直接来判断?件的是否结束。
feof 的作?是:当?件读取结束的时候,判断是读取结束的原因是否是:遇到?件尾结束。
1. ?本?件读取是否结束,判断返回值是否为 EOF fgetc ),或者 NULL fgets
例如:
? fgetc 判断是否为 EOF .
? fgets 判断返回值是否为 NULL .
2. ?进制?件的读取结束判断,判断返回值是否?于实际要读的个数。
比如:
? fread判断返回值是否?于实际要读的个数。
?本?件的例?:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
	int c; // 注意:int,?char,要求处理EOF
	FILE* fp = fopen("test.txt", "r");
	if (!fp) {
		perror("File opening failed");
		return EXIT_FAILURE;//1
	}
	//fgetc 当读取失败的时候或者遇到?件结束的时候,都会返回EOF
	while ((c = fgetc(fp)) != EOF) // 标准C I/O读取?件循环
	{
		putchar(c);
	}
	//判断是什么原因结束的
	if (ferror(fp))//遇到错误结束
		puts("I/O error when reading");
	else if (feof(fp))//遇到文末结束
		puts("End of file reached successfully");
	fclose(fp);
}
?进制?件的例?:
#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))//yudao文末结束
			printf("Error reading test.bin: unexpected end of file\n");
		else if (ferror(fp)) {//遇到错误结束
			perror("Error reading test.bin");
		}
	}
	fclose(fp);
}

8. ?件缓冲区

ANSIC 标准采?“缓冲?件系统”处理的数据?件的,所谓缓冲?件系统是指 系统?动地在内存中为
程序中每?个正在使?的?件开辟?块“?件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓
冲区,装满缓冲区后才?起送到磁盘上。如果从磁盘向计算机读?数据,则从磁盘?件中读取数据输 ?到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区 (程序变量等)。缓 冲区的??根据C编译系统决定的。
例子体验缓冲区的存在:
#include <stdio.h>
#include <windows.h>
//VS2019 WIN11环境测试
int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
	printf("睡眠10秒-已经写数据了,打开test.txt?件,发现?件没有内容\n");
	Sleep(10000);
	printf("刷新缓冲区\n");
	fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到?件(磁盘)
	//注:fflush 在?版本的VS上不能使?了
	printf("再睡眠10秒-此时,再次打开test.txt?件,?件有内容了\n");
	Sleep(10000);
	fclose(pf);
	//注:fclose在关闭?件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}
这?可以得出?个结论:
因为有缓冲区的存在,C语?在操作?件的时候,需要做刷新缓冲区或者在?件操作结束的时候关闭?件,如果不做,可能导致读写?件的问题。
好啦今天就到这里了,都看到这里了,点一个赞吧,感谢观看。

文章来源:https://blog.csdn.net/chendemingxxx/article/details/135064766
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。