C语言进阶——文件操作

1. 为什么使用文件

在我们实现通讯录时,我们发现两个问题,第二个问题就是:
每次退出程序,数据并不会保存,这样无法达到我们保存联系人的目的。
今天我们就来学习一下解决第二个问题所需要的知识。
想解决程序退出时,内存中存放的数据全都不见了的问题,这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。
使用文件就是将数据直接存放在电脑的硬盘上,也就实现了数据的持久化。

2. 什么是文件

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

2.1 程序文件

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

2.2 数据文件

文件的内容不一定是程序,还有可能是程序运行时读写的数据,
比如程序运行需要从中读取数据的文件,或者输出内容的文件。
今天我们讲解的就是如何用C语言的代码操作数据文件。

之前我们所学习的处理数据的输入输出都是以终端为对象的,即:
从终端的键盘输入数据,运行结果显示到显示器上。

其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上的文件。

2.3 文件名

同一个路径下是不可能存在同名的文件的,所以文件名是文件的唯一标识,以便用户识别和引用。
为了方便起见,文件标识常被称为文件名。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
这里需要注意,文件夹中的这个选项最好是勾选上。
在这里插入图片描述
不然当你创建文件时,可能会发生预期之外的事情。
在这里插入图片描述

3. 文件的打开和关闭

3.1 文件指针

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。
每个被使用的 文件 都在内存中开辟了一个相应的 文件信息区 ,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。
这些信息是保存在一个结构体变量中的。该 结构体类型 是由系统声明的,取名 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 。
不同的C编译器的FILE类型包含的内容不完全相同,具体取决于编译器的实现,但是都大同小异。
每当打开一个文件的时候,系统会根据文件的情况自动创建一个FILE结构的变量,并填充其中的信息,使用者不必关心细节

3.2 如何访问文件?

当我们想打开一个文件时,我们该如何访问文件呢?
打算读写一个文件的时候

  1. 打开文件
  2. 被打开的文件就维护了一个文件信息区
    在这里插入图片描述

一般都是通过一个指向文件信息区的指针来维护这个FILE结构的变量,这样使用起来更加方便。
下面我们可以创建一个FILE*的指针变量:

FILE* pf;//文件指针变量

定义 pf 是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。文件和文件信息区是相互关联的,通过该文件信息区中的信息就能够访问该文件
也就是说,通过文件指针就能够找到与它关联的文件

一种特殊的理解:

如果还是不懂可以尝试这样理解,把文件和文件信息区看作一个整体,一个文件指针指向一个文件的文件信息区,而通过文件信息区就能够访问该文件。
也就是说通过文件指针就能够访问:该文件指针指向的文件信息区所关联的文件。约等于文件指针指向一个文件
(这种说法并不严谨,但是可以这样想象便于理解,事实上文件指针并不指向文件本身,而是指向文件对应的文件信息区)
在这里插入图片描述

3.3 文件的打开和关闭

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

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

ANSIC 规定使用fopen函数来打开文件,fclose来关闭文件。

//打开文件
FILE * fopen ( const char * filename, const char * mode );
//关闭文件
int fclose ( FILE * stream );

fopen函数的参数和返回值:

  1. 第一个参数:字符指针类型,以字符串的形式传入文件名
  2. 第二个参数:字符指针类型,以字符串的形式传入文件使用方式
  3. 返回值:如果打开成功,返回FILE*类型的指针;
    (fopen函数返回该文件对应的文件信息区的起始地址)

注意:文件是有可能打开失败的,如果打开失败,fopen函数返回空指针。

文件名和文件使用方式都是字符串,所以参数类型为char*,接收字符串的首字母的地址。
使用fopen函数可以获得想打开的文件指向该文件对应的文件信息区文件指针。(fopen函数返回该文件对应的文件信息区的起始地址)
简单可以理解为:fopen函数 可以获得指向该文件的指针。

fclose函数的参数介绍:

FILE*类型的文件指针,这个指针被抽象成数据流。
fclose传参传文件指针就可以了,

  1. fclose(pf); 的含义就是关闭 pf 所指向的 文件信息区 对应的 文件
  2. pf = NULL; 但是fclose函数不会把文件指针置空,后面依然需要手动将文件指针置空。

这里可以类比:动态开辟空间后,free释放空间时,也不会把指针置空,如果不手动将指针置空,该指针将成为野指针。

文件的使用方式

文件的使用方式不止于此,今天先介绍这么多:

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

3.4 使用文件的完整流程

我们来看一下使用文件的完整代码:

//打开文件的流程
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt","w");
	if (pf == NULL)//判断pf是否为空指针(文件是否打开失败)
	{
		perror("fopen");
		return 1;//文件打开失败,程序不再继续往下走
	}
	//文件打开成功

	//进行读写操作

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

