系统编程--标准IO

Posted 悠悠南山下

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了系统编程--标准IO相关的知识,希望对你有一定的参考价值。

1.流和FILE对象
  对于国际字符集,一个字符可以由一个以上的字节来表示。标准I/O文件流可以用来操作单字节和多字节(宽,wide)字符集。一个流的方向(orientation)决定了字符是以单字节还是多字节的方式读取,当一个流被创建时,它没有方向。如一个多字节I/O函数(见<wchar.h>)用在了没有方向的流上,那么流的方向会设为面向宽字符的。如果一个字节I/O函数用在一个没有方向的流上,那么流的方向会设为面向字节的。只有两个函数可以在设置后改变这个方向。freopen函数(简单说明)将会清除一个流的方向,而 fwide函数用来设置一个流的方向。

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);
//流面向宽字符返回正值、面向字节返回负值、没有方向则返回0。

根据mode参数的不同值,fwdie函数执行不同的任务:
1).如果mode参数为负值,fwide将会尝试让流面向字节;
2).如果mode参数为正值,fwide将会尝试让流面向宽字符;
3).如果mode参数为0,fwid将不会设置方向,而是返回一个表示流的方向的值。
注意fwide不会改变一个已经有方向的流的方向。同样注意它不会返回错误。
当打开一个流时,标准I/O函数fopen返回一个指向一个FILE对象的指针。这个对象通常是一个包含所有标准I/O库管理流所需的所有信息的结构体。

2.缓冲
三种缓冲类型:
1).全缓冲:在这种情况下,当标准I/O的缓冲被填满后,真实的I/O才会发生。在磁盘上的文件通常都被标准I/O库完全缓冲。这个被使用的缓冲通常被标准I/O库在第一次操作一个流时通过调用malloc得到.注:Stdin和stdout如果被重定位到文件上,则是完全缓冲
术语“冲洗(flush)”描述了标准I/O缓冲的写操作。一个缓冲可以被标准I/O函数自动冲洗,比如在缓冲满时,或者我们可以调用函数fflush来冲洗一个流。不幸的是,在UNIX环境里,冲洗有两个意思。在标准I/O库里,它表示缓冲的内容写出,它可能是部分填充的。

2).行缓冲:在这种情况下,标准I/O库在输入输出时碰到一个换行符时会执行I/O操作。这让我们可以一次输出单个字符(使用标准I/O的 fputc函数),因为我们知道只当我们完成了每行的写操作时真实I/O才会发生。行缓冲被典型用在终端的流上:比如标准输入、标准输出。
行缓冲伴随两个警告。第一,标准I/O库用来收集各行的缓冲区大小是固定的,所以当我们在写一个换行符之前填满这个缓冲I/O可能会发生。第二、不管何时通过标准I/O库从a、一个未缓冲流或b、(需要从内核被请求数据的)一个行缓冲流请求一个输入时,所有行缓冲输出流都会被冲洗。b情况括号内的补充条件的原因是,请求的数据可能已经在缓冲里了,这时不再需要从内核中读取数据。显然,a情况下任何从未缓冲流的输入,都会要求从内核获取数据。

3).无缓冲:标准I/O库不缓冲字符。例如,如果我们用标准I/O函数fputs写15个字符,我们会期望这15个字符尽快地输出,它很可能使用了3.8节的write函数。
标准错误流通常是无缓冲的。这是为了使任何错误消息都能尽快地显示,而不管它们是否包含一个换行符。
通过调用下面两个函数的其中一个来改变缓冲:

#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
void setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);
//成功返回0,失败返回非0值。

3.打开流
1).下面三个函数打开一个标准I/O流

#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 filedes, const char *type);
//成功返回文件指针,错误返回NULL。

区别:

a.fopen函数打开一个指定的文件。
b.freopen函数在一个指定的流上打开一个指定的文件,并关闭之前打开的流。如果之前的流有方向,freopen会清除它.
c.fdopen函数接受一个已有的文件描述符,它可以通过open、dup、dup2、fcntl、pipe、socket、socketpair或 accept函数得到

2).一个打开的流可以调用fclose来关闭

#include <stdio.h>
int fclose(FILE *fp);
//成功返回0,错误返回EOF

任何缓冲的输出数据都在文件关闭前被冲洗。任何可能被缓冲的输入数据都会被舍弃。如果标准I/O库之前自动开辟了一个缓冲,这个缓冲会被释放

4.读和写流
打开一个流,可以从以下三种非格式化的I/O中选择:
● 一次一字符(Character-at-a-time)I/O。我们可以一次读写一个字符,而让标准I/O函数处理所有的缓冲事宜,如果流有缓冲的话。
● 一次一行(Line-at-a-time)I/O。如果我们读一次读写一行,我们使用fgets和fputs。每行都以一个换行符终止,而且当我们调用fgets时必须指定我们能处理的最大行长度。
● 直接(Direct)I/O。这种类型的I/O通过fread和fwrite函数支持。对于每个I/O操作,我们读写一些对象,而每个对象都有一个指定的尺寸。这两个函数经常用来处理二进制文件,那里我们每次操作读写一个结构。
1).输入函数:
下面三个函数允许我们一次读一个字符

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
//三者成功都返回下一个字符,失败都返回EOF。

函数getchar被定义为与getc(stdin)等价。前两个函数的区别是getc可以被作为一个宏而实现,而fgetc不能作为宏实现

