在Unix的应用中,读写文件是最常见的任务。诸如报表的生成,日志的记录,批交易报文的传送都采用文件实现。标准文件编程就是操作文件最简单的huguidong11@yahoo.cn 工具。
1:文件的创建、打开、关闭与删除
在标准库中,结构FILE是指向文件的指针,所有对文件的操作都是通过FILE完成的,FILE指针也称为文件流,它定义在头文件<stdio.h>,相对于整形的低级文件I/O描述符,它提供了I/O缓冲功能。
1)创建、打开、关闭与删除文件的函数族
标准文件编程库中用于文件创建、打开、关闭与删除的函数如下:
函数fopen打开或创建文件;fclose关闭文件;函数freopen重新打开文件;函数remove删除磁盘文件;函数rename更改文件名称。
ex1:以只读方式打开文本文件/etc/passwd:
FILE *fp;
fp=fopen("/etc/passwd","r");
ex2:以二进制方式创建文件rr.txt:
FILE *fp;
fp=fopen("rr.txt","wb");
2)freopen函数
本函数实现文件流的替换。它首先关闭原文件流stream,然后再以freopen的方式打开一个新的文件流,此后对原文件流的任意操作都自动转换为对新文件流的操作。成功时返回指向新文件的FILE型指针,否则返回NULL。
Unix进程默认打开三个文件:标准输出、标准输入、标准错误输出,它们的FILE标识符号分别是stdout、stdin、stderr。函数freopen常用于将以上三个文件流重定向,实现方法如下:
3)fclose函数
为了减少系统资源消耗、避免误改文件内容和更新文件缓冲,应该及时关闭在将来一段时间内不需要使用的文件。函数fclose关闭文件流stream,成功时返回0,否则返回EOF;
4)remove
函数remove删除字符串filename指定的文件或目录,当filename指定文件时,remove相当于unlinke函数,当filename指定目录时,相当于rmdir;
1)字符读写
字符读写函数每次只操作一个字符,为了提高磁盘读写效率,标准文件编程中提供了缓冲处理。
(1)字符输入函数
函数getc以unsigned char类型读取文件输入流stream中的一个字符,并将该无符号字符转化为整数返回,同时移动文件指针到下一个字符处。函数getchar实际上是关于getc的一个宏定义"getc(stdin)".
函数fgetc的功能类似于getc,不同的是,它的执行速度远低于getc,因此getc常常被定义在宏中使用。
当文件结束或错误时,这三个函数都将返回EOF,EOF为常数,因此正确的文件结束判断代码如下:
(2)字符输出函数族
函数putc首先先将int型参数c自动转换为unsigned char类型,然后写入文件流stream中,同时移动文件指针到下一个字符处。函数putchar 实际上是关于putc的宏定义"putc(stdout)"。
(3)实例
报文解析是Unix应用的一个重要内容。双方把约定的几个域通过某种排序和分割方式组合在一起,就成了报文。报文解析就是从报文中分解出各个域的数据,比如从银行的代收代付报文查找出账号域和资金域的内容。
字符串报文是报文的重要分支,它以字符串为载体记录了域的数据。在字符串报文中,域与域之间最常见的2中分隔方式是固定长度分割和特殊字符(串)分割。前者每个域占用固定宽度,解析时只是读取特定位置的数据即可。后者的域与域之间由固定的字符(串)连接,解析时需要计算固定字符(串)出现的次数,以决定域的序号和内容。Unix中passwd文件就是由":"分割的字符串报文组合而成的。
例子:一个使用字符读写函数解析报文的,程序读取文件的"/etc/passwd"中每一个字符,并将"用户名称"域(报文的第一个域)单独提出,存入文件"copyname.txt"中,源程序如下:
2)按行读写
标准函数编程库提供了行读写函数,该类函数读取一行以换行符"/n"结束的数据,写入数据时自动输出换行符。
(1)行输入函数族
函数gets从标准输入流(stdin)中读取一串字符存储到参数s所指向的内存空间中,文件结束或者错误发生时返回NULL,否则将返回参数s所指向的内存地址。
函数fgets中键入了防溢出控制,它从文件流stream中读取一串字符到参数s所指向的内存空间,但读取数据的长度(包括换行符"/n")不能超过n-1,。参数n代表了字符串s的最大存储空间。倘若待读入的实际数据长度包括("/n")超过了n-1,函数将截取该n个字符返回,剩余的字符将在下一次fgets调用时读入。
两个函数都把读取的字符信息存入字符串s中,并且自动增加字符串结束符"0",这也是fgets一次性最多只能读入n-1个字符的原因(第n个字符需要存储结束符"0")。函数调用成功时返回参数s的值,即指向输入的字符信息,否则返回空指针NULL。
(2)行输出函数
标准文件编程库中用于文件行输出的函数如下:
参数s指向一串以字符串结束符"0"结尾的字符;函数puts把该字符串(不包括结束符"0")写入到标准输出流stdout中,并自动输出换行符"/n";函数fputs字符串s(不包括结束符"0")写入文件流stream中,但不再输出换行符"/n"。
两函数都不输出字符串末的结束符,输出失败时,都返EOF。
(3)实例:
解析报文文件的另一种方法是先读入一行数据,再通过字符串函数分解。本处设计了一个使用行读写解析报文的例子,程序按行读取文件"/etc/passwd",并将"用户名称"域提取出来(报文的第一个域)单独提取出来,存入文件"copyname.txt"中,源程序如下:
3)按块读写
块读写函数,能够输入输出任何数量的字符,在操作二进制文件时常常使用,标准文件编程库中用于文件块输入输出的函数如下:
函数fread从文件流stream中读入nitems个数据项存储到指针ptr所指向的内存中,每个数据项具有size字节大小,一次操作总共读入size*nitems个字节。
函数fwrite将ptr的数据写入到stream中,每次可写入size*nitems个字符。参数nitems表示写入文件的数据项个数,参数size表示每个数据项具有的字节大小。
注:在大多数Unix中,size_t被定义为无符号整形:
typedef unsigned int size_t;
fread&fwrite都不返回实际读写的字符个数,而返回的是实际读写的数据项数。成功时,返回值等于参数nitems值,否则返回值将小于nitems值。
块I/O函数常应用于二进制文件读写中,操作比较灵活,既可以读写一个字符,也可以读写一个结构,还可以读写任意数据类型。
实例
块读写函数经常操作二进制文件,保留内存信息或永久存储数据信息等。比如在软件中涉及一个文件型数据库,用以存取历史交易明细。
本处设计了一个存取数据信息的实例,通过文件"array.dat"存储和读取一个整形数组,其中文件起始记录数组的长度,随后是数组的数据信息。源文件如下:
3:函数的变长参数
文件的格式化参数都支持变长参数。定义时,变长参数列表通过省略号"..."表示,因此,具有变长参数列表的函数定义格式如下:
type 函数名(参数1,参数2,参数n,...);
其中type为函数的返回值类型,参数1~n为定长参数,...代表变长参数,...必须定义在参数的最右端。如下例:
int printf(const char * format,...);
int mysum(...);
1)变长参数的使用
Unix的变长参数通过va_list对象实现,定义在文件"stdarg.h"中,变长参数的应用模板代码如下:
1 step. va_list pvar
申明va_list数据类型变量pvar,该变量访问变长参数列表中的参数
2 step. va_start(pvar,parmN)
宏va_start初始化变长参数列表。pvar是va_list型变量,在step1定义,记载列表中的参数信息。parmN是省略号"..."前的一个参数名,va_start根据此参数,判断参数列表的起始位置。
3 step: va_arg(pvar,type)
获取变长参数列表中参数的值。pvar是step定义的va_list型变量,type为参数值的类型,也是红va_arg返回数值的类型,如:
va_arg(pvar,int);
va_arg(pvar,float);
宏va_arg执行完毕后自动更改对象pvar,将其指向下一个参数。
4 step:va_end(pvar)
关闭本次对变长参数列表的访问。
设计函数mysum,计算输入参数的和并返回结果,源程序如下:
compile & run
$make mysum
cc -O -o mysum mysum.c
$./mysum
sum(1,4)=4;
sum(2,4,8)=12;
4)变长参数的传递
变长参数传递的函数族如下:
这些函数完全等价于格式化函数,只是在形式上采用固定参数替代变长参数列表,这样描述的函数更加紧凑,这些函数长应用于变长参数内部的功能实现。
实现:设计函数"int PrintLog(FILE *stream,const char *pformat,...)",它按照字符串format的内容,控制后即参数的数量和格式,并在文件流stream中输出。源程序代码如下:
4:文件读写位置的定位
在实际应用中,我们常常只需要读取文件中的一小段内容,或者写入一小段呢日哦难怪到文件中,使用文件的读写定位功能可以避免些不必要的的数据段。例如某文件由一系列固定大小记录块组成,当访问其第n块记录时,可以先将文件指针移动到第n块记录的起始位置,再访问1个记录快大小的数据,并不需要从文件头开始读取n个记录块。
标准文件变成库中用于定位文件读写为止的函数如下:
其中fseek改变文件流stream中的访问位置,参数whence表示文件定位的方式,fseek中提供了三种定位方式,参数offset表明了定位的偏移量,其值可正可负,与whence一起确定访问文件的最终定位。
whence 含义 文件定位于
SEEK_SET 从文件头开始定位 0+offset
SEEK_CUR 从当前位置开始定位 当前位置+offset
SEEK_END 从文件尾开始定位 文件末+offset
rewind重置流stream,将文件流定位于文件开始处,相当于执行了以下操作:
void fseek(stream,0L,SEEK_SET);
函数ftell获取文件流的当前位置,调用成功时将该值返回,否则返回-1。
注:
大多数系统中,可以通过以下代码获取文件的长度(len)
实例:
本处设计了一个文件读写定位的例子,代码如下,其中seekwrite将整数值narray写入文件第"rec*sizeof(int)"处;函数seekread从文件的第"rec*sizeof(int)"位置中读取整数值存入narray.
如果定位超过了文件的最大长度且执行了写入操作,文件长度将延长到当前位置,中间部分自动补0;
5:文件的状态
每一个流对象内部都保持了两个指示状态:错误指示状态和文件结束状态,函数ferror和feof分别检查这两个状态,函数strerror显示错误的提示信息。
(1)文件的错误与结束状态
当文件I/O发生错误时,调用ferror函数将返回非0值,否则返回0值。当文件结束时,调用feof函数返回非0值,否则返回0值,函数clearerr清除文件错误标志和EOF标志。
实例:
读取文件"/etc/passwd",当文件结束时自动退出。
(2)文件的错误信息
错误状态指示器仅能判断错误是否发生,不能明确错误的内容,标准文件编程库用于明确错误信息的函数如下:
extern int errno;
#include<string.h>
char *strerror(int errnum);
其中外部变量errno代表了发生错误的代码,函数strerror获取第errnum号错误的详细信息。
6:文件的缓冲
所谓文件写缓冲,是指文件流在执行输出操作时,并不立刻将数据写入文件,而是先把数据累计到缓冲区,再以块为单位批量输出到文件中,同理,文件读缓冲是指文件流在执行输入操作时,以块为单位读取文件内容,多余的数据存储在内存中。如果下次读操作的内容刚好在同一块中,则可以直接返回结果,避免一次输入操作。通过缓冲技术,可以减少低级I/O函数read和write函数的调用次数,从而大大提高软件执行效率。
1)缓冲模式
标准文件编程库采用FILE类型描述文件流,与低级I/O函数相比,最大的特性就是应用及增加了缓冲功能(低级I/O函数只使用了文件系统自带的缓冲功能),文件的输入输出以"缓冲块"为单位批量完成,并且根据"缓冲块"大小,提供了三种缓冲模式。
(1)全缓冲(_IOFBF):一般读写普通磁盘文件采用全缓冲模式。
(2)行缓冲(_IOLBF):比如调用fgets函数从标准输入流stdin中输入字符,当且仅当客户输入回车换行时,函数才返回。
(3)无缓冲(_IONBF):比如stderr采用无缓冲模式;
2)缓冲函数
setbuf设置文件流stream的缓冲区,参数buf指向一个大小为BUFSIZ的内存块,调用成功后,文件流stream使用该内存块作为新的缓冲区。倘若buf是空指针NULL,文件流stream的缓冲将被完全关闭。缓冲区内存块的定义一般为:
char buf[BUFSIZ]; ---其中BUFSIZ是stdio.h中的常数,代表缓冲区的大小,常为256的整数倍。
setvbuf设置了文件流stream的缓冲区和缓冲模式,缓冲模式由参数type确定.
任何时候,都可以使用fflush刷新缓冲区,并将缓冲区的内容强制输出到文件中,参数stream指明了更新的 文件流,当其值为NULL时,系统将刷新全部文件流的缓冲区。
实例:
本处设计了一个缓冲显示的实例:
7:项目:通用函数库之调试功能库封装
1)调试库内容:
格式化日志输出
十六进制日志输出
信息判断
2)调试库设计
(1)PrintLog
格式化日志输出函数,其原型为:
int PrintLog(FILE *fp,const char *pformat,...);
(2)PrintTraceLog
int PrintTraceLog(const char *pformat,...);
核心代码
(3) Verify
信息判断函数,其原型为:
int Verify(int bStatus,const char *szBuf,const char *szFile,int nLine);
(5)调试库应用实例:
2:文件的无格式读写
标准程序编程对文件流的输入输出操作,能以无格式方式读写,也可以实现有格式读写,无格式读写,分为字符读写、按行读写、按块读写;