C语言文件操作——使数据能够长久保存的方法

在这里插入图片描述

前言

在我们接触文件操作之前,我们所有的操作都是在内存中进行的,当我们关闭程序,所有的数据就会消失,所以当我们需要将数据长久的保存下去时,就需要内存与文件进行交互,让数据存放在硬盘中。

文件的分类

在程序设计中,以文件的功能分为:程序文件、数据文件
前者:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
后者:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
而我们这里主要 讨论的是数据文件
在以前各章所处理数据的输入输出都是以终端为对象的,即从键盘输入数据,运行结果输出到屏幕上。
其实有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理的就是磁盘上文件。

文件名

一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\VS2022\test\test.txt (c:\VS2022\test\ + test + .txt)
为了方便起见,文件标识常被称为文件名。
文件名的核心是文件名主干,如果我们在对一个文件命名时,忘记后缀的话,我们只需要在打开文件时自行选择某种形式进行打开,也就是说一个文件可以没有后缀。

文件的打开和关闭

文件指针:

文件类型本质上是一个结构体类型(FILE),只不过这个结构体是由系统定义的,每个被使用的文件都会在内存中开辟一个相应的文件信息区(结构体所占的空间),存放了文件名、文件状态(打开关闭状态等)、文件位置等文件信息

所以当我们打开一个文件,向对文件进行读写操作时,就需要用一个FILE类型的指针去存储该文件信息区(结构体变量)的首地址。
FILE* pf//文件指针pf

打开文件的fopen函数声明
FILE* fopen ( const char* filename, const char* mode );
filename就是文件指针pf
mode是打开文件的方式,例如"r" - 只读 “w” - 只写,细节看下表
返回值就是文件指针的首地址
关闭文件fclose函数声明

int fclose ( FILE * stream );
stream 这里就把它简单理解为文件指针pf
返回值基本不会使用,关闭成功返回0,关闭失败返回EOF

下表是常用的一些文件使用方式含义及说明。
在这里插入图片描述
例子:

#include <stdio.h>
int main()
{
    // 打开文件
    // D:\\learning\\VS2022\\Local_warehouse\\learning_-c\\homework2.1\\homework2.1\\text.txt - 绝对路径
    // text.txt - 相对路径
    // 也就是说如果文件是在该程序文件夹下,就可以直接使用相对路径,如果文件是在其他路径下,那么就需要使用绝对路径
    // 而且这里我们使用双斜杠 \\ 是转义的问题
    FILE* pf = fopen("D:\\learning\\VS2022\\Local_warehouse\\learning_-c\\homework2.1\\homework2.1\\text.txt", "r");
    //检验打开文件是否成功
    if (pf == NULL)
    {
        perror("fopen");
        return 1;
    }    
    
    //文件操作...
    
    //关闭文件
    fclose(pf);
    pf = NULL;
    return 0;
}

相对路径的衍生知识:
在这里插入图片描述
…/ 是上一级目录的意思
在这里插入图片描述
当然,上一级目录是可以套娃的,也就是当文件是在上上级文件夹中,可以…/…/来进行寻找

顺序读写函数

在这里插入图片描述

fputc()和fgetc()

对单个字符进行操作
1、fputc()是将一个字符写入到文件中
int fputc (int character, FILE* stream);
character 写入的字符
stream 想要写入的文件的指针
返回值是返回写入字符的ASCII码,然后光标右移,但若失败,返回EOF,但是fputc一般不会用到返回值
在这里插入图片描述
2、fgetc()是从文件中读出一个字符
int fgetc (FILE* stream);
steam - 想要读取的文件的指针
返回值返回这个字符的ASCII码值(int),然后光标右移,若失败,返回EOF
在这里插入图片描述

fputs()和fgets()

对一行数据进行操作
1、fputs()将一串字符串,写入到stream文件中
int fputs(const char* str, FILE* stream);
str - 字符串
stream - 想要写入的文件的指针
在这里插入图片描述
在这里插入图片描述
2、fgets()从文件中最多读取num个数据,到str中,返回str的首地址,注意,在读取num个数据时,会用最后一个数据位置放’\0’
char* fgets(char* str, int num, FILE* stream)
str - 从文件中读取到的数据所放的字符指针
num - 需要注意的是,当文件中是abcdef时,我们num取3,str中只能存ab\0,而不是abc,当num取的很大,也只会在该行中读取,在一次读取之后,光标落在第一次读取的结尾,不会自动换行
stream - 数据来源文件
在这里插入图片描述
在这里插入图片描述

fscanf()和fprintf()

格式化的读写
1、fprintf - 按照格式,将内存中变量存储的数据写入文件
int fprintf(FILE* stream, const char* format, ...);
这里参数的...表示的是可变参数列表,也就是fprintf printf scanf fscanf的参数的个数是可以发生变化的(%d %s这些的对应参数总数的变化)
format 可能带着格式字符(%d %x %s…)的字符串
在这里插入图片描述

2、fscanf - 格式化的读取文件
int fscanf(FILE* stream, const char* format, ...);
将文件中的数据按照格式存入内存中的变量中
在这里插入图片描述
我们是站在内存的角度思考输入/输出函数的,我们把内容从键盘、文件中输入到内存,把内容从内存中输出到屏幕、文件中
用上面的方式去记忆哪些是输入函数哪些是输出函数
在这里插入图片描述

