Linux环境编程---标准I/O

前面的章节主要介绍linux的系统调用,这一节介绍linux的C库,标准IO库有很多细节,如缓冲区分配,优化块长度执行IO等接下来让我们来看看C库中的标准IO

流和FILE对象

在上一章中所有IO都是围绕文件描述符进行的,当我们打开一个文件的时候,返回它的文件描述符,而对于标准IO库,它的操作是围绕流进行的。当我们用标准IO库打开一个文件的时候,我们已使一个文件与流相关联。
流的定向
对于ASCII字符集,一个字符用一个字节表示,对于国际字符集,一个字符可用多个字节表示,标准IO可用单字节或者多字节(宽)来表示流。流的定向决定了所读写的字符是单字节还是多字节的。
当一个流最初被创建的时候,它是没有定向的,如果在未定向的流上用了一个多字节IO函数那么该流被设定为宽定向的,如果在未定向的流上使用了一个单字节IO函数,那么流被设置为字节定向的。只有两个函数可以改变流的定向,fropen函数可以清楚一个流的定向,fwide函数可用于设置流的定向。
int fwide(FILE *stream, int mode);
若流是宽定向的返回正值,若流是字节定向的返回负值,若未定向返回0

  • 若mode参数值为负,fwide试图指定流为字节定向
  • 若mode参数值为正,fwide试图指定流为宽定向的
  • 若mode参数值为0,fwide不指定流的定向,但返回标识该流定向的值

需要注意的是fwide并不改变一个已经定向的流,还要注意fwide没有出错返回
当我们打开一个流的时候,标准IO函数fopen返回一个指向FILE的指针,该对象结构包括了标准IO库为管理该流所需要的所有信息,包括实际IO的文件描述符,指向用于该缓冲区的指针等
标准输入/输出/错误
当Linux创建一个新进程的时候,会自动创建三个文件描述符,分别为0,1,2分别对应标准输入,标准输出,标准错误。C库中与文件描述符相对应的文件指针。与文件描述符相似,我们可以用文件指针操作。
通过分析内核我们可以发现,stdin,stdout,stderr都是FILE类型的文件指针,是由C库静态定义的,通过宏定义直接与文件描述符0,1,2相关联。

缓冲

C库中的IO对文件IO进行了封装,为了提高性能,引入了缓冲机制,共有三种缓冲机制:

  • 全缓存:一般用于访问真正的磁盘文件,C库会为文件访问申请一块内存,只有当文件内容将缓存填满或执行冲刷函数flush的时候,C库才会将缓存写入内核。
  • 行缓存:一般用于终端访问,当遇到一个换行符的时候,就会引发真正的IO操作,需要注意的是,C库中的行缓存也是固定大小的,因此当缓存已满即使没有换行符也会引发IO操作
  • 不带缓冲:标准IO库不对字符进行缓冲
  • 需要注意的是标准错误是不带缓冲的这样错误信息可以尽快显示出来

ISOC要求有下列特征:

  • 当且仅当标准输入与标准输出并不指向交互设备的时候他们才是全缓冲
  • 标准错误是不带缓冲的
  • 若是指向终端设备的流,则是行缓冲,否则是全缓冲

我们通常用下列函数改变缓冲类型
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
这些函数一定要在流已经被打开的基础上使用
可以使用setbuf关闭或者打开缓冲机制,stream是要操作的流,buf是我们提供的缓冲区,当我们要关闭缓冲机制的时候将buf设置成nullptr当我们将buf设置成缓冲区的时候,要是流与终端设备相关的话那就是行缓冲,否则就是全缓冲
使用setvbuf可以精确的说明所需的缓冲类型这是由参数mode实现的
_IOFBF 全缓冲
_IOLBF 行缓冲
_IONBF 不带缓冲
若果指定一个不带缓冲的流,则忽略buf和size参数,如果指定全缓冲或者行缓冲,则buf与size可选择一个缓冲区长度,如果该流是带缓冲的,而buf是nullptr则标准IO库将自动的为流分配适当长度的缓冲区,这个长度由BUFSIZE指定。
在这里插入图片描述

