标准IO库整理--自APUE

文章详细介绍了C语言中文件输入输出(IO)与标准IO的区别,包括FILE对象的结构和作用,标准输入、输出、错误的FILE对象,以及不同类型的缓冲(全缓冲、行缓冲、无缓冲)。此外,还讲解了如何修改缓冲类型、冲洗流、打开和关闭流、读写流以及流的定位等操作。
摘要由CSDN通过智能技术生成

概述

文件IO和标准IO的区别

  1. 文件IO围绕文件描述符,标准IO围绕流,1)流是标准IO函数的参数,文件描述符是系统调用的参数;2)文件流封装了描述符的功能;3)流包含了读和写的缓冲
  2. 标准IO提供缓冲,是为了减少read和write的调用次数(系统调用),这里的缓存指的是用户态的缓存,在用户态缓存满了之后才回去掉read或write函数进行磁盘IO,但是其实read和write也不是没有缓存,比如内核处理write的时候也只是把数据丢到输出队列等待磁盘IO,并不能保证立即写到磁盘中。

FILE对象

定义:

struct _iobuf
{
    char *_ptr; //下一个要被读取的字符的地址
    int   _cnt; //剩余的字符,如果是输入缓冲区,那么就表示缓冲区中还有多少字符未被读取
    char *_base;//缓冲区基地址
    int   _flag;//读写状态标志位
    int   _file; //文件的有效性验证
    int   _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
    int   _bufsiz; //文件的大小
    char *_tmpfname;//临时文件名
};
typedef struct _iobuf FILE;

作用:

  1. 管理流需要的所有信息
  2. 作为参数传递给标准IO函数

标准输入、输出、错误的FILE对象

标准输入、输出和错误都有对应的文件描述符宏定义和FILE对象的宏定义。

文件描述符为:

  • 标准输入:STDIN_FILENO

  • 标准输出:STDOUT_FILENO

  • 标准错误:STDERR_FILENO

FILE对象为:

  • 标准输入:stdin
  • 标准输出:stdout
  • 标准错误:stderr

缓冲类型

全缓冲、行缓冲、无缓冲

  • 全缓冲:填满标准IO缓冲区后,执行冲洗,将缓冲区中的数据写到磁盘中,并清空缓冲区。

  • 行缓冲:遇到换行符,再执行冲洗。限制,行缓冲区的大小固定,可能不到换行就进行IO操作。判断是否行缓冲:

    FILE *fp->_flags & _IO_LINE_BUF
    
  • 无缓冲:标准错误通常不带缓冲,可以立即输出。判断是否无缓冲:

    FILE *fp->_flags & _IO_UNBUFFERED
    

    一般来说:标准错误不带缓冲;指向终端设备的流,行缓冲,其他情况,使用全缓冲

修改缓冲类型1:setbuf

setbuf函数能够修改一个已经打开的流的缓冲类型。一般在打开流之后,第一次使用流之前进行缓冲类型的设置。

#include <stdio.h>
void setbuf(FILE *stream, char *buf);
成功返回0,失败返回非0,并且置上errno;
buf:如果buf指向一个缓冲区,那么就是设置成全缓冲,并且缓冲区大小就是全缓冲的大小;如果buf = NULL,则不带缓冲;

修改缓冲类型2:setvbuf

setvbuf用于使用一个新的缓冲区来代替原来流的缓冲区。一般在打开流之后,第一次使用流之前,进行设置。

#include <stdio.h>
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
成功返回0,失败返回非0,并且置上errno;
mode表示设置缓冲的类型,是个枚举,包括:
    _IONBF:无缓冲
    _IOLBF:行缓冲
    _IOFBF:全缓冲
如果mode是无缓冲,那么忽略buf参数;
如果mode不是无缓冲,buf就是新的缓冲区的地址,size就是缓冲区的大小;如果buf=NULL,那么由系统来分配缓冲区地址和大小。

冲洗流:fflush

fflush的作用是把用户态缓冲中的数据立即进行冲洗(前提是这个流已经被以可写的状态打开),包括:

  1. 对于输出缓存中的数据,立即调用write系统调用进行处理
  2. 对于输入缓存中的数据,对于应用程序还没有处理的数据,直接被丢弃
