【通关文件操作(上)】--文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写

发布于:2025-05-28 ⋅ 阅读:(14) ⋅ 点赞:(0)

目录

一.文件的意义和概念

1.1--为什么要使用文件?&& 什么是文件?

1.2--程序文件 && 数据文件

1.3--文件名

二.二进制文件和文本文件

三.文件的打开和关闭

3.1--流和标准流

3.1.1--流

3.1.2--标准流

3.2--文件指针

3.3--文件的打开和关闭

3.3.1--fopen函数

3.3.2--fclose函数

四.文件的顺序读写

4.1--fputc函数

4.2--fgetc函数

4.3--feof和ferror函数

4.4--fputs函数

4.5--fgets函数

4.6--fprintf函数

4.7--fscanf函数


🔥个人主页:@草莓熊Lotso的个人主页

🎬作者简介:C++研发方向学习者

📖个人专栏:《C语言》

⭐️人生格言:生活是默默的坚持,毅力是永久的享受。


一.文件的意义和概念

1.1--为什么要使用文件?&& 什么是文件?

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

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

1.2--程序文件 && 数据文件

程序文件:
  • 程序文件包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)

数据文件:

  •  文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
--本篇文章主要介绍的是数据文件,在之前的学习中我们所处理数据的输入输出都是以终端为对象的 即从终端的键盘输入数据,运行结果显示到屏幕上。
但其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件

1.3--文件名

--一个文件要有唯一个的文件标识,以便我们识别和使用。

文件名包含3个部分:文件路径+文件名主干+文件后缀

例如:D:\code\test.txt


二.二进制文件和文本文件

根据数据的组织形式,数据文件被分为文本文件和二进制文件。
  1. 数据在内存中以⼆进制的形式存储,如果不加转换的输出到外存的文件中,就是二进制文件。
  2. 如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
一个数据在文件中如何存储呢?
  • 字符⼀律以ASCII形式存储,数值型数据既可以用ASCII形式存储,也可以使用二进制形式存储。
  • 如有整数10000,如果以ASCII码的形式输出到磁盘,则磁盘中占用5个字节(每个字符⼀个字节),而二进制形式输出,则在磁盘上只占4个字节。

代码演示:

#include <stdio.h>

