UNIX环境高级编程学习之路(三)----标准I/O库

    对于UNIX环境编程,工作中经常会用到相关知识,作为学习UNIX环境编程的经典书籍--UNIX环境高级编程,是每个UNIX编程人员必看的经典书籍之一,为了将相关知识重新进行学习,以系统的整合所学知识,遂以博文形式作为总结。

一、概述
    标准I/O库处理很多细节,如缓冲区分配,以优化的块长度执行I/O等。

二、流和FILE对象
    对于标准I/O库,他们的操作是围绕流(stream)进行的。当用标准I/O库打开或者创建一个文件时,我们已经使一个流与一个文件相关联。
对于ASCII字符集,一个字符用一个字节表示。对于国际字符集,一个字符可用多个字节表示。标准I/O文件流可用于单字节或多字节(“宽字节”)字符集。流的定向决定了所读、写的字符是单字节还是多字节的。当一个流最初被创建时,他并没有定向。如若在未定向的流上使用一个多字节I/O函数,则将该流定向设为宽定向的。若在未定向的流上使用一个单字节I/O函数,则将该流的定向设为字节定向的。只有两个函数可以改变流的定向,freopen函数清除一个流的定向;fwide函数可用于设置流的定向。

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
返回值:若流是宽定向的,返回 正值;若流是字节定向的,返回负值;若流是未定向的,返回0;
</pre></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13px; line-height:19.5px"><span style="font-size:0.933rem">根据mode参数的不同值,fwide函数执行不同的工作。</span></span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13px; line-height:19.5px"><span style="font-size:0.933rem">* 若mode参数值为负,fwide将试图使指定的流是字节定向的。</span></span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13px; line-height:19.5px"><span style="font-size:0.933rem">* 若mode参数值为正,</span></span><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">fwide将试图使指定的流是宽定向的。</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">* 若mode参数为0,fwide将不试图设置流的定向,但返回标识该流定向的值。</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">    当打开一个流时,标准I/O函数fopen返回一个指向FILE对象的指针,该对象通常是一个结构,它包含了标准I/O库为管理该流所需要的所有信息,包括用于实际I/O的文件描述符、指向于该流缓冲区的指针、缓冲区的长度,当前在缓冲区中的字节数以及出错标志等。</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">应用程序没有必要检验FILE对象,为了引用一个流,需要FILE指针作为参数传递给每个标准I/O函数,FILE * 为文件指针。</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px"></span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">三、标准输入、标准输出和标准错误</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">    这三个流可以自动的被进程使用,他们是:标准输入、标准输出和标准错误。这些流引用的文件与文件描述符STDIO_FILENO、STDOUT_FILENO、STDERR_FILENO所引用的相同。</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">这3个标准I/O流通过预定义文件指针stdio、stdout和stderr加以引用。</span></div><div style="margin:0px; font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px"></span></div><div style="margin:0px; widows:1"><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1"><span style="font-family:Verdana,Arial,Helvetica,sans-serif; font-size:13.995px; line-height:19.5px">四、缓冲</span></div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">    标准I/O库提供缓冲的目的是尽可能的减少使用read和write调用次数。他也对每个I/O流自动的进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">标准I/O提供了一下三种类型的缓冲。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">(1)全缓冲。这种情况是在填满标准I/O缓冲区后才进行实际的I/O操作。驻留在磁盘上的文件通常是由标准I/O库实施全缓冲的。在一个流上执行第一次I/O操作时,相关标准I/O函数通常调用malloc获得需要使用的缓冲区。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">   术语冲洗(flush)说明标砖I/O缓冲区的写操作。缓冲区可由标准I/O例程自动的冲洗(例如,当填满一个缓冲区时,)或者可以调用函数fflush冲洗一个流。在标准I/O方面,flush(冲洗)意味着将缓冲区的内容写到磁盘上,在终端驱动上面,flush(刷清)表示丢弃已经存储在缓冲区的数据。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">(2)行缓冲。在输入和输出中遇到换行符时,标准I/O库执行I/O操作。这允许我们一次输出一个字符,但只有在写了一行之后才进行实际I/O操作、当流涉及一个终端时,通常使用行缓冲。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">行缓冲限制:第一、标准I/O库用来收集每一行的缓冲区的长度是固定的,所以,只要填满了缓冲区即使还没有一个换行符,也进行I/O操作。第二,任何时候只要通过标准I/O库要求从(a)一个不带缓冲的流,或者(b)一个行缓冲的流得到输入数据,那么就会冲洗所有行缓冲输出流。一个不带缓冲的流输入需要从内核获得数据。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">(3)不带缓冲。标准I/O库不对字符进行缓冲存储。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">    标准错误流stderr通常是不带缓冲的,这样就可以让出错信息尽可能快的显示出来。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">*当且仅当标准输入和标准输出并不指定交互式设备时,他们才是全缓冲的。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">*标准错误绝不会是全缓冲的。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">很多系统默认使用下列类型的缓冲:</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">* 标准错误是不带缓冲的。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">* 若是指向终端设备的流。则是行缓冲的;否则是全缓冲的。</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1">更改缓冲类型的函数</div><div style="font-family:Helvetica,'Hiragino Sans GB',微软雅黑,'Microsoft YaHei UI',SimSun,SimHei,arial,sans-serif; font-size:15px; line-height:24px; margin:0px; widows:1"><pre name="code" class="cpp">#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
返回值:成功返回0,出错返回非0。

    可以使用setbuf函数打开或者关闭缓冲机制。为了带缓冲进行I/O,参数buf必须指向一个长度为BUFSIZ的缓冲区。通常在此之后该流就是全缓冲的,但是如果该流与一个终端设备有关,那么某些系统也可以将其设置为行缓冲的。为了关闭缓冲,将buf设置为NULL。
    使用setvbuf,可以精确的说明所需要的缓冲类型。这是用mode参数实现的:
