第12章文件 引言 在程序设计中,往往要对大量数据进行处理。许多程序在实现过程中,依赖于把数据 保存到变量中,而变量是通过内存单元存储数据的,数据的处理完全由程序控制。当一个 程序运行完成或者终止运行,所有变量的值不再保存。另外,一般的程序都会有数据输入 与输出,如果输入输出数据量不大,通过键盘和显示器就可方便解决,而当数据较多时就 极为繁琐。例如,学生成绩管理系统要用到学生的学号、姓名以及课程成绩等数据,对于 一个几千人的中等规模的院校,可能要对几万到几十万个数据进行处理,数据量很大。 文件是解决上述问题的有效办法,它通过把数据存储在磁盘文件中,得以长久保存。 每次运行程序时从磁盘文件中读取数据,程序的输出数据也存入磁盘中,这就为程序运行 提供了很大方便。为此,C语言提供了一系列对磁盘文件进行的操作。 本章将重点介绍文件的概念、文件的分类以及文件操作的相关函数,包括fopen(、 fclose()、fgetc()、fpute(、)fprintf0(、fscnaf0、fgets(、fputs()、exit()、ffush()、fread( fwrite(),rewind()、fseek()、feof(). 12.1文件概述 12.1.1文件的概念 文件是指一组相关数据的有序集合,文件通常是驻留在外部存储介质(如磁盘等)上 的,在使用时才调入内存。比如stdio.h就是一个包含一些有用信息的文件的名称。 与计算机内存存储数据不同,文件通常对应于程序的地址空间之外的存储器,比如U 盘或硬盘。存取数据的细节都是由操作系统来实现的,程序员只需要考虑的是如何在C程 序中处理文件。 12.1.2文件的分类 可以从不同的角度对文件进行分类。 从用户的角度,文件可分为普通文件和设备文件两种。普通文件是指驻留在磁盘或其 他外部介质上的一个数据集,可以是源文件、目标文件、可执行程序,也可以是一组待输 入处理的原始数据,或者是一组输出结果:设备文件是指与主机相关联的各种外部设备, 如显示器、打印机、键盘等。 从文件编码的角度,文件可分为文本文件和二进制文件两种。两者的差别在于存储数 值型数据的方式不同。文本文件将数值型数据的每一位数字作为一个字符以其ASCI码的 形式存储,例如源程序文件、记事本文件等都是文本文件,用DOS命令TYPE可显示文
第 12 章 文件 引言: 在程序设计中,往往要对大量数据进行处理。许多程序在实现过程中,依赖于把数据 保存到变量中,而变量是通过内存单元存储数据的,数据的处理完全由程序控制。当一个 程序运行完成或者终止运行,所有变量的值不再保存。另外,一般的程序都会有数据输入 与输出,如果输入输出数据量不大,通过键盘和显示器就可方便解决,而当数据较多时就 极为繁琐。例如,学生成绩管理系统要用到学生的学号、姓名以及课程成绩等数据,对于 一个几千人的中等规模的院校,可能要对几万到几十万个数据进行处理,数据量很大。 文件是解决上述问题的有效办法,它通过把数据存储在磁盘文件中,得以长久保存。 每次运行程序时从磁盘文件中读取数据,程序的输出数据也存入磁盘中,这就为程序运行 提供了很大方便。为此,C 语言提供了一系列对磁盘文件进行的操作。 本章将重点介绍文件的概念、文件的分类以及文件操作的相关函数,包括 fopen()、 fclose()、fgetc()、fputc()、fprintf()、 fscnaf()、fgets()、 fputs()、exit()、fflush()、fread()、 fwrite()、rewind()、fseek()、feof()。 12.1 文件概述 12.1.1 文件的概念 文件是指一组相关数据的有序集合,文件通常是驻留在外部存储介质(如磁盘等)上 的,在使用时才调入内存。比如 stdio.h 就是一个包含一些有用信息的文件的名称。 与计算机内存存储数据不同,文件通常对应于程序的地址空间之外的存储器,比如 U 盘或硬盘。存取数据的细节都是由操作系统来实现的,程序员只需要考虑的是如何在 C 程 序中处理文件。 12.1.2 文件的分类 可以从不同的角度对文件进行分类。 从用户的角度,文件可分为普通文件和设备文件两种。普通文件是指驻留在磁盘或其 他外部介质上的一个数据集,可以是源文件、目标文件、可执行程序,也可以是一组待输 入处理的原始数据,或者是一组输出结果;设备文件是指与主机相关联的各种外部设备, 如显示器、打印机、键盘等。 从文件编码的角度,文件可分为文本文件和二进制文件两种。两者的差别在于存储数 值型数据的方式不同。文本文件将数值型数据的每一位数字作为一个字符以其 ASCII 码的 形式存储,例如源程序文件、记事本文件等都是文本文件,用 DOS 命令 TYPE 可显示文
件的内容:二进制文件是将数值型数据以二进制形式存放的,例如可执行C程序的内容是 存储在二进制文件中的。文本文件中字节表示字符,由于是按字符显示,因此能读懂文件 内容:二进制文件字节不一定表示字符,字节组也可以表示整数和浮点数,虽然也可在屏 幕上显示,但其内容无法读懂。 前面我们已经介绍计算机的存储在物理上是二进制的,所以文本文件与二进制文件的 区别并不是物理上的,而是逻辑上的,这两者只是在编码层次上有差异。例如整型数据5678 以二进制形式存储需要两个字节的存储空间,见表121:而以文本文件存储则需要四个字 节的存储空间,见表122 表12-1二进制文件中数据5678占2个字节 0001011000101110 表12-2文本文件中数据5678占4个字节 字符 5 *6 479 8* 十进制的ASCI码 53 54 55 56 ASCIⅡ码转换为二进制 00110101 0011011000110111 00111000 C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。输入 输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。因此也把这种 文件称作“流式文件”。 12.1.3标准文件V0 编程从文件读取信息或者将结果写入文件是一种经常性的需求。为此,C提供了功能 强大的文件通信方法。使用这种方法可以在程序中打开文件,然后使用专门的0函数读 取或者写入文件 C语言通信有交互式VO和文件VO两种方式。交互式VO意味着与人或物理设备通信, 人或设备都与运行着的程序并行工作,送给程序的输入可能依赖于程序在此前的输出(例 如提示)。到目前为止,我们所讨论的例子都是从标准输入读取数据并向标准输出输出数据。 标准高级I/O(standard high-level I/O)使用一个标准的C库函数包和stdio.h头文件中的 定义。标准VO包中包含很多专用的函数,可以方便地处理不同的VO问题。例如,printf) 将各种类型的数据转换成为活合终瑞的字符串输出。 同时,文件VO允许C将数据保存到文件中或者从文件中读取数据。标准O对输入 和输出进行了缓冲。也就是在读写文件时,会把一大块数据复制到缓冲区(一块中介存储 区)中,再传给C语言程序或外存上。这种缓冲大大提高了数据传输率。随后程序就可以分 析缓冲区中的个别字节,缓冲过程是在后台处理的,所以你会产生逐字节读取的错觉。 ANSI C采用缓冲文件系统,即既用缓冲文件系统处理文本文件,也用它来处理二进 制文件。本章主要介绍ANSIC规定的缓冲文件系统以及对它的读写。 2
2 件的内容;二进制文件是将数值型数据以二进制形式存放的,例如可执行 C 程序的内容是 存储在二进制文件中的。文本文件中字节表示字符,由于是按字符显示,因此能读懂文件 内容;二进制文件字节不一定表示字符,字节组也可以表示整数和浮点数,虽然也可在屏 幕上显示,但其内容无法读懂。 前面我们已经介绍计算机的存储在物理上是二进制的,所以文本文件与二进制文件的 区别并不是物理上的,而是逻辑上的。这两者只是在编码层次上有差异。例如整型数据 5678 以二进制形式存储需要两个字节的存储空间,见表 12-1;而以文本文件存储则需要四个字 节的存储空间,见表 12-2: 表 12-1 二进制文件中数据 5678 占 2 个字节 00010110 00101110 表 12-2 文本文件中数据 5678 占 4 个字节 字符 ‘5’ ‘6’ ‘7’ ‘8’ 十进制的 ASCII 码 53 54 55 56 ASCII 码转换为二进制 00110101 00110110 00110111 00111000 C 系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。输入 输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。 因此也把这种 文件称作“流式文件”。 12.1.3 标准文件 I/O 编程从文件读取信息或者将结果写入文件是一种经常性的需求。为此,C 提供了功能 强大的文件通信方法。使用这种方法可以在程序中打开文件,然后使用专门的 I/O 函数读 取或者写入文件。 C 语言通信有交互式 I/O 和文件 I/O 两种方式。交互式 I/O 意味着与人或物理设备通信, 人或设备都与运行着的程序并行工作,送给程序的输入可能依赖于程序在此前的输出(例 如提示)。到目前为止,我们所讨论的例子都是从标准输入读取数据并向标准输出输出数据。 标准高级 I/O (standard high-level I/O) 使用一个标准的 C 库函数包和 stdio.h 头文件中的 定义。标准 I/O 包中包含很多专用的函数,可以方便地处理不同的 I/O 问题。例如,printf() 将各种类型的数据转换成为适合终端的字符串输出。 同时,文件 I/O 允许 C 将数据保存到文件中或者从文件中读取数据。标准 I/O 对输入 和输出进行了缓冲。也就是在读写文件时,会把一大块数据复制到缓冲区 (一块中介存储 区)中,再传给 C 语言程序或外存上。这种缓冲大大提高了数据传输率。随后程序就可以分 析缓冲区中的个别字节,缓冲过程是在后台处理的,所以你会产生逐字节读取的错觉。 ANSI C 采用缓冲文件系统,即既用缓冲文件系统处理文本文件,也用它来处理二进 制文件。本章主要介绍 ANSI C 规定的缓冲文件系统以及对它的读写
12.2文件指针 在C语言中,用一个指针变量指向一个文件,该指针称为文件指针。通过文件指针可 以对文件进行各种操作。定义文件指针的一般形式为: EILE指针变量标识符 说明:FLE是由系统定义的一个结构体,其中含有文件名、文件状态和文件当前位置 等信息,在编写源程序时不必关心LE结构体的细节。 例如:FLE*p, 含义:fp是指向FILE结构体的指针变量,通过fp即可找到存放某个文件信息的结构 体信息,按结构体信息找到该文件,可以实施对文件的操作。习惯上把印称为指向一个文 件的指针,简称文件指针。 12.3文件的打开与关闭 12.3.1文件打开函数(fopen)与程序结束函数(exit) ANSIC规定了标准输入输出函数库.fopen函数用于打开一个文件,这一函数在stdio.h 中声明。其调用的一般形式为:它的 文件指针名=fopen(文件名,文件打开方式): 说明 (1)文件指针名必须是被说明的FLE类型的指针变量。 (2)第一个参数是要打开的文件名,文件名是字符串常量或字符数组,文件名可以包 含路径。更确切地说,是包含该文件名的字符串地址。 (3)第二个参数文件打开方式是用于指定文件打开模式的一个字符串,是指文件的类 型和对文件的操作要求。 fopen()i函数在打开一个文件时,需将以下三个信息通知编译系统:①需要打开的文件 名:②文件打开方式(读还是写等):③让哪一个文件指针变量指向被打开的文件。例如: FILE fp: fp=fopen("c:\\filel.dat","rb"): 含义:以只读方式打开C盘根目录下的二进制文件lel.dat。两个反斜线“W”中的第 个表示转义字符,第二个表示根目录。“b”是文件打开方式,C语言中的文件打开方式 见表12-3。 3
3 12.2 文件指针 在C语言中,用一个指针变量指向一个文件,该指针称为文件指针。通过文件指针可 以对文件进行各种操作。定义文件指针的一般形式为: FILE *指针变量标识符; 说明:FILE 是由系统定义的一个结构体,其中含有文件名、文件状态和文件当前位置 等信息,在编写源程序时不必关心 FILE 结构体的细节。 例如:FILE *fp; 含义:fp 是指向 FILE 结构体的指针变量,通过 fp 即可找到存放某个文件信息的结构 体信息,按结构体信息找到该文件,可以实施对文件的操作。习惯上把 fp 称为指向一个文 件的指针,简称文件指针。 12.3 文件的打开与关闭 12.3.1 文件打开函数(fopen)与程序结束函数(exit) ANSI C规定了标准输入输出函数库。fopen函数用于打开一个文件,这一函数在 stdio.h 中声明。其调用的一般形式为:它的;. 文件指针名=fopen(文件名,文件打开方式); 说明: (1)文件指针名必须是被说明的 FILE 类型的指针变量。 (2)第一个参数是要打开的文件名,文件名是字符串常量或字符数组,文件名可以包 含路径。更确切地说, 是包含该文件名的字符串地址。 (3)第二个参数文件打开方式是用于指定文件打开模式的一个字符串,是指文件的类 型和对文件的操作要求。 fopen()函数在打开一个文件时,需将以下三个信息通知编译系统:①需要打开的文件 名;②文件打开方式(读还是写等);③让哪一个文件指针变量指向被打开的文件。例如: FILE *fp; fp=fopen("c:\\file1.dat","rb"); 含义:以只读方式打开 C 盘根目录下的二进制文件 file1.dat。两个反斜线“\\”中的第 一个表示转义字符,第二个表示根目录。“rb”是文件打开方式,C 语言中的文件打开方式 见表 12-3
表12-3文件打开方式及其含义 文件打开方式 只读打开一个文本文件 只写打开或建 个文本文件,只允许写数据,如果文件存在则重写 超加打开或建立 个文本文件,并在文件末尾写数据 “rb 只 进制文件,只允许读数据 46 只写打开或建立 个二进制文件,只允许写数据,如果文件存在则重写 'ab' 道川开双建立 二进制文件,并在文件术尾写数据 “t+ 读写打开一个文本文件,允许读和写 t+ 读写打开或建立一个文本文件,允许读写,如果文件存在则重写 “at+ 读写打开或建立 一个文本文件,允许读取整个文件,或在文件末追加数据 “rb+ 读写打开一个二进制文件,允许读和写 “wb+ 读写打开或建立一个二进制文件,允许读和写,如果文件存在则重写 “ab+ 读写打开或建立一个二进制文件,允许读取整个文件,或在文件术追加数据 说明 (1)文件打开方式由r、w、a、t、b、+六个字符拼成,含义如下。 ·r(read: 。w(write): 写 ·a(append): 追加 t(text): 文本文件,可省路不写 ·b(banary): 二进制文件 。+: 读和写 (2)用“”打开一个文件时,该文件必须己经存在,且只能从该文件读出信息。 (3)用“w”打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件 名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。 (4)用“a”打开一个文件,若文件已存在,则在文件末尾追加新的信息。以追加方式 打开文件时,文件位置指针自动指向文件末尾。若文件不存在,则重建一个文件。 无论采用哪种打开方式,程序成功地打开一个文件后,fopen(0函数返回一个文件指针 其他I/O函数用这个指针来指定该文件。文件指针(比如这个例子中的)是一种指向 FLE的指针。指针并不指定实际的文件,而是一个关于文件信息的数据包,其中包括 文件/O使用的缓仲区信息。 因为标准库中的O函数使用缓冲区,所以它们需要知道缓冲区的位置,还需要知道 缓冲区的当前缓冲能力以及所使用的文件,这样这些函数在必要的时候可以再次填充或者 清空缓冲区。指向的数据包中包含全部这些信息。 在打开一个文件时,如果出错,fopen函数返回一个空指针值NULL。据此在程序中可 以判断是否能顺利打开文件。例如: if((fp=fopen("c:\\filel.dat","rb"))==NULL) printf("\n error on open c:\filel.dat !") exit(1) 4
4 表 12-3 文件打开方式及其含义 文件打开方式 含 义 “rt” 只读打开一个文本文件,只允许读数据 “wt” 只写打开或建立一个文本文件,只允许写数据,如果文件存在则重写 “at” 追加打开或建立一个文本文件,并在文件末尾写数据 “rb” 只读打开一个二进制文件,只允许读数据 “wb” 只写打开或建立一个二进制文件,只允许写数据,如果文件存在则重写 “ab” 追加打开或建立一个二进制文件,并在文件末尾写数据 “rt+” 读写打开一个文本文件,允许读和写 “wt+” 读写打开或建立一个文本文件,允许读写,如果文件存在则重写 “at+” 读写打开或建立一个文本文件,允许读取整个文件,或在文件末追加数据 “rb+” 读写打开一个二进制文件,允许读和写 “wb+” 读写打开或建立一个二进制文件,允许读和写,如果文件存在则重写 “ab+” 读写打开或建立一个二进制文件,允许读取整个文件,或在文件末追加数据 说明: (1)文件打开方式由 r、w、a、t、b、+ 六个字符拼成,含义如下。 r(read): 读 w(write): 写 a(append): 追加 t(text): 文本文件,可省略不写 b(banary): 二进制文件 +: 读和写 (2)用“r”打开一个文件时,该文件必须已经存在,且只能从该文件读出信息。 (3)用“w”打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件 名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。 (4)用“a”打开一个文件,若文件已存在,则在文件末尾追加新的信息。以追加方式 打开文件时,文件位置指针自动指向文件末尾。若文件不存在,则重建一个文件。 无论采用哪种打开方式,程序成功地打开一个文件后,fopen() 函数返回一个文件指针, 其他 I/O 函数用这个指针来指定该文件。文件指针 (比如这个例子中的 fp) 是一种指向 FILE 的指针。指针 fp 并不指定实际的文件,而是一个关于文件信息的数据包,其中包括 文件 I/O 使用的缓冲区信息。 因为标准库中的 I/O 函数使用缓冲区,所以它们需要知道缓冲区的位置,还需要知道 缓冲区的当前缓冲能力以及所使用的文件,这样这些函数在必要的时候可以再次填充或者 清空缓冲区。fp 指向的数据包中包含全部这些信息。 在打开一个文件时,如果出错,fopen 函数返回一个空指针值 NULL。据此在程序中可 以判断是否能顺利打开文件。例如: if((fp=fopen("c:\\file1.dat","rb"))==NULL) { printf("\n error on open c:\file1.dat !"); exit(1);
程序含义:如果返回的指针为空,则不能正确打开C盘根目录下的ll.dat文件,给 出提示信息“error on open c:\filel.dat!”,退出程序。 磁盘已满,文件名非法,存取权限不够或者硬件问题等都会导致fopen()函数执行失 败。 x)函数关闭所有打开的文件并终止程序。在使用xiO函数时村,通常的约定是正常 终止的程序传递值0,非正常终止的程序传递非0值,不同的退出值可以用来标识导致程 序失败的不同原因。 按照ANSIC,在最初调用的main(中使用return0和调用exit(0)的效果相同。两 者的区别是:如果main)在一个递归程序中,exit0仍然会终止程序:但return将控制权 移交给递归的前一级,直到最初的那一级,此时return才会终止程序。 12.3.2文件关闭函数(fclose) 文件使用完毕,应该关闭文件,断开文件指针与文件之间的联系,同时根据需要刷新 缓冲区。fclose函数调用的一般形式为: fc1o3e(文件指针): 例如: fclose(fp); 说明:正常关闭文件操作,fclose函数返回值为0:如返回非零值,则表示有错误发 生。 12.4文本文件的读写 对文件的读和写是最常用的文件操作,C语言中提供了多种文件读写函数,使用文件 操作函数都要求包含头文件stdio.h。 12.4.1字符读写函数(getc和putc) 前面章节中的每个/O函数都存在一个相似的文件/O函数,主要的区别在于你需要 使用一个FLE指针来为这些新函数指定要操作的文件。与getchar(和putchar(相似,字符 读写函数getc0和fputc()以字符(字节)为单位,每次可从文本文件读出或写入一个字符, 不同之处在于你需要告诉fgetc(和fpute(0函数他们要使用的文件。 1.字符读函数(fgete) 格式:字符变量=getc(文件指针): 功能:从指定的文件中读一个字符 5
5 } 程序含义:如果返回的指针为空,则不能正确打开 C 盘根目录下的 file1.dat 文件,给 出提示信息“error on open c:\file1.dat !”,退出程序。 磁盘已满,文件名非法,存取权限不够或者硬件问题等都会导致 fopen() 函数执行失 败。 exit() 函数关闭所有打开的文件并终止程序。在使用 exit()函数时,通常的约定是正常 终止的程序传递值 0,非正常终止的程序传递非 0 值,不同的退出值可以用来标识导致程 序失败的不同原因。 按照 ANSI C,在最初调用的 main() 中使用 return 0 和调用 exit(0) 的效果相同。两 者的区别是:如果 main() 在一个递归程序中,exit()仍然会终止程序;但 return 将控制权 移交给递归的前一级,直到最初的那一级,此时 return 才会终止程序。 12.3.2 文件关闭函数(fclose) 文件使用完毕,应该关闭文件,断开文件指针与文件之间的联系,同时根据需要刷新 缓冲区。fclose 函数调用的一般形式为: fclose(文件指针); 例如: fclose(fp); 说明:正常关闭文件操作,fclose 函数返回值为 0;如返回非零值,则表示有错误发 生。 12.4 文本文件的读写 对文件的读和写是最常用的文件操作,C语言中提供了多种文件读写函数,使用文件 操作函数都要求包含头文件 stdio.h。 12.4.1 字符读写函数(fgetc 和 fputc) 前面章节中的每个 I/O 函数都存在一个相似的文件 I/O 函数,主要的区别在于你需要 使用一个 FILE 指针来为这些新函数指定要操作的文件。与 getchar()和 putchar()相似,字符 读写函数 fgetc()和 fputc()以字符(字节)为单位,每次可从文本文件读出或写入一个字符, 不同之处在于你需要告诉 fgetc()和 fputc() 函数他们要使用的文件。 1.字符读函数(fgetc) 格式:字符变量=fgetc(文件指针); 功能:从指定的文件中读一个字符