看到这段代码,有没有觉得似曾相识,感觉在哪里见到过。
往期我们讲动态内存管理的时候讲到动态内存管理函数的使用时,是不是跟这里的打开文件的流程类似。
回忆一下:
在这里插入图片描述
为了便于理解,我画了一个简易流程图:
在这里插入图片描述

3.5 关于文件的路径

  1. 相对路径:如果没有给出文件路径,就在当前程序路径下。
    .\ \当前目录下
    …\ \上一级目录下
    .\ \ debug 上一级目录下的debug文件夹中
  2. 绝对路径:文件的完整路径。
    在字符串内部输入路径时,需要避免斜杠被当做转义字符,
    所以把\ 转义为字符串内的\ ,也就是\ \ 。
    假设我想打开我的电脑中桌面的data.txt文件:
    在这里插入图片描述
FILE* pf = fopen("C:\\Users\\XN\\Desktop\\data.txt","r");
//路径的 \ 符号不能只写一个,因为在字符串内部会被当做转义字符

3.6 关于流的概念

我们在使用fopen函数打开文件的时候,我们有三个步骤:

  1. 打开文件
  2. 读写文件
  3. 关闭文件

但是我们之前用printf、scanf函数的时候好像并没有需要打开屏幕,打开键盘这种操作。这是为什么呢?

这是因为C语言程序在运行时,默认打开三个流:

  1. 标准输入流 — stdin
  2. 标准输出流 — stdout
  3. 标准错误流 — stderr
    以上三个流,均为FILE* 类型

那么什么是流呢?
流,其实是一个抽象的概念,比如:有一个水流,可以从水流中取水出来,也可以灌水进去。
在这里插入图片描述
我们可以理解为:在编写程序时,程序处理的是数据流。
在读写数据时,计算机所涉及的外部设备是非常多的,例如:

  1. 输入数据,我们从键盘输入
  2. 输入数据,从文件中读取出来
  3. 输入数据,从摄像头读取
  4. 输出数据,我们从屏幕显示
  5. 输出数据,写入到文件中去
  6. 输出数据,从喇叭输出
  7. 等等…

如果让程序员操作各种各样的外部设备,编写程序的难度就比较高了。

为了简化程序员编写程序的难度,就出现了数据流的概念,数据流负责和各种外部设备完成交互,程序员并不需要关心数据流跟各种外部设备之间的交互。

  1. 需要用数据的时候,就从数据流中取;
  2. 需要存数据的时候,就存进数据流中去。

在这里插入图片描述

这就大大减轻了程序员的学习成本,而数据流和各种设备的交互由C语言和操作系统取完成的,我们并不需要关心。

4. 文件的顺序读写

读取和写入的理解

在讲解文件操作函数之前,先给大家讲解一下几个容易混淆的概念:
输入、输出、读取、写入
输入输出都是针对程序而言的
通过下图可得:

  1. 从键盘输入数据 ------ 输入操作
  2. 打印数据到屏幕 ------ 输出操作
  3. 从文件读取数据 ------ 输入操作
  4. 写入数据到文件 ------ 输出操作
    在这里插入图片描述

4.1 顺序读写函数介绍

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

这里所说的适用于所有流,是指,标准输入输出流,文件流都可以。

4.1.1 fputc函数的使用

int fputc ( int character, FILE * stream );
(1)函数的作用:

Write character to stream
Writes a character to the stream and advances the position indicator.
写一个字符到流(这个流指的是数据流)里去。

(2)代码样例:
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt","w");
	if (pf == NULL)//判断pf是否为空指针(文件是否打开失败)
	{
		perror("fopen");
		return 1;//文件打开失败,程序不再继续往下走
	}
	//文件打开成功
	//进行读写操作 -- 写文件
	fputc('a', pf);
	fputc('b', pf);
	fputc('c', pf);
	//关闭文件
	fclose(pf);
	pf = NULL;
	return 0;
}

fputc 函数的第二个参数:
传入刚刚 fopen 函数打开的文件流,也就是pf指针。
运行代码:
在这里插入图片描述
程序没有任何输出,但是我们可以在当前目录下找到data.txt文件

(3)查看data.txt文件:

请添加图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
打开data.txt文件我们可以看到,里面成功写入三个字符,a,b,c
在这里插入图片描述
fputc函数,是按照顺序逐个写入字符的,所以叫顺序读写函数。
怎样做到顺序读写的呢,其实在文件内部有一个文件指针,文件指针默认指向起始位置。

