c语言文件操作

一.为什么使用文件

为了更好的将信息记录下来,对信息进行一个持久化的保存,我们要使用文件

二.什么是文件

磁盘上的文件是文件

但是在程序设计中,我们谈到的文件分为两种 : 1.程序文件 2.数据文件(从文件功能的角度进行分类)

1.程序文件:

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

obj(object的缩写,目标文件是我们的程序在编译的时候产生的临时文件)

2.数据文件:

文件的内容不一的是程序,当文件内容是程序运行时读写的数据时,这种文件称为数据文件。

今天主要讲的是数据文件

补充内容:文件名

一个文件要有一个唯一的文件表示,以便用户识别和使用,这个文件标识就被我们称为文件名

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


最后一个斜杆前面的都是文件路径,且文件层数从左往右每过一个斜杠加一层

后面的则是文件名主干+ 文件后缀


三.文件的打开和关闭

1.文件指针

 文件指针 --- 文件类型指针

开辟文件信息区的是结构体变量,该结构体变量的类型是FILE

当我们在修改文件中的内容的时候,文件信息区中对应的内容也会跟着修改(关于这个功能的实现,c语言已经帮我们实现好了)

 


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

在打开文件的同时,都会返回一个FILE*类型的指针变量来存储打开文件时自动创建的文件信息区的首内存 的地址 ---- 这就建立了指针和文件的关系

打开文件的函数:

1.fopen函数

#include <stdio>
FILE* fopen(const char* filename , const char* mode)

 返回的FILE*指针是存放FILE结构体类型变量开辟的文件信息区的首内存单元的地址的指针

const char* filename --- 是一个指向文件名字符串的首字符的指针

const char* mode --- 是一个指向 打开方式字符串 的首字符的指针

打开方式字符串分为以下几种:

1. " r "  --- open for reading(可以是空文件,也可以是非空文件) , if the file does not exist or cannot be found , the fopen call fails(打开失败)

2."w"  --- open a  empty file(注意是空文件,不要搞错了) for writing(以空文件的方式打开并写入,如果不是空文件,则会自动清空,然后再写入),if the given file exists ,its contents are destroyed. (如果我们传过去的文件不是空文件,那么函数会先将文件内容清空后再writing)

3."a" ---  opens for writing at the end of the file(appending --- 附加)  --- 在文件末尾写入(追加)内容 --- 如果文件不存在的话先创建文件,然后再写入。

4."r+"

5."w+"

6."a+"

上面上个到时自己查cpp看吧

关于函数的返回值;

如果打开成功的话,函数会返回一个指向已打开文件对应的文件信息区的首内存单元的FILE类型的指针

否则的话,会返回一个空指针NULL,此时表示打开出错(error)了


对一个文件进行操作的范式:

1.打开文件  --- 第一个参数:传文件名字符串的首字符的地址 --- 注意 “ 文件名 ” --> 在计算器中这样一个格式的本质是这个字符串的首字符的地址!  |   第二个参数 : 打开方式字符串的首字符的地址

注意写了打开函数之后要判断一下返回值是不是NULL,以此来确定文件打开是否成功

(没有取地址的地址这种东西,只有取变量和常量的地址)

2.写/读文件

3.关闭文件


五.关闭文件的函数 --- fclose函数

#include <stdio>
int fclose(FILE* stream)

FILE* stream --- 这个指针指向的是我们开辟的文件信息区的首内存

关闭文件后,开辟的文件信息区被释放(归还给系统),指向这个信息区的首内存的指针要被置为空指针,不然的话会找不到自己指向的对象,从而变为一个野指针,导致程序出问题。

一个程序能够打开的文件数是有限的,如果只打开文件而不关闭文件的话,最终就会导致我们打不开文件

文件的使用方式有上面这么多种

在函数传文件名参数的时候,如果在文件名前加入正确格式的路径的话,函数依然可以去找文件,且只会去我们在前面给的那个路径下找文件 --- 我们称这个路径为绝对路径

