C程序设计
第13章 文件 文件(file)是程序设计中一个重要的概念。所谓“文件”一般指存储在外部介质上数据的集合。一批数据是以文件的形式存放在外部介质(如磁盘)上的。操作系统是以文件为单位对数据进行管理的,也就是说,如果想找存在外部介质上的数据,必须先按文件名找到所指定的文件,然后再从该文件中读取数据。要向外部介质上存储数据也必须先建立一个文件(以文件名标识),才能向它输出数据。 以前各章中所用到的输入和输出,都是以终端为对象的,即从终端键盘输入数据,运行结果输出到终端上。从操作系统的角度看,每一个与主机相联的输入输出设备都看作是一个文件。例如,终端键盘是输入文件,显示器和打印机是输出文件。
在程序运行时,常常需要将一些数据(运行的最终结果和中间数据)输出到磁盘上存放起来,以后需要时再从磁盘中输入到计算机内存。这就要用到磁盘文件。 C语言把文件看作是一个字符(字节)的序列,即一个一个字符(字节)的数据顺序组成。根据数据的组织形式,可分为ASCII文件和二进制文件。ASCII文件又称文本文件,它的每一个字节放一个ASCII代码,代表一个字符。二进制文件是把内存中的数据按其在内存中的存储形式原样输出到磁盘上存放。
由前所述,一个C文件是一个字节流或二进制流。它把数据看作是一连串的字符(字节),而不考虑记录的界限。换句话说,C语言中文件并不是由记录(record)组成的(这是和PASCAL或其他高级语言不同的)。在C语言中对文件的存取是以字符(字节)为单位的。输入输出的数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制。也就是说,在输出时不会自动增加回车换行符以作为记录结束的标志,输入时不以回车换行符作为记录的间隔(事实上C文件并不由记录构成)。把这种文件称为流式文件。C语言允许对文件存取一个字符,这就增加了处理的灵活性。
在过去使用的C版本(如UNIX系统下使用的C)有两种对文件的处理方法:一种叫“缓冲文件系统”,一种叫“非缓冲文件系统”。所谓缓冲文件系统是指系统自动地在内存中为每一个正在使用的文件名开辟一个缓冲区。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向内存读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区(充满缓冲区),然后再从缓冲区逐个地将数据送到程序数据区(给程序变量)。缓冲区的大小由各个具体的C版本确定,一般为512字节。
所谓“非缓冲文件系统”是指系统不自动开辟确定大小的缓冲区,而由程序为每个文件设定缓冲区。 在UNIX系统下,用缓冲文件系统来处理文本文件,用非缓冲文件系统处理二进制文件。用缓冲文件系统进行的输入输出又称为高级(或高层)磁盘输入输出(高层I/O),用非缓冲文件系统进行的输入输出又称为低级(低层)输入输出系统。ANSI C标准决定不采用非缓冲文件系统,而只采用缓冲文件系统。即既用缓冲文件系统处理文本文件,也用它来处理二进制文件。也就是将缓冲文件系统扩充为可以处理二进制文件。 在C语言中,没有输入输出语句,对文件的读写都是用库函数来实现的。ANSI规定了标准输入输出函数,用它们对文件进行读写。
13.2 文件类型指针 缓冲文件系统中,关键的概念是“文件指针”。每个被使用的文件都在内存中开辟一个区,用来存放文件的有关信息(如文件的名字、文件状态及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统定义的,取名为FILE。
13.3 文件的打开与关闭 13.3.1 文件的打开(fopen函数) 文件使用方式 含义 “r”(只读) 为输入打开一个文本文件 “w”(只写) 为输出打开一个文本文件 “a”(追加) 向文本文件尾增加数据 “rb”(只读) 为输入打开一个二进制文件 “wb”(只写) 为输出打开一个二进制文件 “ab”(追加) 向二进制文件尾增加数据 “r+”(读写) 为读/写打开一个文本文件 “w+”(读写) 为读/写简历一个新的文本文件 “a+”(读写) “rb+”(读写) 为读/写打开一个二进制文件 “wb+”(读写) 为读/写建立一个新的二进制文件 “ab+”(读写)
说明: (1)用“r”方式打开的文件只能用于向计算机输入而不能用作向该文件输出数据,而且该文件应该已经存在,不能用“r”方式打开一个并不存在的文件(即输入文件),否则出错。 (2)用“w”方式打开的文件只能用于向该文件写数据(即输出文件),而不能用来向计算机输入。如果原来不存在该文件,则在打开时新建立一个以指定的名字命名的文件。如果原来已存在一个以该文件名命名的文件,则在打开时将该文件删去,然后重新建立一个新文件。 (3)如果希望向文件末尾添加新的数据(不希望删除原有数据),则应该用“a”方式打开。但此时该文件必须已存在,否则将得到出错信息。打开时,位置指针移到文件末尾。 (4)用“r+”、“w+”、 “a+”方式打开的文件既可以用来输入数据,也可以用来输出数据。用“r+”方式时该文件应该已经存在,以便能向计算机输入数据。用“w+”方式则新建立一个文件,先向此文件写数据,然后可以读此文件中的数据。用“a+”方式打开的文件,原来的文件不被删去,位置指针移到文件末尾,可以添加,也可以读。 使用文件的基本步骤 File *p; P=fopen(“a.txt”,”r”); Read/write 功能 Fclose(p);
(5)如果不能实现“打开”的任务,fopen函数将会带回一个出错信息。出错的原因可能是用“r”方式打开一个并不存在的文件;磁盘出故障;磁盘已满无法建立新文件等。此时fopen函数将带回一个空指针值NULL。 (6)在向计算机输入文本文件时,将回车换行符转换为一个换行符,在输出时把换行符转换成为回车和换行两个字符。在用二进制文件时,不进行这种转换,在内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。 (7)在程序开始运行时,系统自动打开3个标准文件:标准输入、标准输出、标准出错输出。这3个文件都与终端相联系。因此以前所用到的从终端输入或输出都不需要打开终端文件。系统自动定义了3个文件指针stdin、stdout和stderr,分别指向终端输入、终端输出和标准出错输出(也从终端输出)。如果程序中指定要从stdin所指的文件输入数据,就是指从终端键盘输入数据。
13.3.2 文件的关闭(fclose函数) 在使用完一个文件后应该关闭它,以防止它再被误用。“关闭”就是使文件指针变量不指向该文件,也就是文件指针变量与文件“脱钩”,以后不能再通过该指针对原来与其相联系的文件进行读写操作。除非再次打开,使该指针变量重新指向该文件。 int numclosed; FILE * stream, * stream2; //Open for read (will fail if file "data" does not exist) if( (stream = fopen( "data.txt", "r" )) == NULL ) { printf( "The file 'data' was not opened\n" ); exit(0); } else printf( "The file 'data' was opened\n" ); // Open for write if( (stream2 = fopen( "data2", "w+" )) == NULL ) printf( "The file 'data2' was not opened\n" ); printf( "The file 'data2' was opened\n" ); //Close stream //if(stream != NULL) //{ if( fclose( stream ) ) printf( "The file 'data' was not closed\n" ); //} //All other files are closed: numclosed = _fcloseall( ); printf( "Number of files closed by _fcloseall: %u\n", numclosed );
13.4 文件的读写 13.4.1 文本文件的读写 1、写字符函数fputc和读字符函数fgetc [例13.1]从键盘输入一些字符,逐个把它们送到磁盘上去,直到输入一个“#”为止 [例13.2]将一个磁盘文件中的信息复制到另一个磁盘文件中 2、写字符串函数fputs和读字符串函数fgets(书上344页) [例13.3]将学生数据,由键盘输入并存储到磁盘文件中(这个例题没有用书上339页的方法实现,而是用fputs函数实现) [例13.4]从上例文件中读取学生数据,并显示在屏幕上 3、格式化写函数fprintf和格式化读函数fscanf [例13.5]将学生数据,由键盘输入并存储到磁盘文件中 [例13.6]从上例文件中读取学生数据,并显示在屏幕上 以下需要加上头文件 #include <stdlib.h> Fgetc用法: FILE * fp; if((fp=fopen("data.txt", "r")) == NULL) { printf("can not open file\n"); exit(0); } printf("%c", fgetc(fp)); printf("%c", fgetc(fp)) Fputc用法: char c; if((fp=fopen("data.txt", "w")) == NULL) fputc('a', fp); fputc('b', fp); //fputc('\n', fp); fputc('c', fp); fputc('d', fp); fputc('e', fp); fputc('f', fp); fclose(fp); while (!feof(fp)) //feof用于测试是否到达文件尾部 ,下面的循环用于顺序读出文件中的内容 { c=fgetc(fp); printf("%c",c); return 0;
13.4.2 二进制文件的读写 1、fread函数和fwrite函数 [例13.7]从键盘输入4个学生数据,然后把它们转存到磁盘文件上去,然后再从磁盘文件中一次性读入内存并显示出来
13.5 文件的定位 文件中有一个位置指针,指向当前读写的位置。如果顺序读写一个文件,每次读写一个字符,则读写完一个字符后,该位置指针自动移动指向下一个字符位置。如果想改变这样的规律,强制使位置指针指向其他指定的位置,可以用后面介绍的有关函数。
13.5.1 rewind函数 rewind函数的作用是使位置指针重新返回文件的开头,此函数没有返回值。 [例13.8]有一个磁盘文件,第一次将它的内容显示在屏幕上,第二次把它复制到另一文件上
13.5.2 fseek函数和随机读写 对流式文件可以进行顺序读写,也可以进行随机读写,关键在于控制文件的位置指针。如果位置指针是按字节位置顺序移动的,就是顺序读写;如果能将位置指针按需要移动到任意位置,就可以实现随机读写。所谓随机读写,是指读写完上一个字符(字节)后,并不一定要读写其后的字符(字节),而可以读写文件中任意位置上所需要的字符(字节)。 用fseek函数可以实现改变文件的位置指针。 [例13.9]在磁盘文件上存有10个学生的数据。要求将第1、3、5、7、9个学生数据输入计算机,并在屏幕上显示出来
13.5.3 ftell函数 例题:ftell函数使用.cpp #include <stdio.h> #include <stdlib.h> int main(void) { FILE * stream; long position; char list[100]; if( (stream = fopen( "1.jpg", "rb" )) != NULL ) // 通过读数据移动指针 fread( list, sizeof( char ), 100, stream ); // Get position after read: position = ftell( stream ); //获取当前指针指向的字节数 printf( "Position after trying to read 100 bytes: %ld\n", position ); fclose( stream ); } return 0;
13.6 出错的检测 13.6.1 ferror函数(例题: ferror函数.cpp) 13.6.2 clearerr函数 #include <stdio.h> #include <stdlib.h> int main(void) { int count, total = 0; char buffer[100]; FILE *stream; if( (stream = fopen( "ReadMe.txt", "r" )) == NULL ) exit( 1 ); /* Cycle until end of file reached: */ while( !feof( stream ) ) // Attempt to read in 100 bytes: count = fread( buffer, sizeof( char ), 100, stream ); if( ferror( stream ) ) { perror( "Read error" ); break; } // 计算总的实际字节数 total += count; printf( "Number of bytes read = %d\n", total ); fclose( stream ); return 0; Clearerr int c; /* Create an error by writing to standard input. */ putc( 'c', stdin ); if( ferror( stdin ) ) perror( "Write error" ); clearerr( stdin ); /* See if read causes an error. */ printf( "Will input cause an error? " ); c = getc( stdin );
13.7 文件输入输出小结 分类 函数名 功能 打开文件 fopen() 关闭文件 fclose() 文件定位 fseek() 改变文件位置指针的位置 rewind() 使文件位置指针重新置于文件开头 ftell() 返回文件位置指针的当前值 文件读写 fgetc(), getc() 从指定文件取得一个字符 fputc(), putc() 把字符输出到指定文件 fgets() 从指定文件读取字符串 fputs() 把字符串输出到指定文件 getw() 从指定文件读取一个字(int型) putw() 把一个字(int型)输出到指定文件 fread() 从指定文件中读取数据项 fwrite() 把数据项写到指定文件 fscanf() 从指定文件按格式输入数据 fprintf() 按指定格式将数据写到指定文件中 文件状态 feof() 若到文件末尾,函数值为“真”(非0) ferror() 若对文件操作出错,函数值为“真”(非0) clearerr() 使ferror和feof函数值置零
习题 13.4 从键盘输入一个字符串,将其中的小写字母全部转换成大写字母,然后输出到一个磁盘文件“test”中保存。输入的字符以“!”结束。 13.5 有两个磁盘文件“A”和“B”,各存放一行字母,今要求把这两个文件中的信息合并(按字母顺序排列),输出到一个新文件“C”中去。 13.6 有5个学生,每个学生有3门课程的成绩,从键盘输入学生数据(包括学号,姓名,3门课程成绩),计算出平均成绩,将原有数据和计算出的平均分存放在磁盘文件“stud”中。 13.7 将题13.6“stud”文件中的学生数据,按平均分进行排序处理,将已排序的学生数据存入一个新文件“stu_sort”中。 13.8 将题13.7已排序的学生成绩文件进行插入处理。插入一个学生的3门课程成绩,程序先计算新插入学生的平均成绩,然后将它按成绩高低顺序插入,插入后建立一个新文件。