C 语言文件操作

简单记录一下 C 语言的文件操作的相关内容。

前期准备

1
2
3
4
// stdio.h 提供了 C 语言中的许多 IO,当然包括文件 IO
#include <stdio.h>
// 定义文件指针
FILE *fp;

打开文件 fopen ()

函数名称:fopen
参数:待打开文件的名称(包含该文件名的字符串地址),打开文件的模式;
返回值:成功打开文件则返回一个文件指针,否则返回空指针(NULL)

例如:

1
2
3
4
5
6
FILE *fp;
if (fp = fopen("example.file", "r") == NULL)
{
fprintf(stderr, "Fail to open the file.\n");
exit(EXIT_FAILURE);
}

需要注意,该函数第二个参数是一个字符串,可能的值如下(表来自《C Primer Plus(第 6 版)中文版》P357 表 13.1):

模式字符串 含义
"r" 以读模式打开文件
"w" 以写模式打开文件,把现有文件长度截为 0,如果文件不存在,则创建一个新文件
"a" 以写模式打开文件,在现有文件末尾添加内容,如果文件不存在,则创建一个新文件
"r+" 以更新模式打开文件(即可以读写文件)
"w+" 以更新模式打开文件,如果文件存在,则将其长度截为 0,如果文件不存在则创建一个新文件
"a+" 以更新模式打开文件,在现有文件的末尾添加内容,如果文件不存在则创建一个新文件,可以读整个文件,但只能从末尾添加内容
"rb"、"wb"、"ab"、"rb+"、"r+b"、"wb+"、"w+b"、"ab+"、"a+b" 与上边的对应类似,但以二进制模式打开文件
"wx"、"wbx"、"w+x"、"wb+x" 或 "w+bx" (C11)与上边对应类似,但如果文件已存在或以独占模式打开文件,则打开文件失败

带字母 x 的写模式比以前的具有更多特性:

  1. 如果以传统的一种写模式打开一个现有文件,fopen () 会把该文件的长度截为 0,这样就丢失了该文件的内容。但是使用带 x 字母的写模式,即使 fopen () 操作失败,原文件的内容也不会被删除;
  2. 如果环境允许,x 模式的独占特性使得其他程序或线程无法访问正在被打开的文件。

关闭文件 fclose ()

函数名称:fclose
参数:待关闭文件的名称(包含该文件名的字符串地址);
返回值:成功关闭返回 0,否则返回 EOF。

注意区分 fopen () 和 fclose () 的返回值!前者失败时返回 NULL(通常情况下就是 0),后者成功时返回 0。

读写文件

fprintf () 和 fscanf ()

这两个函数分别和 printf () 和 scanf () 类似,只不过 printf () 和 scanf () 分别默认了写和读的标准文件是 stdout 和 stdin,而 fprintf () 和 fscanf () 的第一个参数都需要指定文件指针。

getc () 和 putc ()

这两个函数分别和 getchar () 和 putchar () 类似,只是需要提供文件指针。

ungetc()

函数名称:ungetc
函数原型:int ungetc(int c, FILE *fp);
函数作用:把 c 指定的字符放回输入流中
返回值:如果成功,则返回被推入的字符,否则返回 EOF

fgets () 和 fputs ()

虽然这两个函数也分别类似于 gets () 和 puts (),但比起上边几个函数的 "类似",这个要低一点,所以详细说明一下。

函数名称:fgets
函数原型:char *fgets (char * restrict str, int n, FILE * restrict fp);
返回值:如果成功,该函数返回相同的 str 参数。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。如果发生错误,返回一个空指针。

需要注意 fgets () 的第二个参数,因为 fgets () 读取输入知道第一个换行符的后边(意味着它会读入换行符),或读到文件结尾,或读取 n-1 个字符,并在结尾加上一个 \0 使之成为一个字符串。

函数名称:fputs
函数原型:int fputs (char * restrict str, FILE * restrict fp);
返回值:该函数返回一个非负值,如果发生错误则返回 EOF。

fputs () 与 puts () 类似,但不会在结尾自动添加换行

注意区分 fgets () 与 gets (), fputs () 与 puts ()!gets () 不保留换行符所以 puts () 自动添加换行符;fgets () 保留换行符所以 fputs () 不会添加换行符。

fread () 和 fwrite ()