_IOFBF    全缓冲
_IOLBF    行缓冲
_IONBF    不带缓冲
    如果指定一个不带缓冲的流,则忽略buf和size参数。如果指定全缓冲或者行缓冲,则buf和size可选择的指定一个缓冲区自己长度。如果流是带缓冲的,二buf是NULL,则标准I/O库将自动的为流分配适当长度的缓冲区。适当长度指的是由常量BUFSIZ所指定的值。
下表列出了这两个函数的动作,以及各自的选项

    如果在一个函数内分配一个自动变量类的标准I/O缓冲区,则从该函数返回之前,必须关闭该流。某些实现将缓冲区的一部分存放他自己的管理操作信息,所以可以存放在缓冲区的实际数据字节数少于size。一般而言,应由系统选择缓冲区的长度,并且自动分配缓冲区。在这种情况下关闭此流时,标准I/O库将自动释放缓冲区。
    任何时候,我们都可以强制冲洗一个流。
#include <stdio.h>
int fflush(FILE *fp);
返回值:若成功,返回0,若出错,返回EOF
此函数使该流所有未写的数据都被传送至内核。如若fp是NULL,则次函数将导致所有输出流被冲洗。

五、打开流
#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict fp);
FILE *fdopen(int fd, const char *type);
三个函数的返回值:若成功,返回文件指针,出错,返回NULL。

这三个函数的区别:
(1)fopen函数打开路径名为pathname的一个指定文件。
(2)freopen函数在一个指定的流上打开一个指定的文件,如若该流已经打开,则先关闭该流。若该流已经定向,则使用freopen清除该定向。此函数一般用于将一个指定文件打开为一个预定义的流:标准输入、标准输出和标准错误吧。
(3)fdopen函数取一个已有的文件描述符(可以从open。dup、dup2、fcntl、pipe、socket、socketpair或者accept函数得到此文件描述符),并使一个标准的I/O流与该文件描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。因为这些特殊类型的文件不能用标准I/O函数fopen打开,所以我们必须先调用设备专用函数获得一个文件描述符,然后用fdopen使一个标准I/O流与该描述符相结合。


因为UNIX内核不区分文本文件和二进制文件,所以在UNIX下制定字符b作为type的一部分并无作用。
    对于fdopen,type的单数有区别:
(1)因为该描述符已经被打开,所以fdopen为写二打开并不截断该文件。
(2)标准I/O追加写方式也不能用于创建该文件(因为如果一个描述符引用一个文件,则该文件一定已经存在)。
    当用追加方式打开一个文件后,每次写都将数据写到文件的当前尾端处。如果有多个进程用标 准I/O 追加写方式打开同一个文件,那么来自每个进程的数据都将正确的写到文件中。
