第八章 数据文件处理技术
注:本文参考中国铁道出版社《C语言程序设计(第三版)》书写
主要用于记录学习中的重点要点,欢迎指出错误
同时也欢迎交流学习~
8.1 文件类型 and 文件类型指针变量
8.1.1 数据流
计算机系统中,两种不同形式的数据流:
- 字符形式存储的数据流文件,正文文件(or文本文件)
- 字节形式存储的数据流文件,二进制文件
首先需要强调的是,最终计算机存储两种文件都是用0与1构成的二进制(物理层面没有差别)
因而它们的主要的区别是在于编码方式(逻辑层面):
文本文件因为是基于字符编码的,一般采用定长编码方式,常见的有ASCII编码、Unicode编码
而二进制文件则不存在统一的字符编码
这可以解释一般来说用打开文本文件的方式来打开二进制文件都会造成所谓的乱码
因此,两者的优缺点也由编码方式的差异体现:
- 文本文件:
优点:字符定长,易于译码;可读性更高;
缺点:至少需要一个基础字符来表示一个意思,空间存储利用率相比较低;相比而言读写需要转换时间; - 二进制文件:
优点:编码变长,存储利用率一般较高;读写不需要转换时间;
缺点:译码方式相对而言不统一,译码难度更高;可读性较差;
总结来说,参考这两种文件的优缺点,我们可以借助一般的实际使用场景来理解
对于存储字符数据来说,两者实际上没有区别
对于存储非字符数据,则需要根据实际需要区别使用:
- 需要频繁保存和访问的数据,采取二进制文件存放,节省存储空间和转换时间
- 需要频繁向终端显示数据或从终端输入数据,建议采取文本文件存放,节省转换时间
8.1.2 操作系统下程序使用文件的方式
为避免应用程序直接从外存种读取文件信息,一般由操作系统来直接管控对常规设备的控制、I/O操作等
为高效读/写数据,操作系统为每个正在被程序使用的文件分配两个内存块:I/O缓冲区 and 控制块
8.1.3 文件I/O控制块:一种结构
命名:FILE
一般称为 文件类型
定义在头文件stdio.h
之中
一般都是使用FILE *
,因为文件打开函数返回的是系统分配的控制块的指针
我们需要使用 文件类型指针 来保存
值得注意的是
C的运行系统中,有几个专门的文件指针变量:
- stdin 键盘输入数据流
- stdout 输出到屏幕的输出数据流
- stdprn 输出到打印机的数据流
8.2 几个常用的数据文件库函数
8.2.1 fopen()
程序在文件输入/输出数据之前,要先用fopen()打开文件
调用方式:
FILE *fopen( char *fname , char *mode )
解析:
- fname为文件名(包含路径)
- mode为文件的使用方式,指定文件I/O方式
实际运行过程:
调用后,函数分配一个控制块结构,并返回此结构的指针
此后,就可以使用此指针为实参调用文件输入/输出操作的库函数
mode的使用方式的参数及意义:
文件使用方式 | 意义 |
---|---|
“r” | 只读,为输入打开文本文件 |
“r+” | 读/写,为输入/输出打开文本文件 |
“w” | 只写,为输出打开文本文件 |
“w+” | 读/写,为输入/输出建立并打开新的文本文件 |
“a” | 追加,输出从正文文件的末尾开始 |
“a+” | 读/写,为输入/输出打开正文文件 |
“rb” | 只读,为输入打开二进制文件 |
“rb+” | 读/写,为输入/输出打开二进制文件 |
“wb” | 只写,为输出打开二进制文件 |
“wb+” | 读/写,为输入/输出建立并打开新的二进制文件 |
“ab” | 追加,输出从二进制文件的末尾开始 |
“ab+” | 读/写,为输入/输出打开二进制文件 |
另外关于这些方式参数的注意点:
- "r"仅用于从文件输入数据,且该文件要已存在
- "w"仅用于向文件输出数据
若打开时文件不存在,则新建一个;若已存在,则其中已存在的数据会被清除 - 若输入时不想清空已存在数据,且要求从末尾追加,用"a"
- “r+” and “w+” and “a+” 均可读/写
区别在于:"r+"仅允许已存在文件;"w+"用于要新建的文件;"a+"则是直接将位置先已到文件末尾; - 打开二进制文件,直接对应方式参数后面加个b
另外还有一个比较重要的概念就是 标准文件
系统将 常规设备上的I/O数据流 称为 标准文件
分别有:
- stdin 标准输入文件
- stdout 标准输出文件
- stderr 标准出错输出文件
- stdprn 标准打印输出文件
8.2.2 fclose()
文件使用结束后,程序应该立即关闭它,防止出现某些不妙的意外情况
为此,有库函数fclose()可以用来关闭文件
调用方式:
fclose( FILE *fp )
返回值:若关闭成功,返回0;发现错误,返回EOF
作用:
- 最主要的就是,使文件指针变量终止之前fopen()调用时与指定文件所构建的联系
- 其次,对于输出到文件的打开方式,还可以将暂留在缓冲区的数据输出到文件之中,防止不正常关闭程序造成意外的数据丢失
8.2.3 fgetc()
调用方式:
fgetc( FILE *fp )
返回值:输入字符的ASCII码;如遇文件结束,返回文件结束标志EOF(被定义为-1)
作用机制解析:
控制块中,有一个信息代表当前位置,而上述的输入字符就是次位置的字符
而在输入一个字符过后,若文件还未结束,当前位置会后移
值得一提的是,为了能正确存储文件结束标志,应该用int类型而不是char类型来存储返回值
此外,getchar()
本质上就是用fgetc()
定义的宏
#define getchar() fgetc(stdin)
8.2.4 fputc()
调用方式:
fputc( char ch , FILE *fp )
返回值:输出成功,返回输出字符的ASCII码;输出失败,返回EOF
类似地,putchar()
也是用fputc()
定义的宏
#define putchar(c) fputc(c,stdout)
8.2.5 fprintf() and fscanf()
fprintf()
and fscanf()
与 printf()
and scnaf()
类似
只是它们针对的是一般的数据文件,因此也要多一个文件指针实参
一般调用形式:
fprintf( FILE * , 输出格式控制字符串 , 输出项表 )
and
fscanf( FILE * , 输入格式控制字符串 , 输入项地址表 )
例子直观解释:
fprintf( wp , "i = %d , r = %6.4f\n" , i , r ) ;
//数据流向:i and r -> 格式化输出 -> wp所指文件
fscanf( rp , "%d %f" , &i , &r ) ;
//数据流向:rp所指文件数据 -> 格式化输入 -> i and r
8.2.6 fgets() and fputs()
fgets()
and fputs()
用于 从正文文件输入字符串 and 向正文文件输出字符串
调用方式:
fgets( char *str , int n , FILE *fp )
and
fputs( char *str , FILE *fp )
作用解析:
fgets()
从数据文件输入字符序,并存储在所指出的字符数组中
在连续输入n-1个字符 or 遇到换行符 or 遇到文件结束 之时结束输入
注意:fgets()
会在输入的字符序列之后自动加字符串结束标志服’\0’,并返回字符串的首字符指针
此外,和gets()
相比,换行符也会被输入而被存储
fputs()
将参数所指的字符串输出到文件
注意:字符串结束标志符不输出,也不会在字符序列之后另外再加换行符
若发生输出错误,返回EOF;否则,返回一个非负值
8.2.7 rewind() and fseek() and ftell()
有时我们还需要对数据文件作随机存取
文件随机存取指输入or输出完一个字符(or字节)之后,可以改变当前位置到其他的位置的字符(or字节)上再进行后续操作
但是文件是存储在外存上的数据流,控制块智能操作当前位置的数据,因而i我们通过调用库函数rewind()
and fseek()
来实现改变当前位置
同时也可以使用ftell()
来查询文件当前位置
调用方式:
-
rewind()
rewind( FILE *fp )
作用是使文件当前位置回到文件之首 -
fseek()
fseek( FILE *fp , long offset , int ptrname )
解析:ptrname
表示定位基准,仅可为 0 or 1 or 2
0代表以文件首位基准,1代表以当前位置位基准,2代表以文件尾为基准
他们也被定义有名称,分别为SEEK_SET
andSEEK_CUR
andSEEK_END
- long型形参
offset
是位移量,以ptrname
位基准,所偏离的字节数(可以是负数) - 由于是对字节数的操作,因而
fseek()
一般用于二进制文件的随机输入or输出
-
ftell()
ftell( FILE *fp )
用于得到文件当前位置相对于文件首的偏移字节数
在随机方式存取文件时,调用ftell()
就可以轻易地确定文件的当前位置此外,还可以用来知道一个文件的长,如下操作即可
fseek( fp , 0L , SEEK_END ); len = ftell( fp ) ;
8.2.8 fread() and fwrite()
有时需要将文件化为结构化数据元素的序列,以结构为单位输入/输出文件中数据,通过成批输入/输出,减少调用库函数的次数,来提高效率
为此,系统提供了成批输入 and 成批输出的库函数
fread()
and fwrite()
就是最常用的 成批输入 and 成批输出的库函数
调用方式:
fread( void *ptr , int size , int count , FILE *rfp )
and
fwrite( void *ptr , int size , int count , FILE *wfp )
解析:
ptr
是数组首元素指针
直观来说:
fread()
中:rfp所指文件 -> 结构化输入(size and count) -> 被输入的数据存储开始地址(ptr)
fwrite()
中:被输出的数据的开始地址(ptr) -> 结构化输出(size and count) -> wfp所指文件size
是输入/输出的单位数据块的字节数count
是要进行输入/输出的数据块的个数
如果是输入/输出二进制文件,调用上述两函数可以输入and输出任何数据类型的数据,甚至可以是我们自定义的结构体
他们也有返回值,返回实际完成输入or输出的数据块的个数,一般来说就是调用时指定的个数
8.3 文件处理程序结构
8.3.1 文件处理程序基础
以下是完整文件处理程序,必须包含的与文件相关的几段代码
-
程序开始处定义文件指针变量 and 存储文件名的字符数组
e.g.#include <stdio.h> FILE *fp ; char fname[ 40 ] ; /* 包含文件目录路径 and 文件名 */
-
文件名的输入
e.g.printf("please enter the filename (path and filename extension included)\n") ; scanf("%s%*c" , fname ); /* *是赋值抑制符,输入但不赋值 */
-
使用文件前,按具体使用要求打开文件
e.g.if((fp = fopen( fname , "r")) == NULL ) { printf("%s 文件无法打开,程序执行终止\n" , fname ) ; return 0 ; }
注意:此处一定要小心选用正确的打开方式
-
文件使用结束之后,一定要及时关闭
e.g.fclose( fp ) ; /* 此后fp还可以被用来打开文件 */
-
此外就是正确使用库函数对数据文件进行输入/输出操作了,此处不多赘述
8.3.2 不同类型文件处理程序的结构
- 正文文件输入程序结构
- 二进制文件输入程序结构
- 文件生成程序结构
(后文待续)