继续赶博客。
全书共分17章,这是关于本书第13章内容的博客。本章主要介绍了关于文件处理的内容。博客的目录和书上目录是相似的。此系列博客的代码都在Visual Studio 2022环境下编译运行。
我目前大一刚刚结束,水平有限,博客中若有错误或者总结不到位的地方也请见谅。
目录
13.4 文件I/O:fprintf()、fscanf()、fgets()和fputs()
13.7.3 二进制I/O:fread()和fwrite()
13.1 与文件进行通信
有时,程序需要从文件中获取信息,或者将信息写入文件。可以通过文件重定向进行,但是这种方法有一定的限制。
C语言提供了更强大的文件通信方法,可以在程序中打开文件,使用特殊的I/O函数读取文件中的信息或把信息写入文件。
13.1.1 文件是什么
文件是在磁盘或固态硬盘上一段已命名的存储区。文件中包含一些信息。
C语言把文件看作一系列连续的字节,每个字节都能被单独读取。
C语言提供两种文件模式:文本模式和二进制模式。
13.1.2 文本模式和二进制模式
所有文件的内容都以二进制形式存储。如果文件最初使用二进制编码的字符(如ASCII)表示文本,该文件就是文本文件,其中包含文本内容。如果文件中的二进制值代表机器语言代码或数值数据或图片或音乐编码,该文件是二进制文件,其中包含二进制内容。
C语言有两种访问文件的模式,即二进制模式和文本模式。二进制模式中程序可以访问文件中的每个字节。文本模式中程序所见的内容与文件的实际内容不同。
13.1.3 I/O的级别
底层I/O使用操作系统提供的基本I/O服务,标准高级I/O使用C库的标准包和stdio.h头文件定义。
C语言只支持标准I/O包。
13.1.4 标准文件
C程序自动打开三个文件,分别是标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)。默认情况下标准输入是键盘,标准输出和标准错误输出是显示屏。
标准输入为程序提供输入,程序输出到标准输出。标准错误输出提供了逻辑上不同的地方发送错误信息。
13.2 标准I/O
标准I/O包可移植,便于在不同系统中使用,并且有很多函数简化和处理相关问题,此外输入和输出都是缓冲的,效率更高。
exit()函数关闭所有打开的文件并结束程序,exit()的参数被传递给操作系统。正常结束的程序传递0,否则传递非零值。宏EXIT_SUCCESS表明成功结束程序,宏EXIT_FAILURE表明结束程序失败。这两个宏和exit()函数定义在stdlib.h头文件中。
在最初使用的main()函数中使用return和exit()的效果相同,都结束程序。但是exit()在程序的任意位置都结束程序。
13.2.1 fopen()函数
程序用fopen()函数打开文件,声明在stdio.h头文件中。第一个参数是待打开文件的名称,包含该文件名的字符串的地址。第二个参数是一个字符串,指定待打开的文件的模式。
C11新增了带x字母的写模式,x模式可以防止删除原文件的内容,也可防止其他程序或线程访问正在被打开的文件。
fopen()函数打开文件后会返回一个文件指针。文件指针是指向FILE的指针,FILE是stdio.h中定义的派生类型。文件指针不指向实际的文件,指向一个包含文件信息的数据对象。
13.2.2 getc()和putc()函数
getc()函数有一个参数,是一个文件指针。getc()函数从这个文件中获取一个字符,并将其返回。
putc()函数有两个参数,第一个是一个字符,第二个是一个文件指针,函数将字符写入对应文件中。
13.2.3 文件结尾
getc()函数在遇到文件结尾时返回EOF。C程序在读到超过文件末尾时才会发现文件的结尾。
13.2.4 fclose()函数
fclose()函数有一个参数,是一个文件指针,该函数关闭参数指向的文件。成功关闭返回0,否则返回EOF。要注意检查是否成功关闭。
13.2.5 指向标准文件的指针
stdio.h头文件将三个文件指针与三个标准文件相关联,C程序会自动打开这三个标准文件。
分别是:标准输入(stdin),通常是键盘;标准输出(stdout),通常是显示器;标准错误(stderr),通常是显示器。
13.3 一个简单的文件压缩程序
同时需要打开多个文件就需要声明多个文件指针。程序单独打开和关闭每个文件。同时打开的文件数是有限的。
同一个文件指针可以处理不同的文件,前提是不同时打开。
13.4 文件I/O:fprintf()、fscanf()、fgets()和fputs()
13.4.1 fprintf()和fscanf()函数
fprintf()和printf()工作方式相似,但是fprintf()函数的第一个参数是文件指针,其余参数与printf()一样,fprintf()将数据按格式写入文件。
fscanf()和scanf()工作方式相似,但是fscanf()函数的第一个参数是文件指针,其余参数与scanf()一样,fscanf()将数据按格式从文件读入。
下面程序演示了fprintf()和fscanf()函数的用法。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX 41
int main(void)
{
FILE* fp;
char words[MAX];
if ((fp = fopen("wordy", "a+")) == NULL)
{
fprintf(stderr, "Can't open \"wordy\" file.\n");
exit(EXIT_FAILURE);
}
puts("Enter words to add to the file; press the #");
puts("key at the beginning of a line to terminate.");
while ((fscanf(stdin, "%40s", words)) == 1 && words[0] != '#')
fprintf(fp, "%s\n", words);
puts("File contents:");
rewind(fp);
while (fscanf(fp, "%s", words) == 1)
puts(words);
puts("Done!");
if (fclose(fp) != 0)
fprintf(stderr, "Error closing file\n");
return 0;
}
rewind()让程序回到文件开始处,以一个文件指针为参数。
13.4.2 fgets()和fputs()函数
fgets()函数第一个参数是存储输入位置的地址,第二个参数表示待输入字符串大小(包括空字符),最后一个是文件指针。遇到换行符(但是会读入换行符)或数量达到上限或读到文件结尾会停止读入,随后增加空字符成为字符串。
fgets()函数读到EOF返回NULL,否则返回第一个参数地址。
fputs()函数有两个参数,第一个是字符串的地址,第二个是文件指针。将数据写入文件,不会增加换行符。
13.5 随机访问:fseek()和ftell()
13.5.1 fseek()和ftell()的工作原理
fseek()有三个参数,第一个是一个文件指针,指向一个已经打开的文件。第二个是偏移量,表示要移动的距离。正数表示前移,负数表示后移,0表示不动。此参数是一个long类型的值。第三个是起点,在stdio.h中提供了关于起点的宏。
SEEK_SET表示开始处,SEEK_CUR表示当前位置,SEEK_END表示文件结尾。
如果跳转正常,fseek()返回0,否则返回-1。
ftell()函数返回long类型的数。返回参数是指向文件的当前位置距离文件开始处的字节数。定义在stdio.h中,唯一的参数是一个文件指针。
13.5.2 二进制模式和文本模式
ftell()函数在二进制和文本模式的工作方式不同,最好使用二进制模式。
13.5.3 fgetpos()和fsetpos()函数
fgetpos()和fsetpos()用来处理较大文件。这两个函数使用fpos_t类型,不使用long类型。
13.6 标准I/O的机理
fopen()函数不仅打开一个文件,还创建了一个缓冲区(读写模式下创建两个缓冲区),和一个包含文件和缓冲区数据的结构,fopen()函数返回一个指向该结构的指针。
13.7 其他标准I/O函数
13.7.1 ungetc()函数
ungetc()函数有两个参数,第一个参数是一个字符,第二个参数是一个文件指针,将指定字符放回输入流中。每次放回一个字符。
13.7.2 fllush()函数
fllush()函数的唯一参数是一个文件指针,调用此函数导致缓冲区所有未写入数据被发送到指针指定的文件,这个过程是刷新缓冲区。
如果参数是空指针,会刷新所有输入缓冲区。输入流中使用此函数的结果是未定义的。
只要最近一次操作不是输入操作就可以用此函数更新流。
13.7.3 二进制I/O:fread()和fwrite()
fprintf()把数值转化为字符数据,这种转变可能改变值。为了保证数值在存储前后一致,最精确的做法是使用与计算机相同的位组合来存储。如果以程序所用的表示法将数据存储在文件中,就是二进制形式存储。
fread()和fwrite()以二进制形式处理数据。
所有数据都是二进制方式存储的。如果所有数据都被解释成字符码,该文件就包含文本数据。如果所有数据被解释成二进制形式的数值数据,就包含二进制数据。用数据表示机器语言指令的文件都是二进制文件。
一般用二进制模式在二进制文件中存储二进制数据。用文本格式打开文本文件中的文本数据。
13.7.4 fwrite()函数
fwrite()函数有四个参数,第一个是待写入数据块的地址,第二个是待写入数据块的大小(字节为单位),第三个是待写入数据块的数量,第四个是一个文件指针。
fwrite()把二进制数据写入文件,并返回成功写入项的数量。
13.7.5 fread()函数
fread()函数接受的参数与fwrite()相同。以二进制形式读取数据,返回成功读取项的数量。
13.7.6 feof()函数和ferror()函数
这两个函数的参数都是一个文件指针。
如果上次输入调用检测到文件结尾时,feof()返回非零值,否则返回零。当读或写出现错误,ferror()返回一个非零值,否则返回零。
下面是一个使用二进制文件的例子。
#include<stdio.h>
#include<stdlib.h>
#define ARSIZE 1000
int main(void)
{
double numbers[ARSIZE];
double value;
const char* file = "number.dat";
int i;
long pos;
FILE* iofile;
for (i = 0; i < ARSIZE; i++)
numbers[i] = 100.0 * i; +1.0 / (i + 1);
if ((iofile = fopen(file, "wb")) == NULL)
{
fprintf(stderr, "Could not open %s for output.\n", file);
exit(EXIT_FAILURE);
}
fwrite(numbers, sizeof(double), ARSIZE, iofile);
fclose(iofile);
if ((iofile = fopen(file, "rb")) == NULL)
{
fprintf(stderr, "Could not open %s for random access.\n", file);
exit(EXIT_FAILURE);
}
printf("Enter an index in the range 0-%d.\n", ARSIZE - 1);
while (scanf("%d", &i) == 1 && i >= 0 && i < ARSIZE)
{
pos = (long)i * sizeof(double);
fseek(iofile, pos, SEEK_SET);
fread(&value, sizeof(double), 1, iofile);
printf("The value there is %f.\n", value);
printf("Next index (out of range to quit):\n");
}
fclose(iofile);
puts("Bye!");
return 0;
}