(之所以加双斜杠,是为了避免转义字符的出现使得路径名出错 --- /是转义字符,将转义字符转义 --- // 得到的就是斜杠)

如果没有加路径的话,函数就会在我们当前程序所在的工程文件中找文件名对应的文件,此时程序所在的工程文件的路径就被我们称为相对路径


如何向文件中写数据呢?

1.文件的顺序读写

所有的流:包括文件流和各种标准输入、输出流

 ps: fgetc读取成功时返回(在文件打开的时候才能开始读取)的是一个整型,这个整型是我们读取的字符的ascii码值 ,读取失败的时候会返回EOF (EOF的本质是 -1 )

fgetc会自动忽略掉已被读取的内容,而读取没被读取的内容(其它的同理),当文件被读完的时候fgetc就会返回EOF( - 1)  

fputs函数:的作用是将给定字符串写入文件,写入时会从文件的最后一个字符后面开始写入,如果想实现换行效果的话,就必须给字符串提前加入换行符号  \n ,这样的话文件再写入的时候就会自动换行

fgets函数:

#include <stdio.h>
char* fgets(char* string , int n , FILE* stream)

从一个文件流中读取 n -1 个字符(要留一个字符用来存终止符 \0 )并存到 string指针指向的字符数组中,然后返回这个字符数组的首元素的地址。

fgets函数的读取特点和fgetc一致,只会读取没有被读取的数据,

上面这些函数即可以从文件流中读取或写入 ,也可以从对应的标准输入/输出流中读取或写入,每个标准输入/输出流都有一个对应的硬件,硬件会从标准输入/输出流中接受数据,进而被读取或者执行功能。

get --是向文件中读取一个数据 / put -- 是向文件中写入一个数据 

读取和写入居多

单引号括住的字符格式 ‘ a ’  --- 本质上就是一个ascii码值

双引号括住的字符串格式“ a ” --- 本质上是其首字符的地址


关于 这个概念  --- 这是一个高度抽象的概念

每一种硬件的读写方式都不同,当我们想用程序向某一个硬件输入数据的时候,我就得专门为这个硬件适配一套输入程序,这是非常麻烦的,为了解决这个问题,人们抽象出了 流  这个概念:

程序只需要向流输出,然后流会自动适配对应的硬件的硬件输入方式 ,并输出我们的信息给对应的硬件,这样的话我们只需要写一种适配于流的输入程序就可以了

流 ---stream

总之我们只需要向流中写入数据,流就会帮我们自动适配好不同硬件的读写方式,并帮我们读取或者写入。

比如文件其实也可以称为文件流,我们在向文件写入数据的时候,其实就相当于在向文件流写入数据

只要c语言程序运行起来,就会默认打开三个流:

1.stdin :标准输入流 -- 对应的硬件是键盘 --- 从键盘输入的数据先存在标准输出流中,我们读取键盘输入的数据的时候都是从标准输入流中读取

2.stdout :标准输出流 -- 对应的硬件是屏幕 --- 同上

3.stderr : 标准错误流 --- 对应的硬件也是屏幕 --- 错误信息先存到标准错误流中,然后再输出到屏幕上

上面这些流都具有类型,它们的类型都是 FILE* ,它们指向的都是一个对应的文件信息区

一个标准流就指向一个文件 --- 这些文件都是C语言在创建程序工程文件时自动帮我们创建好的,当我们想要向某个硬件中写入/读取数据的时候,都要先经过对应的流的转换才可以。

比如在屏幕上显示数据 --- 我们输入要显示的数据到屏幕对应的标准输入流指向的文件中 ,标准输入流指向的文件将我们要输入的数据转换为屏幕能够识别的数据 --- 屏幕接收数据后显示我们想要的数据。


格式化输入/输出函数:

格式化输入输出函数的意思是:按照某一种格式输入 / 输出数据

fscanf ( ) -- 格式化输入(文件向程序输入)

fprintf ( )  -- 格式化输出(程序向文件输出)

后面的 [ , argument]... 代表的是多个可变参数 指这个位置可以被合适个数的正确类型的参数替代

如printf ("%d " ,a)  --->   “%d” 就是那个const char* format 接受的数据

, a就是用来替代[ , argument]...这个可变参数的合适个数和正确类型的参数 

 fscanf ( ) -- 格式化输入 

scanf后面的可变参数只能由地址来替代,因为我们要为输入的值分发一个门牌号(地址),告诉它去哪里住

上面这个的逻辑是 : 从文件流中依次获取数据,然后给这些数据分发门牌号,将它们存储到对应的结构体成员内存空间中。


前面的函数都是以字符的方式读取或写入文件的

接下来我们将会介绍用二进制的方式读取和写入文件。

fread ( ) 函数  --- 二进制输入(指文件向程序的二进制输入)

 fread函数的逻辑是fwrite函数的逻辑的反面 ,它是将 文件流 里的 count 个 size字节大小的元素读取并存进 buffer指针指向的内存空间中

fwrite ( ) 函数 --- 二进制输出(指程序向文件的二进制输出)

 返回值size_t 是我们实际读取的/写入的元素个数

buffer -- pointed to data to be written

 size --- item size in bytes(一项(一个元素)是多少个字节)

 count -- maximum number of itens to be written 

写入的时候会紧接着文件的数据结尾写入,然后读取的时候不会读取以及被读取过的内同

注意这里是按一个元素一个元素的方式来写入的

用fwrite将对应数据的二进制代码写进文件中时:

1.字符串/字符的二进制代码写入到文件中的输出结果是字符串/字符本身

2.非字符串如int , double型数据以二进制代码的形式写入到文件中的输出结果则是一些人了看不懂的值

既然写入到文本中的二进制数据如此凌乱 ,那么我们该如何阅读文本中的二进制数据?

答案:

是在使用 fread函数来读取文本中的二进制数据之后,将读取的数据存到程序中的 buffer指针指向的内存空间,然后内存空间中的数据内容用printf打印出来供人类读取。

读文件的时候一个元素一个元素的读 ---> 方便我们做循环判断 ---> 读一个读了的话--- > size_t = 1

没读的话 --> size_t = 0 

初始化

载入文件 --- 增容处理

程序内修改数据

将新的内容写入文件 --- 实现保存数据的功能

初始化 -- 读(增容) -- 数据修改 -- 写 -- 保存


最后 --> 文件的随机读写

格式化是把同类事物处理成相同的规格、样式。

 sscanf的作用是:从一个字符指针指向的字符数组/字符串中读取格式化的数据 --- read formated data from a string --- 从一个字符指针指向的字符数组/字符串中读取一个字符串,并将这个字符串转换为格式化的数据

至于字符串的长度则是由格式化数据中的输入输出控制符部分,即双引号部分决定的

sprintf的作用 --- write formatted data to a  string --- 写格式化的数据到字符串/字符数组中 --- 把一个格式化的数据转换为字符串并写入到buffer指针指向的字符串/字符数组中中

 用法如下:

 上面这个就是 将 "hello" 20 5.5f转换为字符串 --- “hello 20 5.5f”并写入到buffer指针指向的字符数组/字符串中

“%s %d %f(数据对应的输入输出格式控制符)” ,s.arr ,s.age , s.f (可变参数 --- 可改变的参数)

在输出(printf)的时候:如果是输出字符串/字符数组 --- 对应的控制符是%s , 可变参数需要的是首字符/首元素的地址,对于非字符串,数组而言,这里可以是数据具体的值,也可以是承接数据的变量的变量名

在输入(scanf)的时候: 可变参数是用来存储输入数据的内存空间的地址 ,数组的话只要有首元素地址即可,非数组则是变量对应的地址) ---- 这两个组合起来就是一组格式化数据

