C语言的文件操作

前言

本篇文章介绍C语言的文件操作,内容列表如下:

  • fopen
  • fclose
  • getc、putc、ungetc
  • fprintf、fscanf
  • rewind
  • fgets、fputs
  • fseek、ftell
  • fflush
  • setvbuf
  • fwrite、fread
  • feof、ferror

fopen

C标准使用fopen函数打开文件,该函数有两个参数,第一个参数是文件的绝对路径或者相对路径,第二个参数是文件的打开模式,返回文件指针FILE*,如果打开失败,返回NULL

关于FILE*

首先,FILE*不是指向实际的文件,FILE是一个定义在stdio里的一个结构体,下面给出这个结构体的定义

typedef	struct __sFILE {
	unsigned char *_p;		// 缓冲区的当前位置
	int	_r;			// getc在当前缓冲区已读的数据
	int	_w;			// putc在当前缓冲区剩余可写的数据
	short	_flags;		// 标志位,如果为0,表示当前文件可用
	short	_file;		// 仅用于Unix,表示文件描述符,否则值为-1
	struct	__sbuf _bf;	// 缓冲区结构体数据
	int	_lbfsize;		// 值为0或者-_bf._size, 用于内联 putc

	// 传递给io函数的缓存指针
	void	*_cookie;	
	
	// close函数指针
	int	(* _Nullable _close)(void *); 
	
	// read函数指针
	int	(* _Nullable _read) (void *, char *, int);
	
	// seek函数指针
	fpos_t	(* _Nullable _seek) (void *, fpos_t, int);
	
	// write函数指针
	int	(* _Nullable _write)(void *, const char *, int);
	
	struct	__sbuf _ub;	// ungetc缓冲区
	
	struct __sFILEX *_extra;
	
	int	_ur;		// 当使用ungetc方法向输入缓冲区写回字符时,保_r的值
	// 可支持的最低的写回缓冲区
	unsigned char _ubuf[3];	
	
	// 可支持的最低的读取缓冲区
	unsigned char _nbuf[1];	
	
	// 供fgetln使用的缓冲区结构,当fgetln读取的数据正好部分在当前缓冲区时有用
	struct	__sbuf _lb;	/* buffer for fgetln() */

	// 根据seek值获取数据块的单元大小
	int	_blksize;
	
	// seek的offset值
	fpos_t	_offset;
} FILE;

可以不用知道该结构体内变量的作用,我们基本也用不到,这都是给操作系统IO函数使用的,我们只需要知道这个结构体包含了我们读写缓冲区的信息就行了

文件打开模式

文件的打开模式其实基本就四种:

  • r:只读模式打开
  • w:以写模式打开文件,并且将文件清空,如果文件不存在,创建一个
  • a:以写模式打开文件,不清空文件,定位到文件末尾,如果文件不存在,创建一个
  • wx:C11新增的文件打开模式,该模式下,如果文件存在,会打开失败,也就是如果我以该模式成功创建并打开一个文件,我就不能再次用该模式打开这个文件了,因为该文件已经存在了,这也就是该模式文件具有的独占性

如果这四种文件打开模式后面添加+号,就变成了读写模式,意思还是不变,只是既能写入,也能读出

如果这四种文件打开模式后面添加b,就变成了二进制模式,意思还是不变,只是以二进制模式读写数据
二进制模式和文本模式的区别可以参考文章C语言文本模式和二进制模式

注意:+号和b可以一起添加并且顺序不重要

fclose

关闭当前文件,传递参数FILE*,如果关闭成功,返回0,否则返回EOF

getc、putc、ungetc

这三个函数用来读取和写回字符

getc

传递参数文件指针FILE*,返回从输入流获取到的字符

putc

第一个参数传递写入的字符,第二个参数传递文件指针FILE*,向输出流添加字符

ungetc

向输入流写回字符,第一个参数传递写回的字符,第二个参数传递文件指针FILE*,写回后使用getc会读取写回的字符

fprintf、fscanf

fprintf

fprintf和printf功能类似,只不过第一个参数是一个文件指针FILE*,fprintf可以具有和printf一样的功能,只需要把文件指针写成标准文件输出stdout即可

fscanf

fscanf和scanf功能类似,只不过第一个参数是一个文件指针FILE*,fscanf可以具有和scanf一样的功能,只需要把文件指针写成标准文件输入stdin即可

rewind

就一个参数,传递文件指针FILE*,返回文件开始

fgets、fputs

