lienhua34
2014-09-29

1 标准 I/O 流

之前学习的都是不带缓冲的 I/O 操作函数,直接针对文件描述符的,每调用一次函数可能都会触发一次系统调用,单次调用可能比较快捷。但是,对于需要频繁进行 I/O 操作的程序,频繁触发系统调用产生的消耗太大。

标准 I/O 库提供了带缓冲的 I/O 操作函数,这些函数围绕着一种叫做流(stream)的东西进行。当使用标准 I/O 库打开或创建一个文件时,系统提供了一个流与这个文件相关联。通过流的读入和输出完成所需要的 I/O操作。

标准 I/O 库使用一个 FILE 结构来管理流所需要的所有信息,包括:用于实际 I/O 的文件描述符、指向用于该流的缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等。指向 FILE 对象的指针我们可以称为文件指针。

标准 I/O 库为每个进程预定义了三个流:标准输入、标准输出和标准出错。这三个标准 I/O 流通过预定义文件指针 stdin、stdout 和 stderr 加以引用。这个三个文件指针定义在头文件 <stdio.h> 中。

2 缓冲

标准 I/O 流提供了缓冲是为了尽可能减少使用 read 和 write 系统调用的次数。标准 I/O 库提供了三种类型的缓冲:

1. 全缓冲。在这种情况下,在填满标准 I/O 缓冲区之后才进行实际 I/O操作。对于驻留在磁盘上的文件通常是由标准 I/O 库实施全缓冲的。

2. 行缓冲。在这种情况下,当在输入和输出中遇到换行符时,标准 I/O库执行 I/O 操作。当流涉及一个终端时(例如标准输入和标准输出),通常使用行缓冲。

3. 不带缓冲。标准 I/O 库不对字符进行缓冲存储。标准出错流 stderr 通常是不带缓冲的。

3 打开流

标准 I/O 库提供了 fopen 函数来打开标准 I/O 流,

#include <stdio.h>
FILE *fopen(const char *restrict pathname, const char *restrict type);
返回值:若成功则返回文件指针,若出错则返回NULL

参数 pathname 指定了文件路径;而参数 type 指定了对该 I/O 流的读、写方式,ISO C 规定 type 参数可以有 15 种不同的值,其分别如表 1所示,

表 1: 打开标准 I/O 流的 type 参数
type 说 明
r 或 rb 为读而打开
w 或 wb 把文件截短至 0 长,为写而打开
a 或 ab 添加;为在文件尾写而打开
r+ 或 rb+ 或 r+b 为读和写而打开
w+ 或 wb+ 或 w+b 把文件截短只 0 长,或为读和写而打开
a+ 或 ab+ 或 a+b 为在文件尾读和写而打开或创建

使用字符 b 作为 type 的一部分,是为了区分文本文件和二进制文件。但是对于 UNIX 系统来说,并不区分这两种文件,所有有没有字符 b 都是一样的。

除非流引用终端设备,否则按系统默认的情况,流被打开时是全缓冲的。若流引用终端设备,则该流是行缓冲的。

因为输入和输出都是同一个位置指针,所以当以读和写类型打开一个文件时(type 中 + 符号),具有下面限制:

• 如果中间没有 fflush、fseek、fsetpos 或 rewind,则在输出的后面不能直接跟随输入。

• 如果中间没有 fseek、fsetpos 或 rewind,或这一个输入操作没有达到文件尾端,则在输入操作之后不能直接跟随输出。

UNIX 系统还提供了另外两个函数用于打开标准 I/O 流,

