Linux下的标准I/O库函数小记

原文链接:我的个人博客

摩托车
图片来自免费图片网站:pixabay

当达旺把丁天民和江雪在那什海的照片给他两时,问了一句:“你们认识多久了”
“一天。”丁天民对达旺说。
“那什海的鸟一天要飞几百万次,那什海的云一天要变换几千万次,一天已经很久了。”达旺说。
引自:《末日浮城》


以下内容来自《UNIX环境高级编程》的读书笔记整理。

一、前提

  • 文件流
    标准IO操作的是一个FILE文件流对象,它是自带缓存的。这个缓存不能说他好,也不能说它坏,因为这个缓存存在一些问题。与之对应的是使用文件描述符进行的操作。
    其实,标准文件流就是对文件描述符进行了一些封装,FILE对象是一个结构体,它里面包含了要操作的文件的文件描述符,缓存大小等属性信息。
    而我们操作FILE对象的方式是使用文件指针FILE*来进行操作的。

  • 标准输入输出和出错:
    在文件描述符的操作中前面三个描述符(0,1,2)是被预定义为标准输入(STDIN_FILENO),标准输出(STDOUT_FILENO)和标准出错(STDERR_FILENO)的。
    同样的在标准IO流中也预定义了三个文件指针:

    #include <stdio.h>
    stdin  //标准输入
    stdout  //标准输出
    stderr  //标准出错
  • 缓存
    缓存的目的是:为了减少调用read()和write()的数量。从下面你的表中可以看出不同缓存大小为执行IO所需要的CPU时间大小。

缓存作用
可以看出适量的缓存还是很有必要的。

缓存的分类:
(1) 全缓存:这种情况下,当填满标准IO的缓存才进行标准IO操作。一般对磁盘上的文件使用全缓存。缓存的内存在第一次IO操作时调用malloc分配。
(2) 行缓存:这种情况下,当遇到换行符,执行一次标准IO操作。这就允许操作单个字符的情况。但是行缓存也是有大小限制的,并不是无限的。当行缓存被填满,即使没有换行符也进行IO操作。
(3) 不带缓存:标准出错一般是不带缓存的。IO操作相当于write、read

缓存刷新: flush(缓存写操作)和fflush(缓存刷新)。

二、操作函数

1.缓存操作函数

ANSI C要求的缓存特征:
(1)当且仅当标准输入和标准输出并不涉及交互设备时,它们才是全缓存。
(2)标准出错不会是全缓存的。

没有说明交互设备使用什么,也没有说明标准出错使用什么缓存。一般认为,交互设备使用行缓存,标准出错不带缓存。

我们可以通过一下两个函数对缓存方式进行改变:

#include<stdio.h>
//操作之前文件流是已被打开的,很明显。
void setbuf(FILE *fp, char *buf);//setbuf 函数打开和关闭缓存机制
/*
参数buf必须指向一个长度为BUFSIZ(该常数定义在<stdio.h>中)的缓存。一般默认该流为全缓存。buf=NULL时候是关闭该缓存。
*/
void setvbuf(FILE *fp, char *buf, int mode, size_t size); //可以精确的设置所需缓存类型和大小