当以读和写类型打开一个文件时,具有以下限制。
(1)如果中间没有fflush、fseek、fsetpos或rewind, 则在输出的后面不能直接跟随输入。
如果中间没有fseek、fsetpos或rewind,或者一个输入操作没有到达文件尾端,则在输入操作之后不能直接跟随输出。
下图为打开一个标准I/O流的不同方式

    再指定w或a类型创建一个新文件时,我们无法说明该文件的访问权限位(open函数和create函数能做到)。POSIX.1要求实现如下的权限位集来创建文件:
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_OROTH | S_IWOTH
我们可以通过umask值来显示这些权限。
调用fclose关闭一个打开的流。
#include <stdio.h>
int fclose(FILE *fp);
返回值:若成功,返回0;若出错,返回EOF

在该文件被关闭之前,冲洗缓冲中的输出数据。缓冲区中的任何数据被丢弃。如果标准I/O库已经为该流自动分配了一个缓冲区,则释放此缓冲区。
当一个进程正常终止时(直接调用exit函数,或从main函数返回),则所有带未写缓冲数据的标准I/O流都被冲洗,所有打开的标准I/O流都被关闭。

六、 读和写流
    一旦打开了流,则可在3种不同类型的非格式化I/O中进行选择,对其进行读、写操作。
(1)每次一个字符的I/O。一次读或写一个字符,如果流是带缓冲的,则标准I/O函数处理所有缓冲。
(2)每次一行的I/O。如果一次读或写一行,则是用吧fgets或者fputs。每行都以一个换行符终止。当调用fgets时,应说明能处理的最大行长。
(3)直接I/O。fread和fwrite函数支持这种类型的I/O。每次I/O操作读或写某种数量的对象,二每个对象具有指定的长度。这两个函数常用于从二进制文件中读或写一个结构。
*输入函数
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
返回值:若成功,返回下一个字符;若已经到达尾端或者出错,返回EOF

函数getchar等同于getc(stdin)。getc可被实现为宏,而fgetc不能实现为宏。这意味着以下几点。
(1)getc的参数不应当是具有副作用的表达式,因为他可能被计算多次;
(2)因为fgetc一定是一个函数,所以可以得到其地址,所以允许将fgetc的地址作为一个参数传递给另一个函数。
(3)调用fgetc所需要的时间比调用getc长,调用函数所需要的时间长于宏。
这三个函数在返回下一个字符时,将其unsigned char类型转换为int类型。说明为无符号的理由是,如果最高位为1也不会使返回值为负。要有整型返回值的理由是,这样就可以返回所有可能的字符再加上一个已出错或者已经到达文件尾端的指示值。在<stdio.h>中的常亮EOF被要求是一个负值,其值经常是-1。这就意味着不能讲这三个函数的返回值存放在一个字符变量中,以后还要将这些函数的返回值和EOF比较。
注意,不管是出错还是到达文件尾端,这三个函数都返回同样的值,为了区分不同的情况,必须吊用ferror或feof。

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
返回值:若条件为真,返回非0(真);否则,返回0(假)
void clearerr(FILE *fp);

在大多数实现中,为每个流在FILE对象中维护了两个标志:
* 出错标志;
*文件结束标志
调用clearerr可以清除这两个标志。
从流中读取数据以后,可以调用ungetc将字符在压回送到流中。
#include <stdio.h>
int ungetc(int c, FILE *fp);
返回值:若成功,返回c;若出错,返回EOF
压送回到流中的字符以后又可以从流中读出,但是读出字符的顺序与压送回的顺序相反。虽然ISO C允许任何次数的回送,但是他要求实现提供一次只回送一个字符。不能期望一次回送多个字符。
* 输出函数
#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
返回值:成功,则返回c;出错返回EOF
putchar(c)等同于putc(c,stdout),putc可被实现为宏,fputc不能实现为宏。

七、每次一行I/O
    下面函数提供每次输入一行的功能。