ps : 字符串格式 “a”的本质是首字符的地址

字符格式 ’a‘的本质是对应的类型为整型的ascii码值


文件指针: 当我们在读取文件的时候,会有一个文件指针指向我们当前读取的字符,读取完时,文件指针就会自动+1 , 指向下一个字符,当我们再次读取文件时,我们就会读到文件指针指向的新的字符了。

文件指针在读完一个字符后指向下一个字符的读取方式称为文件的顺序读取

而当我们将文件指针指向任意位置字符,并任意读取的时候,我们就称之为文件的任意读取。

那么我们怎么让文件指针想挪到哪就挪到哪里去呢?

答案就是通过文件指针定位函数来更改文件读取定位指针指向的字符

文件指针定位函数 --- fseek

根据文件指针的位置和偏移量来定位新的文件指针

(用函数读文件的时候,函数中都会自动生成一个文件指针,第一进去文件指针指向第一个字符,我们通过文件指针找到这个字符并读取,读取完后文件指针++,并返回已读取的值 ---- 注意:读一个文件就会专门为这个文件创建一个文件指针,这个文件指针是一个静态局部变量,如果读了一个文件之后读取另外一个文件的话,新的文件指针就会覆盖掉旧的文件指针)

我们可以利用文件指针定位函数fseek来调整文件指针指向的位置

#include <stdio.h>
int fseek(FILE* stream , long int offset , int origin)

fseek : move th file pointer to a specified location

stream --- 对应文件流 

offset --- 偏移量  --- 有偏移量的前提是有起始点

origin --- 起始位置 --- 所以我们要先确定起始位置

而关于起始位置的选择有三个:

1.SEEK_CUR  --- current(当前的,目前的) positiion of file pointer  --- 文件当前的位置

2.SEEK_END -- end of file 

3.SEEK_SET --- beginning of file

选择三个起点都能够得到同样的效果,但不同的起点达到同样效果的偏移量一般不同

这是因为偏移量是相对于起点而言的:

偏移量为1的时候,指针从起点开始向右偏移1位 

为 -1 的话,则是向左偏移一位

ps : 偏移时,不能够偏移到文件未设定字符的位置 ,所以 对于END偏移量只能为正数,对于 SET偏移量只能为正数

调整文件指针的步骤:

1.规定是那个文件 FILE* 

2.确定偏移起点 : 三个选择 CUR END SET

