UNIX环境编程学习笔记(13)——文件I/O之标准I/O流

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[10];
if (fwrite(&data[2], sizeof(float), 4, fp) != 4) {
    printf("fwrite error");
}

2. 读或写一个结构。

struct {
    short count;
    long total;
    char name[NAMESIZE];
} item;
if (fwrite(&item, sizeof(item), 1, fp) != 1) {
    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)

时间: 09-30

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

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

lienhua342014-09-15 1 文件系统数据结构 UNIX 文件系统通过 i 节点来存储文件的信息.如图 1 所示为一个磁盘柱面上的 i 节点和数据块示意图.其中 i 节点是一个固定长度的记录项,它包含了有关文件的大部分信息.数据块用于存储文件的实际内容.每个文件的 i 节点会记录该文件的内容所占用的数据块信息. 图 1: i 节点和数据块 图 1 中还有一些信息需要进行说明: 1. 每个目录项只存储了文件的文件名和 i 节点编号(每个文件系统各自对它们的 i 节点进行编号).文件的

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

lienhua342014-09-01 1 文件类型 我们平时最常接触的文件类型有普通文件(regular file)和目录(di-rectory file),但是 UNIX 系统提供了多种文件类型: (1) 普通文件(regular file) 这种文件包含了某种形式的数据,这些数据无论是文件还是二进制对于 UNIX 内核而言都是一样的.对普通文件内容的解释有处理该文件的应用程序进行. (2) 目录文件(directory file) 目录文件包含了其他文件的名字以及指向与这些文件有关信息的指

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

lienhua342014-08-23 UNIX 提供了两个函数 dup 和 dup2 用于复制一个现存的文件描述符. #include <unistd.h> int dup(int filedes); int dup2(int filedes, int filedes2); 返回值:若成功则返回新的文件描述符,如出错则返回-1. 由 dup 函数返回的文件描述符一定是当前可用文件描述符中的最小描述符.用 dup2 函数则可以通过参数 filedes2 指定目标文件描述符.如果filedes2

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

lienhua342014-09-10 1 文件访问权限 在文件访问权限和进程访问控制中,我们已经讲述过文件访问权限位,为了方便,我们重新列在下面, 表 1: 文件的 9 个访问权限位  st_mode 屏蔽  意义  S_IRUSR  用户 -读  S_IWUSR  用户 -写  S_IXUSR  用户 -执行  S_IRGRP   组 -读  S_IWGRP  组 -写  S_IXGRP  组 -执行  S_IROTH  其他 -读  S_IWOTH  其他 -写  S_IXOTH  其他

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

lienhua342014-09-02 1 文件的设置用户 ID位 和设置组 ID位 与进程相关联的 ID 如下表所示, 表 1: 与进程相关联的用户 ID 和组 ID 实际用户 ID 我们实际上是谁 实际组 ID 有效用户 ID 用于文件访问权限检查 有效组 ID 附加组 ID 保存的设置用户 ID 由 exec 函数保存 保存的设置组 ID 保存的设置用户 ID 和保存的设置组 ID 在执行一个程序时包含了有效用户 ID 和有效组 ID 的副本,这个后面我们学习到进程时在详细学习. 此处,我

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

lienhua342014-08-27 内核使用三种数据结构表示打开的文件,分别是文件描述符表.文件表和 V 节点表. (1) 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每个描述符占用一项.与每个文件描述符相关联的是: (a) 文件描述符标志. (b) 指向一个文件表项的指针. (2) 内核为所有打开文件维持一张文件表.每个文件表项包含: (a) 文件状态标志(读.写.添写.同步和非阻塞等). (b) 当前文件偏移量. (c) 指向该文件 V 节点表项的指针. (3)

UNIX环境编程学习笔记(11)——文件I/O之文件时间以及 utime 函数

lienhua342014-09-16 1 文件的时间 每个文件都有三个时间字段,如表 1 所示. 表 1: 文件的三个时间字段 说明 字段 st_atime 文件数据的最后访问时间 st_mtime 文件数据的最后修改时间 st_ctime i 节点状态的最后更改时间 最后修改时间是文件内容最后一次被修改的时间.更改状态时间是该文件的 i 节点最后一次被修改的时间. 2 utime 函数 utime 函数提供了对文件最后访问时间和最后修改时间的更改功能. #include <utime.h>

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

lienhua342014-09-18 1 引言 在 UNIX 系统中,目录是一种特殊的文件类型.我们可以使用 open 函数来打开目录,获取文件描述符,然后调用 stat 函数来获取目录的属性信息,但是我们却不能够使用 read 函数来读取目录内容.例如,下面例子所示, #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <fcn

UNIX环境编程学习笔记(8)——文件I/O之校验当前登录用户对文件的访问权限

lienhua342014-09-03 通过前面一篇随笔(文件访问权限与进程访问控制),我们知道内核校验文件的访问权限使用的是进程的有效用户 ID 和有效组 ID.但有时我们需要知道当前登录用户对某个文件访问权限.虽然说进程的有效用户 ID 和有效组 ID 通常分别等于当前登录用户 ID 和用户所在组 ID.例如,一个进程可能因设置用户 ID 以另一个用户权限运行,它仍可能想验证当前实际登录的用户是否能否访问一个给定的文件. access 函数提供了按照实际用户 ID 和实际组 ID 进行访问权