#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
返回值:若成功,返回buf;若已经到达尾端或出错,返回NULL
这两个函数都指定了缓冲区的地址,读入的行将送入其中。gets从标准输入读,而fgets则从指定的流读。
对于fgets,必须指定缓冲的长度n。此函数一直读到下一个换行符为止,但是不超过n-1个字符,读入的字符被送入缓冲区,该缓冲区以null字节结尾。如若该行包括最后一个换行符的字符数超过n-1,则fgets只返回一个不完整的行,但是,缓冲区总是以null字节结尾。对fgets的下一次调用会继续该行。
gets是一个不推荐使用的函数,因为gets不能指定缓冲区的长度。这样有可能造成缓冲区溢出,从而产生不可预料的后果。gets与fgets的另一个区别是,gets并不将换行符写入到缓冲区中。
如下是fgets的例子:
#include <stdio.h>
#include <stdlib.h>
int main()
{
	FILE * pFile;
	char mystring [100];
	pFile = fopen ("myfile.txt" , "r");
	if (pFile == NULL)
		perror ("Error opening file");
	else {
		if ( fgets (mystring , 100 , pFile) != NULL )
			puts (mystring);
		fclose (pFile);
	}
	system("pause");
	return 0;
}

输出:


下面函数提供每次输出一行的功能。
#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
参数:str,这是一个数组,包含null结尾的要写入的字符序列;fp,一个文件对象标识字符串被写入流的指针。
返回值:若成功,返回非负值,若出错,返回EOF
函数fputs将一个以null字符终止的字符串写到指定的流,尾端的终止符null不写出。注意,这并不一定是每次输出一行,因为字符串不需要换行符作为最后一个非null字节。通常,在null字节之前是一个换行符,但并不要求如此。
puts将一个以null字符终止的字符串写到标准输出,终止符不写出。但是,随后puts又将一个换行符写到标准输出。(puts("")可换行)。
puts并不像它所对应的gets那样不安全。但是我们还是应该避免使用它,以免需要记住它在最后是否添加了一个换行符。如果总是使用fgets和fputs,那么在每行终止处我们必须自己处理换行符。
以下是fgets的例子:
#include <stdio.h>
int main ()
{
   FILE *fp;
   fp = fopen("file.txt", "w+");
   fputs("This is c programming.", fp);
   fputs("This is a system programming language.", fp);
   fclose(fp);
   
   return(0);
}
结果为:


八、标准I/O的效率
    标准I/O库与直接调用read和write函数相比并不慢很多。对于大多数比较复杂的应用程序,最主要的用户CPU时间是由应用本身的各种消耗的,而不是由标准I/O例程消耗的。

九、二进制I/O
    若一次读或者写一个完整的结构,则用下面的函数来进行
#include <stdio.h>
size_t fread(void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp);
size _t fwrite(const void *restrict ptr, size_t size, size_t nobj, FILE *restrict fp)
两个函数返回值:读或写的对象数
这些函数有以下两种常见用法
(1)读或写一个二进制数组。
float data[10];
if (fwrite(&data[2], sizeof(float), 4, fp) != 4)
    printf("fwrite error!");

其中,指定size为每个数组元素的长度,nobj为欲写的元素个数。
(2)读或写一个结构
struct {
    short count;
   long total;
   char name[32];
}item;
if (fwrite(&item, sizeof(item), 1, fp) != 1)
    printf(fwrite error!);

其中,指定size为结构的长度,nobj为1(要写的对象个数)。
这两个例子结合起来就可读或者写一个结构数组,size应当是该结构的sizeof,nobj该是数组中的元素个数。
    fread和fwrite返回读或写的对象数。对于读,若果出错或者到达文件尾端,则此数字可以少于nobj。此时,应该调用ferror或feof来判断究竟是哪一种情况。对于写,如果返回值少于所要求的nobj,则出错。
使用二进制I/O的基本问题是,他只能用于读在同一系统上已写的数据。有这种情形,在一个系统上写的数据,要在另一个系统上进行处理,可能出问题的原因是:
(1)在一个结构中,同一成员的偏移量可能随编译程序和系统的不同而不同。
(2)用来存储多字节整数和浮点数的二进制格式在不同的系统结构间也可能不同。

十、定位流
    有3种方法定位标准I/O流。