#include <stdio.h>
int fflush(FILE *stream);
若stream非空,将stream对应缓冲区所有数据送到内核;若stream为空,则冲洗所有的缓冲区;
冲洗成功返回0,失败返回非0,并且置上errno;

打开流

打开流:fopen

fopen用于打开一个文件,并且给它关联上一个流。

#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
path:文件路径;
mode:文件打开方式,是字符串,详见下面的表格;
返回值:成功返回FILE指针,失败返回NULL;
moderwar+w+a+
文件必须存在11
放弃文件以前内容11
可读1111
可写11111
尾端写11

打开流:freopen

freopen的作用是关闭文件关联的当前的流,然后创建一个新的流关联上去。

#include <stdio.h>
FILE *freopen(const char *path, const char *mode, FILE *stream);
path:文件路径;
mode:文件打开方式,是字符串,详见fopen;
返回值:成功返回新的FILE指针,失败返回NULL;
stream:文件原来的FILE指针;

根据文件描述符产生流:fdopen

fdopen的作用是,针对一个已经打开的文件描述符,创建一条流并且与这个文件描述符相结合。通常用于,将管道和网络通信返回的描述符(这些不能用标准IO打开),用fdopen打开后,关联到一个标准IO

#include <stdio.h>
FILE *fdopen(int fd, const char *mode);
mode:文件打开方式,是字符串,详见fopen;
fd:已经打开的文件描述符;
返回值:成功返回新的FILE指针,失败返回NULL;

根据流获取文件描述符:fileno

有文件描述符不一定有流,有流一定有关联的文件描述符,fileno用于获取流对应的文件描述符。

#include <stdio.h>
int fileno(FILE *stream);
返回值:成功返回关联的文件描述符,失败返回-1,并且置上errno;
stream:被获取文件描述的流;

关闭流:fclose

fclose做了以下几个事情:

  1. 丢弃输入缓冲区的数据
  2. 调用fflush冲洗输出缓冲区的数据
  3. 释放缓冲区
#include <stdio.h>
int fclose(FILE *fp);
fp:待关闭的流指针;
返回值:成功返回0,失败返回非0,并且置上errno;

读写流

从指定流读一个字节:getc

作用:从流中读取下一个字节,读出来的数据强制类型转换成int型返回

#include <stdio.h>
int getc(FILE *stream);
返回值:成功返回读出来的数据,失败返回EOF;
stream:被读取的流;

从指定流读一个字节:fgetc

同getc

#include <stdio.h>
int fgetc(FILE *stream);

从标准输入读一个字节:getchar

等效getc(stdin)

#include <stdio.h>
int getchar(void);
返回值:成功返回读出来的数据,失败返回EOF;

从指定流读一行数据:fgets

fgets的作用是从指定流读取一行数据,放入到指定的缓冲区域,读过来的数据会在最后加上’\0’,读取结束的判断:

  1. 读到换行符
  2. 读到的数据在换行之前就已经把缓冲区填满了。由于最后的\0’占了一个字节,所以假设缓冲区是4,那么最多只能读取3字节数据
  3. 读取错误
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
返回值:如果读取成功,返回s,如果读取失败,返回NULL;
stream:准备读取的流;
s:读过来的数据放在哪个缓冲区;
size:缓冲区的大小;

从标准输入读取一行数据:gets

注意到gets函数没有传递s的大小,所以使用的时候有一定的缓冲区溢出风险,实际编译的时候也会提示,所以最好使用fgets代替gets:

main.c:7:2: warning: ‘gets’ is deprecated (declared at /usr/include/stdio.h:638) [-Wdeprecated-declarations]
#include <stdio.h>
char *gets(char *s);

从指定流读取指定大小的数据:fread

fread是一种二进制IO,作用是从指定流读取nmemb个size字节大小的数据,函数原型如下:

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size:读取的块的字节数;
nmemb:读取的块的数目;
stream:从哪个流读取;
返回值:读取成功,返回实际读取的字节数目,读取失败的话或者没有数据可读,返回0,所有需要用feof或ferror来判断是读完了还是读失败了;

向指定流写一个字符:putc

作用是往指定的流输出一个字符,这个字符要强制转成int再作为参数传递进去,返回值也是这个字符强制转成int的结果。

