知识杂货铺

不卖切糕

View on GitHub
6 October 2017 14:19

《UNIX环境高级编程》 - Standard IO Library

by 宋强

流和FILE对象

fwide

#include <stdio.h>
#include <wchar.h>
int fwide(FILE *fp, int mode);

fwide用于设置一个未定向的流的宽度,不能用于已定向的流。

mode  
宽定向的
字节定向的
不设置流的定向,但是返回标识流定向的值

标准输入,输出和出错

这三个的预定义文件描述符宏为STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO,预定义的流文件指针为stdin、stdout和stderr。

缓冲

缓冲有三种:

通常不涉及交互的设备不需要进行行缓冲或者不缓冲,类似于命令行终端的设备使用行缓冲,保证交互性,标准错误输出使用不缓冲,这样能够使错误信息尽可能快的提示出来。

更改缓冲类型

#include <stdio.h>
void setbuf(FILE *restrict fp, char *restrict buf);
int setvbuf(FILE *restrict fp, char *restrict buf, int mode, size_t size);

setbuf用于打开关闭缓冲,当给buf一个缓冲区的时候,采用系统决定的缓冲类型,当给buf一个NULL指针的时候,缓冲被关闭。

setbuf和setvbuf的实际效果如下:

函数 mode buf 缓冲区及长度 缓冲类型
setbuf 宽定向的 非空 长度为BUFSIZE的用户指定buf 全缓冲或者行缓冲
  字节定向的 NULL 无缓冲区 无缓冲
setvbuf _IOFBF 非空 长度为size的用户指定buf 全缓冲
    NULL 合适长度的系统缓冲区  
  _IOLBF 非空 长度为size的用户指定buf 行缓冲
    NULL 合适长度的系统缓冲区  
  _IONBF 忽略 无缓冲区 无缓冲

强制清空流

流可以被强制冲洗,使用fflush函数:

#inlcude <stdio.h>
int fflush(FILE *fp);

将缓冲区内的所有数据进行IO操作,如果fp为NULL,那么将冲洗所有缓冲区。

打开和关闭流

有三个函数可以打开标准IO流:

#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);

区别:

type有以下几种:

字符b是用于区分文件是文本文件还是标准二进制文件,不过在UNIX中不区分这两种数据,所以b在UNIX中没有作用。

使用fclose关闭一个流

#include <stdio.h>
int fclose(FILE *fp);

对打开的流进行读写

使用标准库对流读写有三种方式:

字符形式读

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);

getchar()相当于fgetc(stdin),第一个getc是预定义宏,剩下两个是函数,返回值由unsigned char被强制转换成了int。

这三个函数也有些问题,就在于出错或者到达文件尾端,他们都将返回-1,但是可以使用下面的函数来获取一个流的错误状态:

#incldue <stdio.h>
int ferror(FILE *fp);
int feof(FILE *fp);
void clearerr(FILE *fp);

这样个函数如果返回零的话代表相应的事件发生。

每个流为错误留下了两个标志,一个出错标志和一个到达流末尾的标志,使用最后一个clearerr函数则可以清除这两个标志。

将字符送入到流中

流里面读取出字符之后可以使用ungetc函数将字符压回到流,类似于入栈操作,读取出来的时候也类似于栈使用的FILO形式。

#include <stdio.h>
int ungetc(int c, FILE *fp);

通常有的时候要根据一个流前几个字符是什么来决定要如何处理后面的,比如说帧头指定的后面压缩帧的长度,这样的话这个函数就会比较有用。

字符形式写

同样使用类似的三个函数进行写操作:

#include <stdio.h>
int putc(int c, FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);

行读

两个函数可以提供读行的操作:

#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);

gets相当于从标准输入读取,而且相比fgets,他并不讲换行符存入到缓冲区中,这个函数并不被推荐使用,因为他没有指定缓冲区,所以容易造成缓冲溢出。

行写

类似的,也有两个函数用于写行的操作:

#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);

fputs一直写到null,但并不输出null,并不保证一定有换行符,这个要自己处理,puts同样读取到null,但是他多输出一个换行符。

标准库的二进制IO

对于特殊结构的读写,我们希望能够自定制一次都写的字节数以及数据结构,这个时候就会希望直接使用write或者read,标准库提供了这样一个接口进行读写:

#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);