fgets传递三个参数,第一个参数是存储字符数据的数组指针,第二个参数是读取的最大字符,如果值为m,则最多读取m-1个,因为后面还要添加’\0’,第三个参数是文件指针FILE*,也可以是标准输入stdin

  • fgets在读取到换行符会把换行符一并保存,然后添加’\0’结束
  • 如果读取到EOF,然后添加’\0’结束
  • 如果读取到最大长度m-1,然后添加’\0’结束

fputs传递两个参数,第一个参数传递输出字符数组的指针,第二个参数是文件指针FILE*,也可以是标准输出stdout
fputs不会在输出完成后添加换行符

fseek、ftell

fseek函数用于设置当前文件读写的位置,该函数接受三个参数,第一个参数是文件指针FILE*,第二个参数是相对于第三个参数设置的偏移量offset,可正可负,第三个参数是设置文件的起始点mode,有三个:
SEEK_SET:文件开始处
SEEK_CUR:当前文件位置
SEEK_END:文件结尾
也就是当前文件的位置为:pos = mode + offset

如果fseek正常,返回0,如果seek失败,返回-1

ftell函数只需要一个参数,当前的文件指针FILE*,返回long类型,表示当前位置距离起始位置的字节大小。ftell在二进制模式和文本模式的不同可以参考文章C语言文本模式和二进制模式

fflush

fflush函数接受一个参数,就是文件指针FILE*
该函数用于输出缓冲区的刷新,即将输出缓冲区的数据写入文件或者标准输出。如果文件指针为NULL,所有输出缓冲区都被刷新

setvbuf

该函数是一个自定义缓冲区的函数,该函数接受四个参数:

  • 第一个参数是文件指针
  • 第二个参数是要指定的缓冲区指针,通常是一个字符数组,如果设置NULL,则自动创建一个缓冲区
  • 第三个参数是缓冲区刷新模式
  • 第四个参数是缓冲区的大小

返回值是int类型,如果操作成功,返回0

缓冲区的刷新模式

缓冲区刷新模式有三种类型:
_IOFBF:(io full buffer flush),缓冲区满了以后刷新缓冲区
_IOLBF:(io line buffer flush),遇到换行符刷新缓冲区
_IONBF:(io no buffer flush),不刷新缓冲区
看下面的例子:

#include 
int main(void)
{
    char data[10];
    FILE* file = fopen("test.d", "w");
    if(file != NULL)
    {
        setvbuf(file, data,_IOFBF , sizeof(data));
        fprintf(file, "t\nhis is a test!!!");
        fclose(file);//在这行打断点
    }
    return 0;
}

当使用_IOFBF模式时,在断点处,查看输出文件,发现只有下面这些数据:

t
his is a

因为输入的数据太多,缓冲区满了,所以缓冲区刷新,文件被写入一部分,剩下的内容没有填满缓冲区,所以不会输出到文件,等fclose之后就都写入文件了。

当使用_IOLBF模式时,在断点处,查看输出文件,发现只有下面这些数据:

t
his is a t

在t后遇到换行符,刷新缓冲区,写入文件(换行符也写入了文件中),然后继续读取数据到缓冲区,到test的t时缓冲区满,再次刷新缓冲区,所以文件显示如此

当使用_IONBF模式时,数据压根就不会进缓冲区,直接写入文件

fwrite、fread

这两个函数是用来写入二进制文件的
fwrite有四个参数

  1. 参数一表示待写入数据的指针
  2. 参数二表示写入数据块的大小
  3. 参数三表示写入数据的块数
  4. 参数四表示文件指针

返回结果表示实际写入的数据块数,如果写入正确,一般等于参数三

fread也有四个参数

  1. 参数一表示存放读取数据的指针
  2. 参数二表示读取数据块的大小
  3. 参数三表示读取数据的块数
  4. 参数四表示文件指针

返回结果表示实际读取的数据块数,如果读取正确,一般等于参数三

可能的问题

通常来说,使用这两个函数来读写二进制数据非常方便,但是这两个函数读取写入数据是完全是按照内存的数据布局来写入或者读取的,这样如果我们在一个小端法存放数据的机器上保存的数据,如果在大端法存放数据的机器上读取可能出现问题,我们在后面的文章会实现一个机器无关的数据写入和读取库。

feof、ferror

如果标准输入函数返回EOF时,我们通常认为到达文件结尾,但是如果读取错误时,也会返回EOF,那么怎么区分这两种情况呢。这两个函数就专门用来区分这两种情况的。
feof函数需要一个参数,就是文件指针FILE*,如果达到文件结尾,返回非零值,否则返回0
ferror函数也需要一个参数,就是文件指针FILE*,如果读取文件出现错误,返回非零值,否则返回0

你可能感兴趣的:(程序设计-C语言,c语言,开发语言)