2).注意上面三个函数当碰到一个错误或到达文件尾时返回同一个值。为了区别这两者,我们必须调用ferror或feof

#include <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
//两者如果条件成立的话返回非0(真),否则返回0(假)。
void clearerr(FILE *fp);

在多数实现上,在FILE对象里为每个流维护了两个标志:

a.一个错误标志
b.一个文件结束标志
两个标志都可以调用clearerr来清除。

3).在从一个流读入后,我们可以调用ungetc来把字符放回去。

#include <stdio.h>
int ungetc(int c, FILE *fp);
//成功返回c,否则返回EOF。

放回的字符会被后续在流上的读操作以放回的相反顺序返回

4).输出函数
对应于每个之前讨论过的输入函数的输出输出有:

#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可以作为一个宏实现(将被替换为IO_putc),但fputc不能作为一个宏实现

5.每次一行I/O
1).以下两个函数提供一次一行输入功能

#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE* restrict fp);
char *gets(char *buf);
//两者成功返回buf,文件结束或错误返回NULL。

两都都指明一个把行读入的缓冲区的地址。gets函数从标准输入中读,而fgets从指定的流中读取。

2).一次一行输出由fputs和puts提供

#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
//成功返回非负值,否则返回EOF。

函数fputs把一个null终止的字符串写入到指定的流中,末尾的null字节没有被写,puts函数在标准输出写一个空字符终止的字符串,但不会把这个空字节写出。不同的是puts接着会把一个换行符写入到标准输出

6.二进制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)读写一个二进制数组。
2)读写一个结构体。

7.定位流
有三种方式来定位一个标准I/O流
1)

#include <stdio.h>
long ftell(FILE *fp);
//如果成功返回当前文件位置指示器,错误返回-1L。
int fseek(FILE *fp, long offset, int whence);
//成功返回0,否则返回非0值。
void rewind(FILE *fp);

对于一个二进制文件,一个文件的位置指示器是从文件开始的字节来衡量的.

a.ftell用于二进制文件,返回值是这个字节位置。
b.为了用fseek定位一个二进制文件,我们必须指定一个字节偏移以及这个偏移如何被解释。whence的值和前面的lseek函数里的一样:SEEK_SET表示从文件开头开始;SEEK_CUR表示从当前文件位置开始;SEEK_END表示从文件末尾开始。
c.使用rewind函数也可将一个流设置到文件起始位置

2).ftello函数和ftell函数相同,而fseeko函数和fseek函数相同,除了偏移量的类型是off_t而不是long

#include <stdio.h>
off_t ftello(FILE* fp);
//成功返回当前文件位置,否则返回(off_t)-1。
int fseeko(File *fp, off_t offset, int whence);
//成功返回0,失败返回非0.

3).想移植到非UNIX系统上运行应用程序应该使用fgetpos和fsetpos

#inlcude <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);
//两者成功都返回0,失败返回非0值。

fgetpos返回把文件位置指示器的值存储在由pos指向的对象里。这个值可以被之后的fsetpos调用使用,来重定位流的位置。

8.格式化I/O
1).格式化输出
格式化输出由4个printf函数处理:

#include <stdio.h>
int printf(const char *restrict format, ...);
int fprintf(FILE *restrict fp, const char *restrict format, ...);
//两者成功都返回字符数,输出错误则返回负值。
int sprintf(char *restrict buf, const char *restrict format, ...);
int snprintf(char *restrict buf, size_t n, const char *restrict format, ...);
//两者成功都返回在字节里存储的字符数量,编码错误返回负值。

printf函数向标准输出写,fprintf向一个指定的流写,而sprintf把格式化的字符串放入到数组buf里。sprintf函数在数组末自动将上一个空字节,但这个空字节没有包含在返回值里。注意sprintf可能为溢出buf指向的缓冲区,这时调用者的执行来保证这个缓冲区足够大。

下面4个printf家族的变体与前4个函数相似,只是参数列表(...)被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 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);
//两都成功都返回数组存储的字符数,编码错误返回负数。

2).格式化输入

格式化输入由三个scanf函数处理

#include <stdio.h>
int scanf(const char *restrict format, ...);
int fscanf(FILE *restrict fp, const char *restrict format, ...);
int sscanf(const char *restrict buf, const char *restrict format, ...);
//三者都返回被赋值的输入项的数目,输入出错或碰到文件结尾时返回EOF。

scanf家族用来解析一个输入的字符串并转换成指定类型的变量。在格式后的参数包含需要用转换结果初绐化的变量的地址。

和printf家族一样,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

9.实现细节
可以调用fileno来得到流的文件描述符

#include <stdio.h>
int fileno(FILE *fp);
//返回相关联的文件描述符。

10.临时文件

ISO C标准定义了两个由标准I/O库提供的函数,用来协助创建临时文件

#include <stdio.h>
char *tmpnam(char *ptr);
//返回指向唯一的路径名的指针。
FILE *tmpfile(void);
//成功返回文件指针,失败返回NULL

tmpname函数返回一个不与任何已有文件相同的合法路径名的字符串

以上是关于系统编程--标准IO的主要内容,如果未能解决你的问题,请参考以下文章

Linux系统编程IO标准缓冲区

1.Linux标准IO编程

Linux_Unix系统编程Chapter4 文件IO

Unix高级编程之标准IO

golang代码片段(摘抄)

LINUX原始IO和标准IO