3.确定偏移起点到新位置的偏移量

4.如此这般,函数就能够通过上面这三个信息确定指针的新位置了 

还要介绍一个辅助函数,这个函数是帮助我们算偏移量的

ftell函数 --- 这个函数的作用是返回当前文件指针相对于文件起始位置的偏移量是多少

#include <stdio.h>
long int ftell(FILE* stream)

这个函数就会返回当前文件中的文件指针相对于文件起始位置的偏移量

注:文件起始位置是指文件中的第一个字符!!!

如果我们想让文件回到起始位置的话,我们可以使用rewind函数

#include <stdio.h>
void rewind(FILE* stream)

作用是让文件指针重新指回文件起始位置 (文件中的第一个字符)


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

 外存 --- 就是指文件

注意中间那句话应该改为:一个数据在文件中是怎么存储的(无论啥数据在内存中都是以二进制的方式存储的)

二进制存进去 --- 二进制文件

将二进制数据转换为对应的10进制数据,然后再找到10进制数据每一位上的数字变为字符后对应的ascii码值,然后将ascii码值存到文件中 --- 这种文件就被称为文本文件(本身就是字符的话就直接存ascii码值)

规定一个字符的大小是一个字节 

wb是以二进制的形式写入,w是以文本的形式写入 

存内存数据二进制码转译的ascii码值的是文本文件,存内存数据的二进制码的是二进制文件


八.文件读取结束的判定 

不能用feof函数的返回值来判断文件读取是否结束 

而是用它在文件读取结束之后判断 --- 文件是因为读取失败结束的还是 遇到文件尾结束的

那么我们用什么来判断文件读取是否结束呢?

答案是用对应读取函数的返回值来判断

 1.fgetc 和 fgets 都是文本文件的读取函数:

fgetc是读取字符 ,如果返回为EOF则读取结束 --- 注意只是知道读取结束,但不知道是为什么读取结束

fgets是读取字符串(读够一定(数目是我们给定的参数)字符后将这些字符的ascii码值的二进制数存到我们给定的(参数)内存中的一个内存空间),读取结束 --- NULL

2.fread 是二进制文本的读取函数 ,它是一个元素一个元素的读取的(元素的数量,大小都是由我们确定的),返回的是实际读取到的元素个数

读取结束原因:

1.能读取到的元素小于规定的元素个数时,读完能够读的就结束了

2. ......................大于..................... , 读到规定元素个数就结束了

3.由于读取失败直接读取结束了

如果想要判断读取结束的原因 ---- 用feof函数 --- feof(指向已经读取结束的文件对应的文件信息区的指针) --- 如果feof 返回非0的值 ,则读取结束原因是 : 读取到文件尾(文件结束标志)正常结束了,否则的话返回0,文件是因为读取失败结束的

与feof对应的还有一个函数 --- ferror函数 -- 其参数和deof一致,当这个函数的返回值是非0的话,文件就是因为读取失败而结束的,否则的话为0,是读取到文件尾(文件结束标志)正常结束的

如果想判断是否结束  --- 看对应读取函数的返回值

从文件中读数据其实是指 --- 将文件中读到的数据存到内存中指定空间

 第二个文件如果打开失败的话,函数就结束了,但是!!!在结束之前千万不要完了我们第一个文件已经打开了,所以我们要在函数结束前先将第一个文件关闭(防止可打开文件额度浪费,以恶个程序可打开的文件数是有限的),并将指针置为空指针(不要成为野指针 --- 关闭文件的时候会将指针对应的内存空间 --- 文件信息区归还)

  EOF的本质是 -1


八.文件缓冲区

从内存中写入数据到文件(硬盘中),数据先存到文件缓冲区,缓冲区装满了才送到文件(硬盘)中 。从文件读取然后存到内存中同理 

 文件缓冲区是一块在内存中开辟的空间,每一个在程序运行时使用中的文件都会开辟文件缓冲区

如果从程序数据区直接写入数据到硬盘中的话,就需要操作系统来帮我们写入

如果频繁调用操作系统帮我们写数据到硬盘中的话,就会导致操作系统必须分出大量的精力帮我们写入数据,哪它干其它事情的效率就大大降低了

于是为了提高效率,我们引入了操作缓存区

引入文件缓存区的目的就是为了提高操作系统的写入效率,当存够一定量的字符的时候才写入一次肯定比有一个字符就写入一次效率高的多呀

(ps:从内存中写入数据到文件(硬盘)中的时候需要经过操作系统的辅助才写入)

当文件关闭的时候也会刷新缓存区 --- 即在文件关闭时,不管缓存区满没满,计算机都会自动将文缓存区中的内容写入到文件中 / 存储在内存中

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值