(1)ftell和fseek函数,他们假定文件的位置可以存放在一个长整形中。
(2)ftello和fseeko函数,使文件偏移量可以不必一定使用长整型。使用off_t数据类型代替了长整型。
(3)fgetpos和fsetpos函数,使用一个抽象数据类型fpos_t记录文件的位置。这种数据类型可以根据需要定义为一个足够大的数,用以记录文件的位置。
    需要移植到非UNIX系统运行的应用程序应当使用fgetpos和fsetpos。
#include <stdio.h>
long ftell(FILE *fp);
返回值:若成功,返回当前文件位置指示,若出错,返回-1L;
int fseek(FILE *fp, long offset, int whence);
返回值:若成功,返回0,出错,返回-1;
void rewind(FILE *fp);

    对于一个二进制文件,其文件位置指示器是从文件开始位置,并以字节为度量单位的。ftell用于二进制文件时,其返回值就是这种字节位置。(从开始位置,相当于SEEK_SET)。为了用fseek定位一个二进制文件,必须指定一个字节offset,以及解释这种偏移量的方式。whence的值和lseek函数的相同:SEEK_SET表示从文件的起始位置,SEEK_CUR表示从当前文件位置开始,SEEK_END表示从文件尾端开始。
    除了偏移量的类型是off_t而非long以外,ftello函数与ftell相同,fseeko函数和fseek相同。
#include <stdio.h>
off_t ftello(FILE *fp);
返回值:若成功,返回当前文件位置,若出错,返回(off_t-1)
int fseeko(FILE *fp, off_t offset, int whence);
返回值:若成功,返回0,若出错,返回-1;
#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t restrict *pos);
int fsetpos(FILE *fp, const fpos_t *pos);
返回值:若成功,返回0;若出错,返回非0
fgetpos将文件位置指示器的当前值存入由pos指向的对象中。在以后调用fsetpos时,可以使用此值重新定位至该位置。

十一、格式化I/O
*格式化输出
    格式化输出是由5个printf函数来处理
#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format,...);
int dprintf(int fd, const char *restrict format,...);
3个函数返回值:若成功,返回输出的字节数,若输出出错,返回负值。
int sprintf(char *restrict buf, const char *restrict format,...);
返回值:若成功,返回存入数组的字节数,若编码出错,返回负值
int snprintf(char *restrict  buf, size_t n, const char *restrict format, ...);
返回值:若缓冲区足够大,返回将要存入数组的字节数,若编码出错,返回负值

    printf将格式化数据写到标准输出,fprintf写至指定的流,dprintf写至指定的文件描述符,sprintf将格式化的字符送入数组buf中。sprintf在该数组尾端自动加一个null字节,但是该字节不包括在返回值中。
注意,sprintf函数可能会造成由buf指向的缓冲区溢出,所以,引入了snprintf函数。
   下列5种printf族的变体类似于上面的5种,但是可变参数表(...)替换成了arg。
#include< stdarg.h>
#include  <stdio.h>
int vprintf(const char *restrict format, va_list arg);
int vfprintf(FILE *restrict fp, const char *restrict format, va_list arg);
int vdprintf(int fd, const char *restrict format, va_list arg);
所有3个函数返回值:若成功,返回输出字节数;若输出出错,返回负值
int vsprintf(char *restrict buf, const char *restrict format, va_list arg);
函数返回值:若成功,返回存入数组的字节数;若编码出错,返回负值
int vsnprintf(char *restrict buf, size_t n, const char *restrict format, va_list arg);
返回值:若缓冲区足够大,返回存入数组的字节数,若编码出错,返回负值。

*格式化输入
#include <stdio.h>
int scanf(const char *restrict format,...);
int fscanf(FILE *restrict buf, const char *restrict format,...);
int sscanf(const char *restrict buf, const char *srestrict format,...);
返回值:赋值的输入项数,若输入出错或在任一转换已经到达文件尾端,返回EOF
scanf族用于分析输入字符串,并将字符序列转换成指定类型的变量。在格式之后的各参数包含了变量的地址,用转换结果对这些变量赋值。

scanf族也使用由<stdarg.h>说明的可变长度参数表
#include <stdarg.h>
#include <stdio.h>
int  vscanf(const char *restrict format, va_list arg);
int vfscanf(FILE *restrict fp, const char *restrict format, va_list arg);
int vsscanf(const char *restrict buf, const char *restrict format, va_list arg);
返回值:指定的输入项目数,若输入出错或在任一转换前文件结束,返回EOF