当进行读写操作时,文件指针会进行偏移,在fputc的场景下,也就是写完一个字符文件指针就偏移到下个位置,这就做到了顺序读写。
当写完c字符时,文件指针偏移到c字符的下一个字符的位置。
在这里插入图片描述
如果,我们想再屏幕上显示这三个字符,而不是写入到文件中去,该怎么做呢?
只需要把文件流改为标准输出流就好了,这样再次运行代码,abc将被打印到屏幕上。

	fputc('a', stdout);
	fputc('b', stdout);
	fputc('c', stdout);

在这里插入图片描述

4.1.2 fgetc函数的使用

int fgetc ( FILE * stream );
(1)函数的作用:

Get character from stream
从流里读取一个字符
Returns the character currently pointed by the internal file position indicator of the specified stream. The internal file position indicator is then advanced to the next character.
返回文件指针当前指向的字符,并且将文件指针移动到下一个字符。

(2)代码样例:
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)//判断pf是否为空指针(文件是否打开失败)
	{
		perror("fopen");
		return 1;//文件打开失败,程序不再继续往下走
	}
	//文件打开成功
	//进行读写操作 -- 读文件
	int arr[3] = { 0 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		arr[i] = fgetc(pf);
		printf("%c", arr[i]);
	}
	//关闭文件
	fclose(pf);
	pf = NULL;

	return 0;
}

我们在data.txt文件中存入abcdef
在这里插入图片描述

运行代码:
我们可以看到,读取3次的话,确确实实读取到了3个字符。
在这里插入图片描述
在这里插入图片描述
同样如果我们想从键盘中读取字符,只需要将这段代码
arr[i] = fgetc(pf);,改为arr[i] = fgetc(stdin);即可。
也就是把文件流改为标准输入流。
再次运行代码,我们发现程序在等待输入字符。
在这里插入图片描述
输入abc后,成功打印abc三个字符。
在这里插入图片描述

4.1.3 fputs函数的使用

(1)函数的作用:
int fputs ( const char * str, FILE * stream );

Write string to stream
把字符串写入流中去。

(2)代码样例:
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "w");
	if (pf == NULL)//判断pf是否为空指针(文件是否打开失败)
	{
		perror("fopen");
		return 1;//文件打开失败,程序不再继续往下走
	}
	//文件打开成功
	
	//写文件  --  写一行
	fputs("abcdef", pf);

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

	return 0;
}

运行代码后程序并没有输出,查看data.txt文件:
请添加图片描述
在这里插入图片描述
成功将字符串写入文件。
这里需要注意,当需要写入两个字符串并且需要换行时,需要手动换行,fputs并不会自动换行,例如:

fputs("abcdef\n", pf);
//如果还想输入一个字符串并且在abcdef的下一行,那么需要加上\n
fputs("hello\n", pf);

代码运行后查看data.txt文件:
在这里插入图片描述
fputs同样使用于所有输出流,所以如果需要将字符串打印到屏幕上去可以将文件流替换为 标准输出流stdout,和fputc同理,这里不再做示范。

4.1.4 fgets函数的使用

(1)函数的作用:
char * fgets ( char * str, int num, FILE * stream );

Get string from stream
从流中读取字符串,最多读取(num-1)个字符,并且存放到str指向的字符串中去。
为什么读取(num-1)个,我认为是给'\0'字符留一个位置。

(2)代码样例:
int main()
{
	//打开文件
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL)//判断pf是否为空指针(文件是否打开失败)
	{
		perror("fopen");
		return 1;//文件打开失败,程序不再继续往下走
	}
	//文件打开成功
	//读文件  --  读一行
	int arr[10] = { 0 };
	fgets(arr,10,pf);
	printf("%s", arr);

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

	return 0;
}

fgets函数有三种情况,会终止读取

  1. 当读取够num-1个字符时,自动停止
  2. 当读取到\n换行时,自动停止
  3. 当读取到文件末尾时,自动停止
当读取够num-1个字符时,自动停止

事先在data.txt文件中存入数据如下:
在这里插入图片描述
运行代码:
在这里插入图片描述
读取够num-1个字符后,不会继续向后读取。

当读取到\n换行时,自动停止

事先在data.txt文件中存入数据如下:
在这里插入图片描述
读取两次时,第二次遇见\n停止读取
在这里插入图片描述
读取第三次时,才会读取到abcdef
在这里插入图片描述

当读取到文件末尾时,自动停止

在这里插入图片描述
一共就7个字符,所以读取到最后一个时,自动停止读取。
运行代码:
在这里插入图片描述
fgets同样使用于所有输入流,所以如果需要将字符串打印到屏幕上去可以将文件流替换为 标准输出流stdin,和fgetc同理,这里不再做示范。

下面讲解这4个函数:

在这里插入图片描述

4.1.5 fscanf函数的使用

————————未完结
知识点太多了之后再补…QAQ,T_T

  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值