size为单个元素长度,nobj为个数。

两种常用用法:

定位标准流

有三种方式定位标准流:

ftell和fseek

#include <stdio.h>
long ftell(FILE *fp);
int fseek(FILE *fp, long offset, int whence);
void rewind(FILE *fp);

这个就和普通文件定位中的lseek一样的。

ftello和fseeko

#include <stdio.h>
off_t ftello(FILE *fp);
int fseeko(FILE *fp, off_t offset, int whence);

特殊之处就是offset比32位更长了。

fgetpos和fsetpos

#include <stdio.h>
int fgetpos(FILE *restrict fp, fpos_t *restrict pos);
int fsetpos(FILE *fp, const fpos_t *pos);

只是将存储位置的形式从一个变量变成了一个结构体。

格式化IO

输出

格式化输出使用的是四种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, ...);

sprintf是一个我们常用的函数,他相比printf会在最后自动插入一个null,但是这个函数非常容易导致缓冲区溢出,所以推荐使用snprintf函数,这个函数显式指定了缓冲区的大小。

format的格式

%[flags][fldwidth][precision][lenmodifier]convtype

flag

标志 说明
- 字段内左对齐输出
+ 总是显示带符号转换的符号
空格 如果第一个字符不是括号,则在其前面加上一个空格
# 制定另一种转换形式,(例如对16进制,采用0x前缀形式)
0 添加前导0进行填充

fldwidth

说明转换的最小字段宽度,如果得到的少,就用空格填充,这个宽度是一个非负10进制数,或者是一个*号。

precision

说明最少输出数字位数、浮点数转换后小数点后的最少位数、字符串转换后的最大位数。

lenmodifier

说明参数长度,可取值如下:

长度修饰符 说明
hh 有无符号char
h 有无符号short
l 有无符号long
ll 有无符号long
j intmax_t或uintmax_t
z size_t
t ptrdiff_t
L long double

convtype

转换类型 说明
d,i 有符号十进制
o 无符号八进制
u 无符号十进制
x,X 无符号十六进制
f,F double
e,E double
g,G 根据被转换的值,自动解释为f、F、e或E
a,A 十六进制指数格式的double
c 字符(带l为宽字符)
s 字符串(带l为宽字符)
p 指向void的指针
n 将到目前为止,所写的字符数写入到指针所指向的无符号整形数中
% %符号
C 宽字符,等效于lc
S 宽字符,等效于ls

标准输出的printf还有四个变体,可变参数表…替换成了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);

输入

#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, ...);

format

%[*][fldwidth][lenmodifier]convtype

前导*

前导*用于抑制转换,按照转换说明的其余部分进行转换,但是转换的结果并不存放在参数中。

fldwidth

说明最大宽度。

lenmodifier

说明用转换结果初始化的参数的大小。

convtype

转换类型 说明
d 有符号十进制,基数为10(基数???)
i 有符号十进制,基数由输入格式决定
o 无符号八进制
u 无符号十进制,基数为10
x 无符号十六进制
a、A、e、E、f、F、g、G 浮点数
c 字符
s 字符串
[ 匹配列出的字符序列,用]终止
[^ 匹配列出字符以外的所有字符,用]终止
p 指向void的指针
n 将到目前为止,所写的字符数写入到指针所指向的无符号整形数中
% %符号
C 宽字符,等效于lc
S 宽字符,等效于ls

和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);

一些支持

可以用fileno函数来获取一个文件流的文件描述符:

#include <stdio.h>
int fileno(FILE *fp);

临时文件

标准IO库提供了两个函数用于创建临时文件:

#include <stdio.h>
char *tmpnam(char *ptr);
FILE *tmpfile(void);

第一个tmpnam用于产生一个与现有文件名都不同的路径文件名字符串,调用的最多次数由TMP_MAX限定。

tmpfile产生一个临时文件,在程序结束的时候被自动删除。

还有两个扩展函数:

#include <stdio.h>
char *tempnam(const char *directory, const char *prefix);
int mkstemp(char *template);

第一个相比之下增加了可以指定存放目录和文件名前缀,第二个相比之下返回的是文件描述符,template放置的是一个路径名,后六个字符设置为XXXXXX,这样的话mkstemp会自动更改后六位,返回文件描述符,也获得了新的文件的名字。

tags: UNIX