18.C语言文件操作详解:指针、打开、读取与写入

1.文件指针

本篇原文为:C语言文件操作详解:指针、打开、读取与写入

更多C++进阶、rust、python、逆向等等教程,可点击此链接查看:酷程网

C 语言提供了一个 FILE 数据结构,记录了操作一个文件所需要的信息,该结构定义在头文件stdio.h,所有文件操作函数都要通过这个数据结构,获取文件信息。

开始操作一个文件之前,就要定义一个指向该文件的 FILE 指针,相当于获取一块内存区域,用来保存文件信息。

比如定义一个 FILE 指针fp的代码如下:

FILE* fp;

下面是一个读取文件的完整示例:

#include <stdio.h>

int main(void) {
   
   
  FILE* fp;
  char c;

  fp = fopen("hello.txt", "r");
  if (fp == NULL) {
   
   
    return -1;
  }

  c = fgetc(fp);
  printf("%c\n", c);

  fclose(fp);

  return 0;
}

示例中,新建文件指针fp以后,依次使用了下面三个文件操作函数,分成三个步骤,其他的文件操作,大致上也是这样的步骤。

第一步,使用fopen()打开指定文件,返回一个 File 指针,如果出错,返回 NULL。

它相当于将指定文件的信息与新建的文件指针fp相关联,在 FILE 结构内部记录了这样一些信息:文件内部的当前读写位置、读写报错的记录、文件结尾指示器、缓冲区开始位置的指针、文件标识符、一个计数器(统计拷贝进缓冲区的字节数)等等。

后继的操作就可以使用这个指针(而不是文件名)来处理指定文件。

同时,它还为文件建立一个缓存区,由于存在缓存区,也可以说fopen()函数“打开了一个流”,后继的读写文件都是流模式。

第二步,使用读写函数,从文件读取数据,或者向文件写入数据,上例使用了fgetc()函数,从已经打开的文件里面,读取一个字符。

fgetc()一调用,文件的数据块先拷贝到缓冲区,不同的计算机有不同的缓冲区大小,一般是512字节或是它的倍数,如4096或16384,随着计算机硬盘容量越来越大,缓冲区也越来越大。

fgetc()从缓冲区读取数据,同时将文件指针内部的读写位置指示器,指向所读取字符的下一个字符。

所有的文件读取函数都使用相同的缓冲区,后面再调用任何一个读取函数,都将从指示器指向的位置,即上一次读取函数停止的位置开始读取。

当读取函数发现已读完缓冲区里面的所有字符时,会请求把下一个缓冲区大小的数据块,从文件拷贝到缓冲区中,读取函数就以这种方式,读完文件的所有内容,直到文件结尾。

不过,上例是只从缓存区读取一个字符,当函数在缓冲区里面,读完文件的最后一个字符时,就把 FILE 结构里面的文件结尾指示器设置为真。于是,下一次再调用读取函数时,会返回常量 EOF。EOF 是一个整数值,代表文件结尾,一般是-1

第三步,fclose()关闭文件,同时清空缓存区。

上面是文件读取的过程,文件写入也是类似的方式,先把数据写入缓冲区,当缓冲区填满后,缓存区的数据将被转移到文件中。

2.fopen

fopen()函数用来打开文件。所有文件操作的第一步,都是使用fopen()打开指定文件,这个函数的原型定义在头文件stdio.h

FILE* fopen(char* filename, char* mode);

它接受两个参数。第一个参数是文件名(可以包含路径),第二个参数是模式字符串,指定对文件执行的操作,比如下面的例子中,r表示以读取模式打开文件:

fp = fopen("in.dat", "r");

成功打开文件以后,fopen()返回一个 FILE 指针,其他函数可以用这个指针操作文件。

如果无法打开文件(比如文件不存在或没有权限),会返回空指针 NULL。所以,执行fopen()以后,最好判断一下有没有打开成功:

fp = fopen("hello.txt", "r");

if (fp == NULL) {
   
   
  printf("Can't open file!\n");
  exit(EXIT_FAILURE);
}

上面示例中,如果fopen()返回一个空指针,程序就会报错。

fopen()的模式字符串有以下几种。

  • r:读模式,只用来读取数据。如果文件不存在,返回 NULL 指针。
  • w:写模式,只用来写入数据。如果文件存在,文件长度会被截为0,然后再写入;如果文件不存在,则创建该文件。
  • a:写模式,只用来在文件尾部追加数据。如果文件不存在,则创建该文件。
  • r+:读写模式。如果文件存在,指针指向文件开始处,可以在文件头部添加数据。如果文件不存在,返回 NULL 指针。
  • w+:读写模式。如果文件存在,文件长度会被截为0,然后再写入数据。这种模式实际上读不到数据,反而会擦掉数据。如果文件不存在,则创建该文件。
  • a+:读写模式。如果文件存在,指针指向文件结尾,可以在现有文件末尾添加内容。如果文件不存在,则创建该文件。

上一小节说过,fopen()函数会为打开的文件创建一个缓冲区。读模式下,创建的是读缓存区;写模式下,创建的是写缓存区;读写模式下,会同时创建两个缓冲区,C 语言通过缓存区,以流的形式,向文件读写数据。

数据在文件里面,都是以二进制形式存储,但在读取的时候,有不同的解读方法。

以原本的二进制形式解读,叫做“二进制流”;将二进制数据转成文本,以文本形式解读,叫做“文本流”。写入操作也是如此,分成以二进制写入和以文本写入,后者会多一个文本转二进制的步骤。