分析表中 适用于 - 所有输出输入流 的含义
在这里插入图片描述
在这里插入图片描述
C语言程序中的数据想要输入输出到各种设备中,需要了解各种设备的原理,所以为了降低编程学习难度,我们引入了流这个概念,并用FILE结构体定义、管理流,我们只需要将数据存入到流中即可,流与各种设备的交互问题C语言已经为我们封装处理好了
简单的说,流就是我们定义的pf变量,以及下面的stdin、stdout、stderr变量

任何一个C语言程序在运行时,会默认打开3个流:
stdin - 标准输入(键盘)类型:FILE*
stdout - 标准输出(屏幕)类型:FILE*
stderr - 标准错误(屏幕)类型:FILE*

所以表中的适用于所有输出输入流的函数,我们不仅可以用fgetc、fputc等函数对 文件 进行操作,还可以用这些函数对屏幕键盘进行操作。

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

fread()和fwrite()

仅针对文件流的二进制读写
1、fwrite()
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
将ptr指向的数据写入文件
ptr - 内存中的某种类型的数据的指针
size - 这是要被写入的每个元素的大小,以字节为单位
count - 这是元素的个数,每个元素的大小为 size 字节。
stream - 这是指向 FILE 的指针
注意:fwrite在进行了一次写入后,并不会自动移动文件中光标位置
在这里插入图片描述

	//所以常用这种方式定位内存中的数据的地址
	for (i = 0; i < con->size; i++)
	{
		fwrite(&data[i], sizeof(PeoInfo), 1, pf);
	}

2、fread()
size_t fwrite(void* ptr, size_t size, size_t count, FILE* stream);
ptr是存储读入数据的指针
size - 这是要被读入的每个元素的大小,以字节为单位
count - 这是元素的个数,每个元素的大小为 size 字节。
stream - 这是指向 FILE 的指针
读取数据后光标自动移动到上一次读取的末尾
在这里插入图片描述

	//一次实际运用
	PeoInfo tmp = { 0 };//创建的一个临时变量,去暂时存储读取的数据
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		check_capacity(con);//检查容量
		con->data[i] = tmp;//将数据存入con->data[i]中
		con->size++;//数据量加1
		i++;
	}

函数对比:
scanf/fscanf/sscanf
printf/fprintf/sprintf
在这里插入图片描述
sprintf() - 把一个格式化的数据,存放在一个字符串中
int sprintf(char* str, const char* format, ...);
交互对象是分别是:字符串与内存
在这里插入图片描述
sscanf() - 从一个字符串中,还原出一个格式化的数据
int sscanf(const char* s, const char* format, ...);
在这里插入图片描述

文件的随机读写函数

fseek()

调整光标在文件中的读写位置的函数
int fseek(FILE* stream, long int offset, int origin);
offset - 相对于origin位置的偏移量
origin - 只有三个取值,分别为SEEK_SET - 文件开头 SEEK_CUR - 文件当前位置 SEEK_END - 文件末尾
在这里插入图片描述

ftell()

返回文件指针相对于起始位置的偏移量
long int ftell(FILE* stream);
在这里插入图片描述
rewind() - 让文件指针的位置回到文件的起始位置
void rewind(FILE* stream);
当然可以用fseek(pf, 0, SEEK_SET);的方式进行,但是没有rewind()方便
在这里插入图片描述

文本文件和二进制文件

根据数据的组织形式,数据文件被称为文本文件或者二进制文件。
数据在内存中以二进制的形式存储,如果不加转换的输出到外存,就是二进制文件。
如果要求在外存上以ASCII码的形式存储,则需要在存储前转换。以ASCII字符的形式存储的文件就是文本文件。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
小端存储:
10000 =>
00000000 00000000 00100111 00010000 =>
00010000 00100111 00000000 00000000( 10 27 00 00 )
前面的00000000是想表示地址但是没有用

feof() 和 ferror()

文件读取结束原因的判定函数
feof(FILE* stream) - 返回真,就说明是文件正常读取遇到了结束标志而结束的
ferror(FILE* stream) - 返回真,就说明是文件在读取过程中出错而结束的(异常结束)
用于判断是文件读取失败结束的,还是遇到文件结尾结束的
例如:
fgetc判断返回值是否为EOF
fgets判断返回值是否为NULL
fread判断返回值是否小于实际要读的个数
在这里插入图片描述
在这里插入图片描述

文件缓冲区

ANSIC 标准采用“缓冲文件系统”处理的数据文件的,所谓缓冲文件系统是指系统自动地在内存中为程序中每一个正在使用的文件开辟一块“文件缓冲区”。从内存向磁盘输出数据会先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘上。如果从磁盘向计算机读入数据,则从磁盘文件中读取数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(程序变量等)。缓冲区的大小根据C编译系统决定的。

说简单点,程序数据和硬盘(文件)进行数据交互时,会由缓冲区先接收,当缓冲区被充满,或者用fflush(pf)函数进行主动刷新数据(写入文件中或者读入内存中),才能真正完成数据传输
在这里插入图片描述
在这里插入图片描述

  • 7
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

失去梦想的小草

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值