int main()
{
	int a = 10000;
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	fwrite(&a, 4, 1, pf);//二进制的形式写到文件中

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

我们默认打开的会是文本文件,观察不了二进制形式,所以我们需要在VS上打开二进制文件 ,具体操作如下:

我们把data.txt的打开方式设置为二进制编辑器,就可以观察二进制文件了。

 


三.文件的打开和关闭

3.1--流和标准流

3.1.1--流

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

3.1.2--标准流

那为什么我们从键盘输入数据,向屏幕上输出数据,并没有打开流呢?
这是因为C语言程序在启动的时候,默认打开了3个流:
  • stdin - 标准输入流,在大多数的环境中从键盘输入,scanf函数就是从标准输入流中读取数据。
  • stdout - 标准输出流,大多数的环境中输出至显示器界面,printf函数就是将信息输出到标准输出
  • 流中。
  • stderr - 标准错误流,大多数环境中输出到显示器界面。

这就是是默认打开的三个流,所以我们使用scanf、printf等函数就可以直接进行输入输出操作。stdin、stdout、stderr 三个流的类型是: FILE * ,通常称为文件指针。 C语言中,就是通过 FILE* 的文件指针来维护流的各种操作的。

3.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 ;
不同的编译器FILE类型包含的内容不完全相同,每次打开文件,系统都会自动创建一个FILE结构的变量,并填充其中信息,我们不需要关心这个
我们一般都是通过一个FILE指针来维护这个结构变量,这样使用起来更加方便。
创建形式如下:
FILE* pf; //文 件指针变量
定义pf是⼀个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是以个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够间接找到与它关联的文件

 

3.3--文件的打开和关闭

--文件在读写之前应该先打开文件,在使用结束之后应该关闭文件。

在编写程序的时候,在打开文件的同时,都会返回⼀个FILE*的指针变量指向该⽂件,也相当于建立了指针和文件的关系。

我们使用  fopen 函数来打开文件, fclose 来关闭文件。

3.3.1--fopen函数

//打开文件
1. FILE * fopen ( const char * filename ,   const char * mode );

功能:

fopen函数是用来打开参数filename所指定的文件的,同时将其和一个流进行关联,后续对流的操作是通关fopen函数返回的指针来维护的。具体对流的操作是通过参数mode指定的

参数:

  • filename:表示被打开的文件的名字,这个名字可以是绝对路径,也可以是相对路径。
  • mode:表示对打开文件的操作方式,后面会有具体解析

返回值:

  • 如果文件成功打开,该函数返回一个指向FILE对象的指针,该指针可以用于后续操作中标识对应的流
  • 如果打开失败,则返回NULL指针,所以我们要对fopen的返回值进行判断,来验证文件是否成功打开

代码演示:这里演示5种不同情况

//1.data.txt在当前工程的目录下
int main()
{
	FILE* pf = fopen("data.txt", "r");
	//这里的路径是相对路径,表示在当前的工程目录下的data.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件

	//关闭文件

	return 0;
}


//2. . --表示当前路径, .. --表示上一级路径
//所以如果data.txt在上一级路径下的时候应该这样写
int main()
{
	FILE* pf = fopen("./../data.txt", "r");
	//这里的路径是相对路径,表示在当前的工程目录下的data.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件

	//关闭文件

	return 0;
}


//3.如果data.txt在上二级路径下的时候应该这样写
int main()
{
	FILE* pf = fopen("./../../data.txt", "r");
	//这里的路径是相对路径,表示在当前的工程目录下的data.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件

	//关闭文件

	return 0;
}


//4.如果data.txt在上二级路径下的文件夹text中的text2文件夹里的时候应该这样写
int main()
{
	FILE* pf = fopen("./../../text/text2/data.txt", "r");
	//这里的路径是相对路径,表示在当前的工程目录下的data.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件

	//关闭文件

	return 0;
}


//5.如果data.txt在桌面上,就得用绝对路径了
int main()
{
	FILE* pf = fopen("C:/Users/LOTSO/Desktop/data.txt", "r");
	//这里的路径是绝对路径
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件

	//关闭文件

	return 0;
}


mode--文件操作方式:

文件使用方式 含义 如果指定文件不存在
"r"(只读)
为了输入数据,打开一个已经存在的文本文件
出错
"w"(只写)
为了输出数据,打开⼀个文本文件 
建立⼀个新的文件
“a”(追加)
向文本文件尾添加数据
建立⼀个新的文件
“rb”(只读)
为了输入数据,打开⼀个二进制文件
出错
"wb"(只写)
为了输出数据,打开⼀个二进制文件
建立⼀个新的文件
“ab”(追加)
向⼀个二进制文件尾添加数据
建立⼀个新的文件
“r+”(读写)
为了读和写,打开⼀个文本文件
出错
“w+”(读写)
为了读和写 建立⼀个新的文件
建立一个新的文件
“a+”(读写)
打开⼀个文件,在文件尾进行读写
建立一个新的文件
“rb+”(读写)
为了读和写打开⼀个二进制文件
出错
“wb+”(读
写)
为了读和写,新建⼀个新的二进制文件
建立一个新的文件
“ab+”(读
写)
打开⼀个二进制文件,在文件尾进行读和写
建立一个新的文件
----------------- ------------------------------------------------------ ------------------------

3.3.2--fclose函数

1. int fclose ( FILE * stream );

功能:
关闭参数 stream 关联的文件,并取消其关联关系。与该流关联的所有内部缓冲区均会解除关联并刷新:任何未写入的输出缓冲区内容将被写入,任何未读取的输入缓冲区内容将被丢弃。
参数:
  • stream :指向要关闭的流的 FILE 对象的指针
返回值:成功关闭 stream 指向的流会返回0,否则会返回 EOF

代码演示:

#include<stdio.h>

int main()
{
	FILE* pf = fopen("data.txt", "r");
	//这里的路径是相对路径,表示在当前的工程目录下的data.txt
	if (pf == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读取文件

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

四.文件的顺序读写

在进行文件写的时候,我们会涉及下面这些函数:

函数名 功能 适用于
fgetc 从输入流中读取一个字符 所有输入流
fputc   向输出流中写入一个字符 所有输出流
fgets 从输入流中读取一个字符串 所有输入流
fputs 向输出流中写入一个字符串 所有输出流
fscanf 从输入流中读取带有格式的数据 所有输入流
fprintf 向输出流中写入带有格式的数据 所以输出流
fread 从输入流中读取一块数据 文件输入流
fwrite 从输出流中写入一块数据 文件输出流

4.1--fputc函数

1. int fputc ( int character, FILE * stream );

功能:
将参数 character 指定的字符写入到 stream 指向的输出流中,通常用于向文件或标准输出流写入字符。在写入字符之后,还会调整指示器。字符会被写入流内部位置指示器当前指向的位置,随后该指示器自动向前移动⼀个位置。
参数
  • character :被写入的字符
  • stream :是⼀个FILE*类型的指针,指向了输出流(通常是文件流或stdout)。
返回值:
  • 成功时返回写入的字符(以 int 形式)。
  • 失败时返回 EOF (通常是 -1),错误指示器会被设置,可通过 ferror() 检查具体错误。

代码演示:

//fputc函数演示
#include<stdio.h>
int main()
{
	FILE* ps = fopen("data.txt", "w");//w--写,用这个才可以写文件
	if (ps == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputc('a', ps);
	fputc('b', ps);
	fputc('c', ps);

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


//循环写入多个字符
int main()
{
	FILE* ps = fopen("data.txt", "w");//w--写,用这个才可以写文件
	if (ps == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	for (int i = 'a';i <= 'z';i++)
	{
		fputc(i, ps);
	}

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

//直接打印在屏幕上,标准输出流
#include<stdio.h>
int main()
{
	fputc('a', stdout);
	fputc('b', stdout);
	fputc('c', stdout);
	return 0;
}

4.2--fgetc函数

1.   int fgetc ( FILE * stream );
功能:
从参数 stream 指向的流中读取一个字符。函数返回的是文件指示器当前指向的字符,读取这个字符之后,文件指示器自动前进道下⼀个字符。
参数:
  • stream : FILE*类型的文件指针,可以是 stdin ,也可以是其他输入流的指针。如果是 stdin 就从标准输入流读取数据。如果是文件流指针,就从文件读取数据。
返回值:
  • 成功时返回读取的字符(以 int 形式)。
  • 若调用时流已处于文件末尾,函数返回 EOF 并设置流的文件结束指示器( feof )。
  • 若发生读取错误,函数返回 EOF 并设置流的错误指示器( ferror )。 

 代码演示: 

//fgetc函数演示,假设文件里是26个字母
#include<stdio.h>
int main()
{
	FILE* ps = fopen("data.txt", "r");
	if (ps == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	for (int i = 0;i < 10;i++)
	{
		int c=fgetc(ps);
		fputc(c, stdout);

	}
	//关闭文件
	fclose(ps);
	ps = NULL;

	return 0;
}

从键盘读取数据
#include<stdio.h>
int main()
{
	//读数据
	    int c=fgetc(stdin);
		fputc(c, stdout);

	return 0;
}

4.3--feof和ferror函数

int feof ( FILE * stream );
// 检测 stream 指针指向的流是否遇到文件末尾
int ferror ( FILE * stream );
// 检测 stream 指针指向的流是否发生读 / 写错误
  • 如果在读取文件的过程中,遇到了文件末尾,文件读取就会结束。这时读取函数会在对应的流上设置⼀个文件结束的指示符,这个为文件结束指示符可以通过 feof 函数检测到。如果 feof 函数检测到文件结束指示符已经被设置,则返回非0的值,如果没有设置则返回0。
  • 如果在读/写文件的过程中,发生了读/写错误,文件读取就会结束。这时读/写函数会在对应的流上设置⼀个错误指示符,这个错误指示符可以通过 ferror 函数检测到。如果 ferror 函数检测到错误指示符已经被设置,则返回非0的值,如果没有设置则返回0。

测试feof: 

//测试feof,假设文件里是abcdef
#include<stdio.h>
int main()
{
	FILE* ps = fopen("data.txt", "r");
	if (ps == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int i = 0;
	for (i = 0;i < 10;i++)
	{
		int c = fgetc(ps);
		if (c == EOF)
		{
			if (feof(ps))
			{
				printf("遇到文件结尾了\n");//读到f就结尾了
			}
			else if (ferror(ps))
			{
				printf("读取发生错误\n");
			}
		}
		else
		{
			fputc(c, stdout);
		}
	}
	fclose(ps);
	ps = NULL;
	return 0;
}

 测试ferror:

//测试ferror,
// 以写的形式打开文件后,后面再读文件,就会发生错误
#include<stdio.h>
int main()
{
	FILE* ps = fopen("data.txt", "w");
	if (ps == NULL)
	{
		perror("fopen");
		return 1;
	}
	//读文件
	int i = 0;
	for (i = 0;i < 6;i++)
	{
		int c = fgetc(ps);
		if (c == EOF)
		{
			if (feof(ps))
			{
				printf("遇到文件结尾了\n");
			}
			else if (ferror(ps))
			{
				printf("读取文件错误\n");
			}
		}
		else
		{
			fputc(c, stdout);
		}
	}
	fclose(ps);
	ps = NULL;
	return 0;
}

4.4--fputs函数

1. int fputs ( const char * str, FILE * stream );

功能:
将参数 str 指向的字符串写入到参数 stream 指定的流中(不包含结尾的空字符 \0 ),适用于文件流或标准输出(stdout)。
参数:
  • str : str是指针,指向了要写入的字符串(必须以 \0 结尾)
  • stream :是⼀个 FILE* 的指针,指向了要写入字符串的流。
返回值:
  • 成功时返回非负整数。
  • 失败时返回 EOF (-1),同时会设置流的错误指示器,可以使用 ferror() 检查错误原因。

代码演示:

//fputs函数演示
#include<stdio.h>
int main()
{
	FILE* ps = fopen("data.txt", "w");
	if (ps == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fputs("abc\n", ps);
	fputs("def", ps);

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

和fputc函数一样,我们也可以直接打印在屏幕上,把ps改成stdout就可以,这里就不再演示了。 

4.5--fgets函数

1.  char * fgets ( char * str, int num, FILE * stream );

功能:
stream 指定输入流中读取字符串,至读取到换行符、文件末尾(EOF)或达到指定字符数
(包含结尾的空字符 \0 ),然后将读取到的字符串存储到str指向的空间中。
参数:
  • str :是指向字符数组的指针,str指向的空间用于存储读取到的字符串。
  • num :最大读取字符数(包含结尾的 \0 ,实际最多读取 num-1 个字符)。
  • stream :输入流的文件指针(如文件流或 stdin )。
返回值:
  • 成功时返回 str 指针。
  • 若在尝试读取字符时遇到⽂件末尾,则设置文件结束指示器,并返回 NULL ,需通过 feof()检测。
  • 若发生读取错误,则设置流错误指示器,并返回 NULL,通过 ferror() 检测。

 代码演示:

// fgets函数演示,假设文件里还是上面fputs函数写的 abc\n def
#include<stdio.h>
int main()
{
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	char arr1[] = "*************";
	/*char* f = fgets(arr1, sizeof(arr1), fp);
	fputs(f, stdout);*///全部打印出来abc

	char*p=fgets(arr1,2, fp);//实际读取的是num-1个
	fputs(p,stdout);//所以是a

	//不再使用文件时,需要关闭文件
	fclose(fp);
	fp = NULL; //将指针置为NULL,避免成为野指针。
	return 0;
}

我们再来看一个例子并且通过调试以及打印在屏幕上来观察: 

//把abc\n def全读出来,调试着看并打印在屏幕上
#include <stdio.h>
int main()
{
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen\n");
	    return 1;
	}
	char arr1[] = "*************";
	char*p=fgets(arr1, sizeof(arr1), fp);
	fputs(p, stdout);

	char arr2[] = "*************";
	char* f = fgets(arr2, sizeof(arr1), fp);
	fputs(f, stdout);

	//不再使用文件时,需要关闭文件
	fclose(fp);
	fp = NULL; //将指针置为NULL,避免成为野指针。
	return 0;
}

 

 和fgetc函数一样,我们也可以直接从键盘上读取,把fp改成stdin就可以,这里也不再演示了。

4.6--fprintf函数

1.   int fprintf ( FILE * stream, const char * format, ... );

2.  //我们类比printf看看

3.  int  printf ( const char * format, ... );

功能:
fprintf 是将格式化数据写入指定文件流的函数。它与 printf 类似,但可以输出到任意文件(如磁盘文件、标准输出、标准错误等),而不仅限于控制台。
参数:
  • stream :指向 FILE 对象的指针,表⽰要写⼊的⽂件流(如 stdout 、文件指针等)。
  • format :格式化字符串,包含要写⼊的⽂本和格式说明符(如 %d %s 等)。
  • ... :可变参数列表,提供与格式字符串中说明符对应的数据。
返回值:
  • 成功时,返回写入的字符总数(非负值)。
  • 失败时,先设置对应流的错误指示器,再返回负值,可以通过 ferror() 来检测。

代码演示:

// fprintf函数演示
#include <stdio.h>
struct stu
{
	char name[30];
	int age;
	float score;
};
int main()
{
	struct stu s = { "zhangsan",18,95.5f};
	FILE* fp = fopen("data.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fprintf(fp, "%s %d %f", s.name, s.age, s.score);

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

当然这也可以直接打印在屏幕上,还是把fp换成stdout,这样效果等同于printf,这里就不演示了。

4.7--fscanf函数

1.  int fscanf ( FILE * stream, const char * format, ... );

2. //类比scanf看看

3.   int scanf const char * format, ... );

功能:
fscanf 是从指定文件流中读取格式化数据的函数。它类似于 scanf ,但可以指定输入源(如文件、标准输入等),而非仅限于控制台输⼊。适用于从文件解析结构化数据(如整数、浮点数、字符串等)。
参数:
  • stream :指向 FILE 对象的指针,表⽰要读取的⽂件流(如 stdin 、文件指针等)。
  • format :格式化字符串,定义如何解析输⼊数据(如 %d %f %s 等)。
  • ... :可变参数列表,提供存储数据的变量地址(需与格式字符串中的说明符匹配)。
返回值:
成功时,函数返回成功填充到参数列表中的项数。该值可能与预期项数⼀致,也可能因以下原因少
于预期(甚至为零):
  • 格式和数据匹配失败;
  • 读取发生错误;
  • 到达文件末尾(EOF)。
如果在成功读取任何数据之前发生:
  • 发生读取错误,会在对应流上设置错误指示符,则返回 EOF
  • 到达文件末尾,会在对应流上设置文件结束指示符,则返回 EOF

代码演示:

//fsanf函数演示
struct stu
{
	char name[30];
	int age;
	float score;
};
int main()
{
	struct stu s = { 0 };
	FILE* fp = fopen("data.txt", "r");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}
	//写文件
	fscanf(fp, "%s %d %f", s.name, &(s.age), &(s.score));
	fprintf(stdout, "%s %d %f", s.name, s.age, s.score);
	//打印数据在stdout
	
	//关闭文件
	fclose(fp);
	fp = NULL;
	return 0;
}

当然这也可以直接从键盘上读取,还是把fp换成stdin,这样效果等同于scanf,这里就不演示了。 

--由于篇幅原因,后面还有几个函数,我们在下篇文章再一起来继续学习。


往期回顾:

【C语言动态内存管理】--动态内存分配的意义,malloc和free,calloc和realloc,常见的动态内存的错误,动态内存经典笔试题分析,柔性数组,总结C/C++中程序内存区域划分

【自定义类型-结构体】--结构体类型,结构体变量的创建和初始化,结构体内存对齐,结构体传参,结构体实现位段

【自定义类型-联合和枚举】--联合体类型,联合体大小的计算,枚举类型,枚举类型的使用 

结语:本篇文章就到此结束了,继前面一篇文章后,在此篇文章中给大家分享了文件操作中的文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写(部分)等知识点,下篇文章会接着从文件的顺序读写后面的几个函数开始。分析文件操作中的剩余知识点,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。


网站公告

今日签到

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