流的打开与读写

流的打开

下面三个函数用来打开一个流
FILE *fopen(const char *path, const char *mode);
FILE *fdopen(int fd, const char *mode);
FILE *freopen(const char *path, const char *mode, FILE *stream);
这三个函数的区别如下
fopen函数打开路径名为path的一个指定文件
freopen函数在一个指定的流上打开一个指定文件,若该流已经打开,则先关闭该流,若该流已经定向就清除该定向,此函数一般用于将一个指定的文件打开为一个预定义的流
fdopen函数取一个已有的描述符,并使一个标准IO流与该描述符相结合,通常用于管道创建和网络通信等
type参数指定对该IO的读写方式
在这里插入图片描述
使用b作为type的一部分是为了使得标准IO区分文本与二进制,另外标准IO追加写方式也不能用于创建一个文件。
当以读写方式打开一个文件的时候,具有下列限制

  • 如果中间没有fflush,fseek,fsetpos或rewind,则在输出后面不能直接跟输入
  • 如果中间没有fseek,fsetpos,rewind或者一个输入没有到达文件尾端,则在输入后不能直接跟输出

文件描述符与流的转换
linux提供了文件描述符,而C库提供了流,C库是对于系统调用的一层封装,于是我们提供了两个相互转换的API
FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);
fdopen用于从文件描述符fd生成一个文件流FILE,而fileno则用于从文件流FILE得到对应的文件描述符文件流FILE保存了文件描述符的值,当从文件流转换成文件描述符的时候,可以直接通过当前FILE保存的值得到fd,而从文件描述符转换成文件流的时候,C库返回都是一个重新申请的文件流FILE。

读和写流

一旦打开了流,则可在三种不同类型的非格式化IO中进行选择,对其进行读写操作

  1. 每次一个字符的IO,一次读或写一个字符,如果流是带缓冲的,则标准IO函数处理所有缓冲
  2. 每次一行的IO,如果想要一次读或写一行,则使用fgets和fputs每行都是换行符结束
  3. 直接IO,fread与fwrite函数支持这种类型的IO每次IO操作读或写某种数量的对象,对象指定长度

每次一个字符的IO
以下三个函数用于一次读一个字符
int getc(FILE *stream);
char *fgets(char *s, int size, FILE *stream);
int getchar(void);
前两个函数的区别在于getc可以被实现为宏,而fgetc不能
不管是出错还是达到文件末尾,这三个函数都返回相同的值为了区分两种情况,调用ferror, ferror用于告诉用户C库的文件流FILE是否有错误发生,当有错误发生就返回非零值,反之返回0
从流中读取数据以后,可以调用ungetc将字符再压回流中
int ungetc(int c, FILE *stream);
押送回流中的字符可以再次从流中读取,但是读出的顺序与押送的相反,回送的字符不一定不许是上一次读到的字符,不能回送EOF但是当已经到达文件末尾的时候依旧可以回送一个字符下次读将返回该字符再读就返回EOF
用ungetc压送回字符的时候,并没有将他们写道底层设备上,只是写回缓冲区
每次一行的IO
char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);
这两个函数都指定了缓冲区的地址,读入的行送入其中
对于fgets,必须指定缓冲的长度n,此函数一直读到下一个换行符为止,但是不超过n个字符读入的字符被送入缓冲区,该缓冲区以null字节结尾。

二进制IO

在前面我们看了一次一个字符或者一次一行进行操作的函数,可是如果进行二进制操作的话,我们更愿意一次读或者写一个结构,如果用getc我们要循环整个结构,如果是gets万一碰到换行符也就没办法了,于是我们提供下面两个函数进行二进制IO操作
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
第一个参数指向结构,第二个参数是结构大小,第三个参数是结构个数,第四个参数是流
使用二进制IO的问题是,他只能用于读在同一系统上已写的数据,当我们在一个系统上写的数据要在另一个系统上读就会发生问题原因是:

  1. 在同一个结构中,同一成员的偏移量可能随程序编译和系统的不同而不同
  2. 用来存储多字节整数和浮点值的二进制格式在不同的系统结构间也有可能不同
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值