fopen()的模式字符串,默认是以文本流读写。如果添加b后缀(表示 binary),就会以“二进制流”进行读写。

比如,rb是读取二进制数据模式,wb是写入二进制数据模式。

模式字符串还有一个x后缀,表示独占模式(exclusive)。如果文件已经存在,则打开文件失败;

如果文件不存在,则新建文件,打开后不再允许其他程序或线程访问当前文件。

比如,wx表示以独占模式写入文件,如果文件已经存在,就会打开失败。

3.标准流

Linux 系统默认提供三个已经打开的文件,它们的文件指针如下。

  • stdin(标准输入):默认来源为键盘,文件指针编号为0
  • stdout(标准输出):默认目的地为显示器,文件指针编号为1
  • stderr(标准错误):默认目的地为显示器,文件指针编号为2

Linux 系统的文件,不一定是数据文件,也可以是设备文件,即文件代表一个可以读或写的设备。

文件指针stdin默认是把键盘看作一个文件,读取这个文件,就能获取用户的键盘输入。

同理,stdoutstderr默认是把显示器看作一个文件,将程序的运行结果写入这个文件,用户就能看到运行结果了。

它们的区别是,stdout写入的是程序的正常运行结果,stderr写入的是程序的报错信息。

这三个输入和输出渠道,是 Linux 默认提供的,所以分别称为标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。因为它们的实现是一样的,都是文件流,所以合称为“标准流”。

Linux 允许改变这三个文件指针(文件流)指向的文件,这称为重定向(redirection)。

如果标准输入不绑定键盘,而是绑定其他文件,可以在文件名前面加上小于号<,跟在程序名后面。这叫做“输入重定向”(input redirection)。

demo < in.dat

上面示例中,demo程序代码里面的stdin,将指向文件in.dat,即从in.dat获取数据。

如果标准输出绑定其他文件,而不是显示器,可以在文件名前加上大于号>,跟在程序名后面。这叫做“输出重定向”(output redirection):

demo > out.dat

上面示例中,demo程序代码里面的stdout,将指向文件out.dat,即向out.dat写入数据。

输出重定向>会先擦去out.dat的所有原有的内容,然后再写入。如果希望写入的信息追加在out.dat的结尾,可以使用>>符号:

demo >> out.dat

上面示例中,demo程序代码里面的stdout,将向文件out.dat写入数据。与>不同的是,写入的开始位置是out.dat的文件结尾。

标准错误的重定向符号是2>。其中的2代表文件指针的编号,即2>表示将2号文件指针的写入,重定向到err.txt,2号文件指针就是标准错误stderr

demo > out.dat 2> err.txt

上面示例中,demo程序代码里面的stderr,会向文件err.txt写入报错信息。而stdout向文件out.dat写入。

输入重定向和输出重定向,也可以结合在一条命令里面:

demo < in.dat > out.dat

// or
demo > out.dat < in.dat

重定向还有另一种情况,就是将一个程序的标准输出stdout,指向另一个程序的标准输入stdin,这时要使用|符号:

random | sum

上面示例中,random程序代码里面的stdout的写入,会从sum程序代码里面的stdin被读取。

4.fclose

fclose()用来关闭已经使用fopen()打开的文件,它的原型定义在stdio.h

int fclose(FILE* stream);

它接受一个文件指针fp作为参数。如果成功关闭文件,fclose()函数返回整数0;如果操作失败(比如磁盘已满,或者出现 I/O 错误),则返回一个特殊值 EOF

if (fclose(fp) != 0)
  printf("Something wrong.");

不再使用的文件,都应该使用fclose()关闭,否则无法释放资源。一般来说,系统对同时打开的文件数量有限制,及时关闭文件可以避免超过这个限制。

5.EOF

C 语言的文件操作函数的设计是,如果遇到文件结尾,就返回一个特殊值。程序接收到这个特殊值,就知道已经到达文件结尾了。

头文件stdio.h为这个特殊值定义了一个宏EOF(end of file 的缩写),它的值一般是-1

这是因为从文件读取的二进制值,不管作为无符号数字解释,还是作为 ASCII 码解释,都不可能是负值,所以可以很安全地返回-1,不会跟文件本身的数据相冲突。

需要注意的是,不像字符串结尾真的存储了\0这个值,EOF并不存储在文件结尾,文件中并不存在这个值,完全是文件操作函数发现到达了文件结尾,而返回这个值。

6.freopen()

freopen()用于新打开一个文件,直接关联到某个已经打开的文件指针,这样可以复用文件指针。它的原型定义在头文件stdio.h

FILE* freopen(char* filename, char* mode, FILE* stream);

它跟fopen()相比,就是多出了第三个参数,表示要复用的文件指针,其他两个参数都一样,分别是文件名和打开模式:

freopen("output.txt", "w", stdout);
printf("hello");

上面示例将文件output.txt关联到stdout,此后向stdout写入的内容,都会写入output.txt

由于printf()默认就是输出到stdout,所以运行上面的代码以后,文件output.txt会被写入hello

freopen()的返回值是它的第三个参数(文件指针),如果打开失败(比如文件不存在),会返回空指针 NULL。

freopen()会自动关闭原先已经打开的文件,如果文件指针并没有指向已经打开的文件,则freopen()等同于fopen()

下面是freopen()关联scanf()</

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

余识-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值