用C语言操作文件

目录

什么是文件?

文件名

文件的打开和关闭

文件指针:

文件的打开与关闭:

 文件的顺序读写:

流:

sscanf和sprintf

文件的随机读写

fseek

ftell

​编辑

 rewind

 文本文件和二进制文件

 文件读取结束的判定

 被错误使用的feof:

feof的返回值并不能用来判断这个文件的读取是否结束,而是用来判定当文件读取结束时的原因是什么,是读取失败了还是读到尾部了

文件缓冲区


什么是文件?

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


程序文件:包括源程序文件(后缀为.c),目标文件(windows环境后缀为.obj),可执行程序(windows环境后缀为.exe)。
数据文件:文件的内容不一定是程序,而是程序运行时读写的数据,比如程序运行需要从中读取数据的文件,或者输出内容的文件。
有时候我们会把信息输出到磁盘上,当需要的时候再从磁盘上把数据读取到内存中使用,这里处理
的就是磁盘上文件。

文件名

 一个文件要有一个唯一的文件标识,以便用户识别和引用。
文件名包含3部分:文件路径+文件名主干+文件后缀
例如: c:\code\test.txt
为了方便起见,文件标识常被称为文件名。

文件的打开和关闭

 接下来我们将使用c语言的一些文件操作尝试创建和打开文件。

文件指针:

缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。

每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名FILE.

struct _iobuf {
    char *_ptr;
    int _cnt;
    char *_base;
    int _flag;
    int _file;
    int _charbuf;
    int _bufsiz;
    char *_tmpfname;
};
    typedef struct _iobuf FILE;
    FILE* pf;//文件指针变量

每当我们打开一个文件的时候,系统都会在内存中开辟一个文件信息区,整个文件信息区是一个结构体,它记录了这个文件的信息,这个结构体内部的信息其实很像这个文件的“身份证”基本的信息都会在这里。

为了能访问到这个文件,既然有了文件指针,那么我们就可以借用一个指针访问到它。

FILE* pf

文件的打开与关闭:

在使用程序打开文件的时候,会返回一个FILE*类型的文件指针,该指针指向该文件

有以下两个函数,可以操控文件的打开与关闭。

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

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

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

//关闭文件
int fclose ( FILE * stream );

我们可以看到,fopen函数的第一个参数是文件名,第二个参数则是打开的方式,有如下的方式打开。

fopen的打开模式是有所不同的,有很多种,使用时请不要忘记加双引号,fopen的返回值是FILE*类型的结构体指针。因为文件的打开很有可能失败,记得检测返回值是否为空指针。

 关闭文件就和malloc一样,文件是关闭了,但是这个指针还需要置空,不然就是野指针。

 我们尝试其中的W来试试效果。

当以只写的方式打开文件时,若文件名并不存在,那么就会创建一个新的文件,名称为使用时的名称。

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

 在执行如上代码后,我们在我们的项目文件夹里面就发现一个同名的txt文件

 文件的顺序读写:

 这里的顺序读写函数其实更像是API手册,只需要在使用对应所需功能的时候调用就好了。


 

 读取到的字符放入str中,num为读取的字符个数,stream是被读取的文件指针。


将指定的数据输入到文件中。

记得正确的使用格式。

 这里的s是一个结构体,将结构体内部的变量放入文件种。

将指定文件的数据输出出来。

这里fscanf和fprintf的用法和我们使用C语言的输出打印的逻辑时不太一样的

 

流:

为什么使用printf和scanf函数的时候我们不需要像打开文件一样需要先fopen一下呢?

以下就提到了数据流的概念,在C程序启动的时候,默认会产生以下3种流

 相当于预先开启的通道,其中,输入流与输出流已经默认打开,所以parintf和scanf是可以直接使用的。

那么流是什么?

由于一台电脑上的外设其实还是非常多的,音响,屏幕,键盘等等等等,当我们程序原尝试用软件控制这些硬件的时候,每个厂商之间的设备交流协议其实是不太一样的,作为人也不可能完全去掌握了解这些设备交流控制方法,所以产生了流,C语言高度封装了这个神奇的玩意帮助我们控制硬件。简而言之,流更像是一条没法看见内部运作的水管,我们只需要往水管里灌指令,剩下的流就会帮助我们与这些硬件构成交流

通常我们把对象接收外界的信息输入(stdin)称为标准输入流(键盘),相应地从对象向外输出(stdout)信息为标准输出流(屏幕),标准错误流(stderr)。只要C语言程序运行,三个流(FILE*)默认开启。而我们要读写文件时打开的又是一个文件流

流更像一个快递员,交流的事情全交给它了,我们只需要管发出和接收就好。

打开文件前的fopen的操作就像是寄快递前我们需要联系的京东小哥,只有小哥才知道怎么去把文件控制的命令交给我们的目标文件,所以我们需要先fopen一下。

这两个函数的意思是,以二进制读数据和以二进制写数据。

