BlockLune's Blog

首页 标签 关于

C 语言文件操作

published at 2023-01-12
cprogramming-languagefile-iostdio

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

前期准备

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

打开文件 fopen()

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

例如:

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()

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

{% note warning no-icon %} 注意区分 fopen() 和 fclose() 的返回值!前者失败时返回 NULL(通常情况下就是 0),后者成功时返回 0。 {% endnote %}

读写文件

fprintf() 和 fscanf()

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

getc() 和 putc()

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

ungetc()

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

fgets() 和 fputs()

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

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

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

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

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

{% note warning no-icon %} 注意区分 fgets() 与 gets(), fputs() 与 puts()!gets() 不保留换行符所以 puts() 自动添加换行符;fgets() 保留换行符所以 fputs() 不会添加换行符。 {% endnote %}

fread() 和 fwrite()

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

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

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

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

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

// 保存一个大小为 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()

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

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

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

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 个字节

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

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

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

其他函数

刷新缓冲区 fflush()

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

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

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

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

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

feof() 和 ferror()

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