十二、临时文件
    标准I/O库提供了两个函数以帮助创建临时文件
#include <stdio.h>
char *tmpnam(char *ptr);
返回值:指向唯一路径名的指针
FILE *tmpfile(void);
返回值:若成功,返回文件指针,若出错,返回NULL
    tmpnam函数产生一个与现有文件名不同的一个有效路径名字符串。每次调用它时,都产生一个不同的路径名,最多调用次数是TMP_MAX。TMP_MAX定义在<stdio.h>中。
若ptr是NULL,则所产生的路径名存放在一个静态区中,指向该静态区的指针作为函数值返回。后续调用tmpname时,会重写该静态区。如若ptr不是NULL,则认为它应该是指向长度至少是L_tmpnam个字符的数组,所产生的的路径名存放在该数组中,ptr也作为函数值返回。
tmpfile创建一个临时二进制文件,在关闭该文件或程序结束时将自动删除这个文件。注意,UNIX对二进制文件不进行特殊区分。
下例说明两个函数的应用:
#incldue <stdio.h>
int main(void)
{
	char name[512],line[1024];
	FILE *fp;
	
	printf("%s\n",tmpnam(NULL));                    /*first temp name*/
	
	tmpnam(name);						            /*second temp name*/
	printf("%s\n",name);
	
	if ((fp = tmpfile()) == NULL)                    /*create temp file*/
			printf("tmpfile error");
	fputs("one line of output\n",fp);                /* write to temp file */
	rewind(fp);										 /* then read it back */
	if (fgets(line, sizeof(line),fp) == NULL)
		printf("fgets error");
	fputs(line,stdout);                              /* print the line we wrote */
	
	exit(0);	
}
执行后的结果为:

    tmpfile函数经常使用的标准UNIX技术是先调用tmpnam产生一个唯一路径名,然后,用该路径名创建一个文件,并立即unlink它。对一个文件解除链接并不删除其内容,关闭该文件时才删除其内容。而关闭文件可以是显示的,也可以是在程序终止时自动进行。
    Single UNIX Specification为处理临时文件定义了另外两个函数:mkdtemp和mkstemp,他们是XSI的扩展部分。
#include <stdlib.h>
char *mkdtemp(char *template);
返回值:若成功,返回指向路径名的指针,若出错,返回NULL
int mkstemp(char *template);
返回值:若成功,返回文件描述符;若出错,返回-1
    mkdtemp函数创建了一个目录,该目录有一个唯一的名字;mkstemp函数创建了一个文件,该文件有一个唯一的名字。名字是通过template字符串进行选择的。这和字符串是后6位设置为XXXXXX的路径名。函数将这些占位符替换成不同的字符来创建一个唯一的路径名。如果成功的话,这两个函数将修改template字符串反映临时文件的名字。
    由mkdtemp函数创建的目录使用下列权限位集:S_IRUSR | S_IWUSR | S_IXUSR。调用进程的文件模式创建屏蔽字可以进一步限制这些权限。如果创建成功,mkdtemp返回新目录的名字。
    mkstemp函数以唯一的名字创建一个普通文件并且打开该文件,该函数返回的文件描述符以读写方式打开。由mkstemp创建的文件使用访问权限位S_IRUSR | S_IWUSR。
    与tempfile不同,mkstemp创建的临时文件并不会自动删除。如果希望从文件系统命名空间中删除该文件,必须自己对他解除链接。
    使用tmpnam和tempnam至少有一个缺点:在返回唯一的路径名和用该名字创建文件之间存在一个时间窗口,在这个时间窗口中,另一个进程可以用相同的名字创建文件。因此应该使用tmpfile和mkstemp函数,因为他们不存在这个问题。