当我们使用fwirte写入数据的时候,我们打开记事本,里头读出来的是乱码,因为记事本本身以字符类型读取数据,但其实其内部的数据并没有出错,只是读取方式错了,用fread在内存里面读取到的数据则都是正常的。

 我们见识到了其他的printf和scanf,接下来介绍一下另一些种类。

sscanf和sprintf

int sscanf( const char *buffer, const char *format [, argument ] ... );
//buffer指从哪个字符串中提取,format指放到哪个数据中去
int sprintf( char *buffer, const char *format [, argument] ... );
//buffer是存储(输出)的位置

 这个格式化的意思更像是打包,sscanf把字符串拿到手,打包一下变成结构体类型,sprintf则是把打包好的结构体拆开来换成字符串。

sscanf的应用场景:前端网页获取的数据(字符串 )交给后端,后端C语言封装成了一个结构体,如何把字符串转换为结构体,就需要用到sscanf,后端转前端同理

文件的随机读写

在文件操作函数中,每一次调用fgets去获取文件里的一个字符时,它会获得当前单个字符,然后返回它,接着向下挪移一次,所以每一次挪动向前遍历的时候都要写一个返回值去接收它,这样子非常麻烦。

所以,我们想要控制文件指针的话,就要使用下列三个函数:

fseek

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

int fseek ( FILE * stream, long int offset, int origin );

stream为需要打开的文件指针,这个函数中的offset为起始位置,其中在官方文档中起始位置的设定是有要求的,也就是origin

SEEK_SET:将位置置为当前文件的开头

SEEK_SETCUR:将位置置为当前文件指针的位置

SEEK_SETEND:将位置置为当前文件的末尾

通过指针的偏移量来更改所需字符的位置,同时在计算偏移时,起始位置可以更换

offse则是偏移量,类似数轴,取决于设定时指定的位置,将设定处的位置看作数轴的零点即可计算偏移量,比如当我们面对一串字符串12345678想要直接读取它的4时候,假设当前设置位置为SET,那么偏移量就是4,而当设置位置为END的时候,取4那就是-5.

注意 ,文件内的字符串是不存在\0的概念的,不需要去注意这个问题

#include <stdio.h>
int main()
{
	FILE* pFile;
	pFile = fopen("example.txt", "wb");
	fputs("This is an apple.", pFile);
	fseek(pFile, 9, SEEK_SET);
	fputs(" sam", pFile);
	fclose(pFile);
	return 0;
}

ftell

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

long int ftell ( FILE * stream );

数偏移量还是蛮蠢得,所以可以使用ftell函数来计算起始位置与当前指针位置得偏移量。

计数的方式也是同理的,但是不会出现负数。

int main()
{
	FILE* pFile;
	long size;
	pFile = fopen("myfile.txt", "rb");
	if (pFile == NULL) perror("Error opening file");
	else
	{
		fseek(pFile, 0, SEEK_END); // non-portable
		size = ftell(pFile);
		fclose(pFile);
		printf("Size of myfile.txt: %ld bytes.\n", size);
	}
	return 0;
}

 rewind

让文件指针的位置回到文件的起始位置

void rewind ( FILE * stream );

 这个就没什么好说的了,直接返回起始位置,重置。

借用以上三个函数就可以很好的控制文件指针了。

 文本文件和二进制文件

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

 测试一下:

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

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

 我们把这个文件添加到我们的编译器里面,然后以二进制的方式读取并打开它。

 

 

 

 文件读取结束的判定

 被错误使用的feof:

feof的返回值并不能用来判断这个文件的读取是否结束,而是用来判定当文件读取结束时的原因是什么,是读取失败了还是读到尾部了

而我们正常的拍段文件的读取是否结束则是使用一下两种方法:

1. 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL


2. 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数

文件缓冲区

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

 

#include <windows.h>
int main()
{
	FILE* pf = fopen("test.txt", "w");
	fputs("abcdef", pf);//先将代码放在输出缓冲区
 
	printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
	Sleep(20000);//睡眠10秒
 
	printf("刷新缓冲区\n");
	fflush(pf);//主动刷新缓冲区时,将输出缓冲区的数据写到文件(磁盘)
	//注:fflush 在高版本的VS上不能使用了
	printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
	Sleep(10000);//让我们知道是fflush刷新了缓冲区而不是fclose
 
	fclose(pf);
	//注:fclose在关闭文件的时候,也会刷新缓冲区
	pf = NULL;
	return 0;
}

 由于fflush在 在高版本的VS上不能使用了,在这里直接阐述结论。

我们先将数据用fput放入缓冲区,然后直接睡眠,我们在睡眠期间打开文件,发现没有数据,其实不是fput没有生效而是此时的数据还在缓冲区里面。

然后我们刷新缓冲区,相当于把缓冲区里面的数据刷出来放入文件内,此时再次睡眠,我们打开文件发现数据存放到了文件里面。

其运作关系如上图,相当于每一次装满缓冲区之后再把数据送达目的地

因为这个机制的存在,所以每一次打开并使用完文件的时候一定要关闭,不然缓冲区里的东西没法载入,可能会导致一些问题.

至此结束,感谢阅读!希望对你有点帮助!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值