#include <stdio.h>
int putc(int c, FILE *stream);
返回值:成功,返回输出字符强制类型转成int之后得到结果,失败返回EOF;
c:准备输出的字符强制转成int之后的结果;
stream:准备往哪个流输出;

例如:

#include <stdio.h>

int
main(int argc, char **argv)
{
        int ret;
        int c = 57;
        if ((ret = fputc(c, stdout)) == EOF)
        {
                perror("fputc failed.\n");
        }
        printf("\nret: %d\n", ret);
        printf("ret: %c\n", ret);
        return 0;
}

输出:因为字符‘9’用ascii码表示就是int型的57

[root@localhost putc]# ./a.out 
9
ret: 57
ret: 9

向指定流写一个字符:fputc

同putc

#include <stdio.h>
int fputc(int c, FILE *stream);
返回值:成功,返回输出字符强制类型转成int之后得到结果,失败返回EOF;
c:准备输出的字符强制转成int之后的结果;
stream:准备往哪个流输出;

向标准输出写一个字符:putchar

#include <stdio.h>
int putchar(int c);//等效于putc(c,stdout)
返回值:成功,返回输出字符强制类型转成int之后得到结果,失败返回EOF;
c:准备输出的字符强制转成int之后的结果;

向指定流写一行数据:fputs

fputs函数的作用是把缓冲区s中的数据向指定的流输出,直到遇到’\0’,注意输出的时候不会带上’\0’

#include <stdio.h>
int fputs(const char *s, FILE *stream);
返回值:成功返回非负整数,表示向stream输出了多少字节的数据,失败返回EOF;
s:输出数据来自哪个缓冲区;
stream:数据输出到哪个流;

向标准输出写一行数据:puts

#include <stdio.h>
int puts(const char *s);
返回值:成功返回非负整数,表示向stream输出了多少字节的数据,失败返回EOF;
s:输出数据存放的缓冲区;

向指定流写指定大小的数据:fwrite

fwrite是一种二进制IO,作用是向指定流写入nmemb个size字节大小的数据,函数原型如下:

#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
size:写的块的字节数;
nmemb:写的块的数目;
stream:写到哪个流;
返回值:读取成功,返回实际写的字节数目,写失败的话或者没有数据可写,返回0;

流标志探测

每个流对象维护2个标志,出错标志和文件结束标志,ferror用于探测流是否出错,feof用于探测流是否到了末尾,clearerr用于清除这两个标志。

探测流状态是否错误:ferror

#include <stdio.h>
int ferror(FILE *stream);
stream:被探测的流;
返回值:非0表示流的状态是错误的;

探测流状态是否到末尾:feof

#include <stdio.h>
int feof(FILE *stream);
stream:被探测的流;
返回值:非0表示流的状态是end-of-file;

清除流状态:clearerr

#include <stdio.h>
void clearerr(FILE *stream);
stream:被操作的流,一旦执行error和eof标志都会被清除;

定位流

类似文件IO中的lseek,标准IO也有一些函数能够对流的相对位置进行查询和修改。比如一条流关联上一个文件的时候,相对位置是0,此时调用fgets会从头开始获取数据,并且偏移量会被修改,下一次调用fgets的时候,会接着上次的位置获取数据,如果想要从头开始,需要调用rewind来重置偏移量。

所谓的偏移量就是指的游标相对于文件起始位置的字节距离,这个偏移量每个文件会给每个打开它的进程维护一个隔离的值。

获取当前的偏移量:ftell

#include <stdio.h>
long ftell(FILE *stream);
stream:获取哪个流的偏移量;
返回值:成功,返回当前的偏移量,失败返回-1,并且置上errno;

修改当前偏移量:fseek

#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);
返回值:成功,返回0,失败返回-1,并且置上errno;
stream:被操作的流
offset:相对于whence的偏移量
whence包括:
    SEEK_SET:相对于文件头部进行偏移
    SEEK_CUR:相对于当前位置进行偏移
    SEEK_END:相对于文件尾部进行偏移,偏完指针可以超过原始文件大小 

重置偏移量:rewind

作用是把偏移量设置到文件的起始位置,等效于:

fseek(stream, 0L, SEEK_SET)
#include <stdio.h>
void rewind(FILE *stream);
stream:被操作的流;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值