使用mkstemp函数的例子:
#include <stdio.h>
#include <errno.h>
void make_temp(char *template)
{
	int fd;
	struct stat sbuf;
	if ((fd = mkstemp(template)) < 0)
		printf("can't create temp file");
	printf("temp name = %s\n",template);
	close(fd);
	if (stat(template, &sbuf) < 0) 
	{
		if (errno == ENOENT)
			printf("file doesn't exist\n");
		else 
			printf("stat failed!\n");
	}
	else 
	{
		printf("file exist!\n");
		unlink(template);
	}
}
int main(void)
{
	char good_template[] = "/tmp/dirXXXXXX";
	char *bad_template = "/tmp/dirXXXXXX";
	
	printf("try to create first tmp file...\n");
	make_temp(good_template);
	printf("try to create second tmp file...\n");
	make_temp(bad_template);
	exit(0);
}
执行结果为:


两个模板字符串的声明方式不同带来了不同的运算结果。对于第一个模板,因为使用了数组,名字是在栈上分配的。但是第二种情况使用了指针,此时,只有指针自身驻留在栈上。编译器把字符串存放在可执行文件的只读段,当mkstemp函数修改字符串时,出现了段错误。

十三、内存流
    标准I/O库把数据缓存在内存中,因此每次一字符和每次一行的I/O更有效。我们也可以通过调用setbuf或setvbuf函数让I/O库使用我们自己的缓冲区。在SUSV4中支持内存流。这就是标准I/O流,虽然仍使用FILE指针进行访问,但其实并没有底层文件。所有的I/O都是通过在缓冲区与主存之间来回传送字节来完成的。
有三个函数用于内存流的创建
#include <stdio.h>
FILE *fmemopen(void *restrict buf, size_t size, const char *restrict type);
返回值:成功,返回流指针,出错,返回NULL
    fmemopen函数允许调用者提供缓冲区用于内存流:buf参数指向缓冲区的开始位置,size参数指定了缓冲区大小的字节数,如果buf参数为空,fmemopen函数分配size字节数的缓冲区。在这种情况下,当流关闭时缓冲区会被释放。
type参数控制如何使用流


    注意,这些取值对应于基于文件的标准I/O流的type参数取值,但其中有些微小差别。第一,无论何时以追加方式打开内存流时,当前文件位置设为缓冲区的第一个null字节。如果缓冲区中不存在null字节,则当前位置就设为缓冲区结尾的后一个l字节 ,当流并不是以追加写方式打开时,当前位置设为缓冲区的开始位置。因为追加写模式通过第一个null字节确定数据的尾端,内存流并不适合存储二进制数据。
第二、如果buf参数是一个null指针,打开流进行读或写都没有任何意义。因为在这种情况下缓冲区是通过fmemopen进行分配的额,没有办法找到缓冲区的地址,只写方式打开流意味着无法读取已存入的数据,同样,以读方式打开流意味着只能读取那些我们无法写入的缓冲区中的数据。
第三、任何时候需要增加流缓冲区中数据量以及调用fclose、fflush、fseek、fseekko以及fsetpos时都会在当前位置写入一个null字节。
用于创建内存流的其他两个函数分别是open_memstream和open_wmemstream。
#include <stdio.h>
FILE *open_memstream(char **bufp, size_t *sizep);
#include <wchar.h>
FILE *open_wmemstream(wchar_t **bufp, size_t *sizep);
返回值:若成功,返回流指针,若出错,返回NULL
    open_memstream函数创建的流是面向字节的, open_wmemstream函数创建的流是面向宽字节的。这两个函数与fmemopen函数的不同在于:
* 创建的流只能写打开;
* 不能指定自己的缓冲区,但可以分别通过bufp和sizep参数访问缓冲区地址和大小;
* 关闭流后需要自行释放缓冲区;
* 对流添加字节会增加缓冲区的大小。
但是缓冲区地址和大小的使用必须遵循一些原则。第一、缓冲区地址和大小只有在调用fclose或fflush后才有效;第二、这些值只有在下一次流写入或调用fclose前才有效。因为缓冲区可以增长,可能需要重新分配。如果出现这种情况,缓冲区的内存地址值在下一次调用fclose或fflush时会改变。
    因为避免了缓冲区溢出,内存流非常适用于创建字符串。因为内存流只访问主存,不访问磁盘上的文件,所以,对于把标准I/O流作为参数用于临时文件的函数来说,会有很大的性能提升。


-------------------------------------------------------------------------华丽的风格线---------------------------------------------------------------------------------

QQ群:西安C/C++开发者,诚邀您的加入


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值