#include <stdio.h>
FILE *freopen(const char *restrict pathname, const char *restrict type, FILE *restrict f
FILE *fdopen(int filedes, const char *type);
这两个函数返回值:若成功则返回文件指针,若出错则返回NULL

freopen 函数在一个指定的流上打开指定的文件,如若该流已经打开,则先关闭该流。此函数一般用于将一个指定的文件打开为一个预定义的流:标准输入、标准输出或标准出错。

fdopen 函数获取一个现有的文件描述符,并使一个标准 I/O 流与该描述符相结合。此函数常用于由创建管道和网络通信通道函数返回的描述符。

4 读和写流

标准 I/O 库提供了三种不同类型的非格式化 I/O 来对流进行读、写操作。

1. 每次一个字符的 I/O:一次读或写一个字符。

2. 每次一行的 I/O:每次读或写一行,每行都以一个换行符终止。

3. 直接 I/O:每次读或写指定长度的数据。

4.1 每次一个字符的 I/O

每次一个字符的输入函数为,

#include <stdio.h>
int getc(FILE *fp);
int fgetc(FILE *fp);
int getchar(void);
三个函数的返回值:若成功则返回下一个字符,若已达到文件结尾或出错则返回EOF

函数 getchar() 等价于 getc(stdin)。getc 和 fgetc 两个函数的区别是:getc可被实现为宏,而 fgetc 不能被实现为宏。

每次一个字符的输出函数为,

#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 函数可以被实现为宏,而 fputc 函数不能被实现为宏。

4.2 每次一行的 I/O

每次输入一行的输入函数,

#include <stdio.h>
char *fgets(char *restrict buf, int n, FILE *restrict fp);
char *gets(char *buf);
两个函数的返回值:若成功则返回buf,若已达到文件结尾或出错则返回NULL

gets 函数从标准输入读取,但是 gets 函数不推荐使用,因其未指定缓冲区大小,可能会造成缓冲区溢出。另外,gets 与 fgets 的另一个不同是,gets函数没有将换行符存入缓冲区中。

每次输出一行的输出函数,

#include <stdio.h>
int fputs(const char *restrict str, FILE *restrict fp);
int puts(const char *str);
两个函数返回值:若成功则返回非负数,若出错则返回EOF。

fputs 和 puts 两个函数都是将以 null 符终止的字符串写入到特定的流中(puts 函数写入到标准输出流),终止符不输出。但是,puts 函数会自动在输出字符串之后,再输出一个换行符。

4.3 直接 I/O

直接 I/O 也被称为二进制 I/O、一次一个对象 I/O、面向记录的 I/O或面向结构的 I/O,因为这种 I/O 函数通常被用于二进制 I/O 操作,一次读写一个二进制数组或者一个结构。

#include <stdio.h>
size_t fread(viod *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);
两个函数的返回值:读或写的对象数

这两个函数的 ptr 参数表示保存输入数据或要输出数据的缓冲区指针,size参数指定每个对象的大小,而 nobj 参数指定对象个数。

下面给出两个例子,

1. 读或写一个二进制数组。例如,将一个浮点数组的第 2 ~ 5 个元素写至一个文件上,

float data[];
if (fwrite(&data[], sizeof(float), , fp) != ) {
printf("fwrite error");
}

2. 读或写一个结构。

struct {
short count;
long total;
char name[NAMESIZE];
} item;
if (fwrite(&item, sizeof(item), , fp) != ) {
printf("fwrite error");
}

5 出错标志

在标准 I/O 流中通常维持了两个标志:

• 出错标志。

• 文件结束标志。

通过前面的几个输入函数的介绍,我们发现在读取输入时,对于达到文件结尾和读取出错,通过返回值无法来判断是哪种情况。为了区分这两个情况,我们可以通过 ferror 或 feof 来进行判断,

#include <stdio.h>
int ferror(FILE *fp);
返回值:如果文件出错标志被设置则返回非0值(真),否则返回0(假)

int feof(FILE *fp);
返回值:如果文件达到结尾则返回非0值(真),否则返回0(假)

调用 clearerr 函数可以清除这两个标志。

#include <stdio.h>
void clearerr(FILE *fp);

6 关闭流

调用 fclose 函数可以关闭一个打开的流。

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

在流被关闭之前,系统会自动冲洗缓冲区中的输出数据,并丢弃缓冲区中的任何输入数据,然后释放缓冲区(如果存在的话)。

当一个进程正常终止时(直接调用 exit 函数,或从 main 函数返回),则所有带未写缓冲数据的标准 I/O 流都会被冲洗,所有被打开的标准 I/O流都会被关闭。

7 定位流

UNIX 系统提供三套定位标准 I/O 流的方法,

1. ftell 和 fseek 函数。

2. ftello 和 fseeko 函数。这两个函数是 Single UNIX Specification 引入的。

3. fgetpos 和 fsetpos 函数。这两个函数是 ISO C 引入的。

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

int fseek(FILE *fp, long offset, int whence);
返回值:若成功则返回0,若出错则返回非0值

void rewind(FILE *fp);

使用 rewind 函数可以将一个流设置到文件的起始位置。对于二进制文件,fseek 定位流时必须指定偏移量 offset,以及解释这个偏移量的方式 whence。whence 的值可以是:SEEK_SET 表示从文件的起始位置开始,SEEK_CUR 表示从当前文件位置开始,SEEK_END 表示从文件的尾端开始。

对于文本文件,fseek 函数的 whence 参数必须是SEEK_SET,而且 offset只能是两种值:0,或是对该文件调用 ftell 所返回的值。因为在文本文件中,文件的当前位置可能不是以简单的字节偏移量来度量的。

#include <stdio.h>
off_t ftello(FILE *fp);
返回值:若成功则返回当前文件位置,若出错则返回-1

int fseeko(FILE *fp, off_t offset, int whence);
返回值:若成功则返回0,若出错则返回非0值

C 标准的两个函数 fgetpos 和 fsetpos,

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

(done)

UNIX环境编程学习笔记(13)——文件I/O之标准I/O流的更多相关文章

  1. UNIX环境编程学习笔记(12)——文件I/O之目录操作

    lienhua342014-09-18 1 引言 在 UNIX 系统中,目录是一种特殊的文件类型.我们可以使用 open 函数来打开目录,获取文件描述符,然后调用 stat 函数来获取目录的属性信息, ...

  2. UNIX环境编程学习笔记(10)——文件I/O之硬链接和符号链接

    lienhua342014-09-15 1 文件系统数据结构 UNIX 文件系统通过 i 节点来存储文件的信息.如图 1 所示为一个磁盘柱面上的 i 节点和数据块示意图.其中 i 节点是一个固定长度的 ...

  3. UNIX环境编程学习笔记(9)——文件I/O之文件访问权限的屏蔽和更改

    lienhua342014-09-10 1 文件访问权限 在文件访问权限和进程访问控制中,我们已经讲述过文件访问权限位,为了方便,我们重新列在下面, 表 1: 文件的 9 个访问权限位  st_mod ...

  4. UNIX环境编程学习笔记(7)——文件I/O之文件访问权限与进程访问控制

    lienhua342014-09-02 1 文件的设置用户 ID位 和设置组 ID位 与进程相关联的 ID 如下表所示, 表 1: 与进程相关联的用户 ID 和组 ID 实际用户 ID 我们实际上是谁 ...

  5. UNIX环境编程学习笔记(6)——文件I/O之判断文件类型

    lienhua342014-09-01 1 文件类型 我们平时最常接触的文件类型有普通文件(regular file)和目录(di-rectory file),但是 UNIX 系统提供了多种文件类型: ...

  6. UNIX环境编程学习笔记(4)——文件I/O之dup复制文件描述符

    lienhua342014-08-23 UNIX 提供了两个函数 dup 和 dup2 用于复制一个现存的文件描述符. #include <unistd.h> int dup(int fi ...

  7. UNIX环境编程学习笔记(3)——文件I/O之内核 I/O 数据结构

    lienhua342014-08-27 内核使用三种数据结构表示打开的文件,分别是文件描述符表.文件表和 V 节点表. (1) 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每 ...

  8. UNIX环境编程学习笔记(2)——文件I/O之不带缓冲的 I/O

    lienhua342014-08-25 1 文件描述符 对于内核而言,所有打开的文件都通过文件描述符引用.文件描述符是一个非负整数.当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符. ...

  9. UNIX环境编程学习笔记(14)——文件I/O之临时文件

    lienhua342014-10-01 ISO C 标准 I/O 库提供了个两个函数 tmpnam 和 tmpfile 以帮助创建临时文件, #include <stdio.h> char ...

随机推荐

  1. 微信web开发者工具同时打开两个小程序项目

    在写小程序时,想要一边参考别人的Demo一边做,但是微信web开发者工具无法同时开两个实例,怎么办? 单个软件实例来回切换打开的项目太麻烦,一种办法是同时下载[微信web开发者工具]和[微信web开发 ...

  2. GitHub限制上传单个大于100M的大文件

    工作中遇到这个问题,一些美术资源..unitypackage文件大于100M,Push到GitHub时被拒绝.意思是Push到GitHub的每个文件的大小都要求小于100M. 搜了一下,很多解决办法只 ...

  3. 关于OpenVR

    一直在期待一种大一统的开放的VR技术规范,虽然短期内这点明显是不太现实的.前几天在翻译Godot的开发进展#6那篇文章时,看到了一个词OpenVR,瞬间有感觉了. 从我的经历的技术规范演进版本来看,从 ...

  4. [uart]linux uart应用层配置

    http://www.raviyp.com/embedded/189-serial-port-programming-in-linux-using-c-working-code

  5. Android ——利用OnDraw实现自定义View(转)

    自定义View的实现方式大概可以分为三种,自绘控件.组合控件.以及继承控件.本文将介绍自绘控件的用法.自绘控件的意思是,这个控件上的内容是用onDraw函数绘制出来的.关于onDraw函数的介绍可参看 ...

  6. [转]TF-IDF与余弦相似性的应用(一):自动提取关键词

    这个标题看上去好像很复杂,其实我要谈的是一个很简单的问题. 有一篇很长的文章,我要用计算机提取它的关键词(Automatic Keyphrase extraction),完全不加以人工干预,请问怎样才 ...

  7. WPF中查找控件的扩展类

    在wpf中查找控件要用到VisualTreeHelper类,但这个类并没有按照名字查找控件的方法,于是搜索网络,整理出下面这个类,感觉用起来很是方便. 贴出来,供大家参考. /// <summa ...

  8. 抓包程序可抓一切数据(破微信oauth2限制) 完整教程

    1.下载fiddler 官网下载或者 https://www.cr173.com/soft/57378.html 2.按图设置 3.重启软件 4.看下自己的网络IP cmd->ipconfig ...

  9. Dapper:安装Dapper时报错

    今天在使用VS 2013安装Dapper的时候报错,具体报错信息如下: 经过网上查找错误原因,发现是安装的Dapper版本过高,.Net Framework版本不支持该版本的Dapper. 解决方案: ...

  10. substance新版及问题

    新版地址:https://github.com/Insubstantial,目前是7.3版 http://stackoverflow.com/questions/3657538/substance-u ...