/**
mode参数:
_IOFBF 全缓存
_IOLBF 行缓存
_IONBF 不带缓存
(1)如果指定一个不带缓存的流,则忽略buf和size参数
(2)如果制定全缓存或者行缓存,则buf和size可以选择的指定一个缓存及其长度。
(3)如果该流是带缓存的,而buf是NULL,则IO库将自动为该流分配适当长度的缓存。
(适当长度来自库

#include<stdio.h>
int fflush(FILE *fp);
    //返回:若成功则为0,若出错则为EOF

这个函数使该流所有未写的数据都传递到内核。
一个特殊的用途:若是传入的fp=NULL,则刷新所有的输出流。

2.打开流

#include <stdio.h>
FILE *fopen(const char *pathname, const char *type);
FILE *freopen(const char *pathname, const char *type, FILE *fp);
FILE *fdopen(int filedes, const char *type);
// 三个函数的返回:若成功返回文件的指针,若出错,返回NULL

(1)fopen打开路径名为pathname的一个文件
(2)freopen在一个特定的流上(fp指向的流)打开一个指定的pathname的文件。如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出、标准出错
(3)fdopen取一个现存的文件描述符,并使一个标准IO流与该文件描述符结合。这个函数一般用于由创建管道和网络通信通道函数获得的描述符。因为这些特殊类型的文件不能用标准IO fopen打开。

type参数类型:
type参数
注意:书中说字符b作为type的一部分,代表的是打开二进制文件(区分与文本文件),但是UNIX系统对两种文件并没有进行区分,所以在UNIX系统中b在type中并没有实际作用。

当type类型是读写(type有+号),有一些限制:
(1)如果中间没有fflush,fseek,fsetpos和rewind,则输出后面不能直接跟输入。(可能是缓存的原因吧)
(2)如果中间没有fseek,fsetpos和rewind,则输入后面不能直接跟输出。

不要使用fopen去创建一个文件,因为他不能指定文件的基本权限,容易出问题。(模棱两可的就直接禁止好了)

  • 关闭流
    调用fclose
#include <stdio.h>
int fclose(FILE *fp)

关闭文件的时候,刷新缓存中的输出数据,输入数据丢去。
程序正常终止的时候,所有带未写缓存的标准IO流都被刷新,所有打开的流都被关闭。

3.读写流

三种非格式化IO流操作
(1)每次一个字符IO
(2)每次一行IO
(3)直接IO

3.1输入函数
#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
//三个函数返回值:若成功则为下一个字符,若已经处于文件尾端或出错则为EOF

getchar() = getc(stdin);
getc()可实现为宏。(还没见过)
fgetc()不能实现为宏。
三个函数以unsigned char类型转换为int类型的方式返回下一个字符。使用无符号的char的理由是当最高位是1的时候返回值不为负值。
返回值为int的理由是:这样就可以返回所有可能的字符和一个错误标志EOF(一般是负值)

当三个函数出错或者到达文件末尾的时候返回值都是EOF。为了区分这两种情况,使用以下两个函数:

#include <stdio.h>
int ferror(FILE *fp)
int feof(FILE *fp)
//返回值:若条件为真则为非0,否则为0
3.2输出函数

每个输入函数都对应着一个输出函数:

#include <stdio.h>
int putc(int c,FILE *fp);
int fputc(int c, FILE *fp);
int putchar(int c);
//返回值:若成功返回c,若出错返回EOF

putchar() = putc(c,stdout);

4.每次一行IO

  • 每次一行输入
    下面两个函数提供每次输入一行
#include<stdio.h>
char *fgets(char *buf, int n, FILE *fp);
char *gets(char *buf);
//返回值:若成功则为buf,若处于文件尾端或者出错则为NULL

都有缓存地址,gets从标准输入读,fgets从指定的流读。
fgets,必须指定缓存长度n,它会根据这个长度一直读到下一个换行符,但是不会超过n-1个字符,缓存以null字符结尾,fgets下一次调用会继续该行。
gets,不推荐使用,缓存长度不指定,可能导致缓存越界。
gets和fgets还有一个不同点是:gets不将新行符号存入缓存中。(可恨的是后面的puts中又自己加入换行符,有些情况你不知道这个东西会出现预想不到的错误。)

  • 每次一行输出:
#include <stdio.h>
int fputs(const char *str, FILE *fp);
int puts(const char *str);
//返回值:若成功返回非负值,若出错则为EOF

fputs将一个以null符终止的字符串写到指定的流中。终止符null不写出来。
puts 将一个以null符终止的字符串写入标准输出,null符不写出,但是它会给你加一个换行符到标准输出。

5.二进制IO

#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nobj, FILE *fp);
size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *fp);
//返回值:读写的对象数

参数很明显,size代表读写对象的自身的字节数,nobj代表读写对象的数量。
错误处理:对于读,如果出错或者到达文件尾端,则此数字可以少于nobj。在这种情况下,应该调用ferror或者feof以判断究竟是哪一种情况。
对于写,如果返回值少于所要求的nobj,则出错。

对于二进制IO最大的问题是:它只适合于读写同一个系统上的数据,这样是没有太大的问题的。由于现在网络文件系统的普遍,造成不同系统之间的读写,对齐使得直接使用这两个函数进行读写会造成错误。
不同系统之间交换二进制数据需要使用较高层次的协议。

6.定位流

两种方式来定位流的位置:
(1)ftell和fseek,他的前提是:坚定文件的位置可以存放在一个长整型中(2)fgetpos和fsetpos 非UNIX系统的ANSI C函数。

#include <stdio.h>
long ftell(FILE *fp);
//返回值:若成功,则为当前文件位置的指示,若错误则为-1L

int seek fseek(FILE *fp, long offset,int whence);
//返回值:若成功,则为0,出错则为非0
//whence = SEEK_SET(文件起始)   SEEK_CUR(文件当前)  
SEEK_END(文件结尾)

void rewind(FILE *fp);

二进制文件:以字节为计量单位,位置指示器从文件开始位置度量,ftell返回的也是字节位置。offset也必须是一个字节偏移量。
文本文件:当前位置不以简单的字节位移量来度量。这种区分也主要是针对非UNIX系统的,为了定位一个文本文件,whence一定要是SEEK_SET,offset只能有两个值:0(表示反绕文件到起始位置),ftell返回的值。

rewind函数可以将一个流设置到文件的起始位置。

//C标准引进的两个函数

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

int fsetpos(FILE *fp, const fpos_t *pos);
//返回值:若成功为0,出错为非0

7.格式化IO

7.1 格式化输出
#incldue <stdio.h>
int printf(const char *format, ...);
int fprintf(FILE *fp, const char *format, ...);
//返回值:成功则为输出的字符数,出错则为负值

int sprintf(char *buf, const char *format, ...);
//返回存入数组的字符数

printf将格式化数据写到标准输出。
fprintf将数据写到指定的流
sprintf将格式化数据写到数组buf中,尾端自动加上null字符,该字符不包含在返回值中。(保证buf缓存不会溢出是作为程序员的基本)

7.2 格式化输入
#include <stdio.h>
int scanf(const char *format,...);
int fscanf(FILE *fp, const char *format, ...);
int sscanf(const char *buf, const char *foemat, ...);
//返回值:指定输入的项数,出错或者到文件尾端返回EOF
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值