上述的函数都是以文本形式读写文件,这两个函数用于以二进制形式读写文件。

函数名称:fread
函数原型:size_t fread(void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
返回值:返回成功读取项的数量。正常情况下返回值等于 nmemb,发生错误则返回值小于 nmemb。

函数名称:fwrite
函数原型:size_t fwrite(const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);
返回值:返回成功写入项的数量。正常情况下返回值等于 nmemb,发生错误则返回值小于 nmemb。

参数 size 表示待写入数据块的大小(以字节为单位),nmemb 表示待写入数据块的数量。

下边是两个使用这两个函数的例子。

1
2
3
4
5
6
7
8
9
10
11
12
// 保存一个大小为 256 字节的 buffer 数组
char buffer[256];
fwrite(buffer, 256, 1, fp);

// 保存一个内含 10 个 double 类型值的数组
// 或者说理解成保存 10 个 double 类型的值
double earnings[10];
fwrite(earnings, sizeof(double), 10, fp);

// 读取 10 个 double 类型的值到一个数组中
double earnings[10];
fread(earnings, sizeof(double), 10, fp);

随机访问 fseek () 和 ftell ()

函数名称:fseek
函数原型:int fseek(FILE *_File,long _Offset,int _Origin);
参数:文件指针,偏移量 (long 类型),模式
返回值:正常则返回 0,错误则返回 -1。

第二个参数偏移量必须是一个 long 类型的值,代表偏移的字节数。这个值为正,则表示像文件末尾方向移动;为负则表示向文件开头处。
第三个参数可以理解成起点位置,可以使用 SEEK_SETSEEK_CURSEEK_END 分别定位到文件开始、当前位置或文件末尾(老版本应分别使用 012)。

下边是一些例子(来自《C Primer Plus(第 6 版)中文版》P364):

1
2
3
4
5
fseek(fp, 0L, SEEK_SET);   // 定位至文件开始处
fseek(fp, 10L, SEEK_SET); // 定位至文件中第 10 个字节
fseek(fp, 2L, SEEK_CUR); // 从文件当前位置向结尾方向移动 2 个字节
fseek(fp, 0L, SEEK_END); // 定位至文件末尾
fseek(fp, -10L, SEEK_END); // 从文件结尾处回退 10 个字节

函数名称:ftell
函数原型:long ftell(FILE *_File);
返回值:返回当前位置距文件开始的字节数,如文件的第一个字节到文件开始处的距离为 0。

下边是书中给出的一个例子:

1
2
3
4
5
6
7
8
fseek(fp, 0L, SEEK_END); // 首先定位到文件结尾
last = ftell(fp); // 统计字节数,并存储到 last 中
// 逆序打印每一个字节的字符
for (count = 1L; count <= last; count++)
{
fseek(fp, -count, SEEK_END);
ch = getc(fp);
}

其他函数

刷新缓冲区 fflush ()

函数名称:fflush
函数原型:int fflush(FILE *fp);
函数作用:调用该函数将刷新缓冲区,即将输出缓冲区中所有的未写入数据被发送到 fp 指定的输出文件。如果 fp 为空指针,所有输出缓冲区都被刷新。
返回值:成功返回 0,错误返回 EOF。

创建替换使用缓冲区 setvbuf ()

函数名称:setvbuf
函数原型:int setvbuf(FILE * restrict fp, char * restrict buf, int mode, size_t size);
返回值:成功返回 0,否则返回非零值。

第二个参数指向待使用的缓冲区。如果是 NULL,则自动分配。
第三个参数为模式,有下边几种:

模式 描述
_IOFBF 全缓冲:对于输出,数据在缓冲填满时被一次性写入。对于输入,缓冲会在请求输入且缓冲为空时被填充。
_IOLBF 行缓冲:对于输出,数据在遇到换行符或者在缓冲填满时被写入,具体视情况而定。对于输入,缓冲会在请求输入且缓冲为空时被填充,直到遇到下一个换行符。
_IONBF 无缓冲:不使用缓冲。每个 I/O 操作都被即时写入。buffer 和 size 参数被忽略。

feof () 和 ferror ()

当上一次输入调用检测到文件结尾时,feof () 函数返回一个非零值,否则返回 0。
当读写出现错误,ferror () 函数返回一个非零值,否则返回 0。