跳转至

第二十二章 输入 输出


流意味着任意输入的源或任意输出的目的地。输入流通常和键盘相关,输出流通常和屏幕相关。

流还可以表示为磁盘上的文件,以及其他设备。

文件指针

流的访问是通过 文件指针(file pointer) 实现的。此指针的类型为FILE*

stdio.h提供了3种标准流,这三个标准流是备用的,不能声明、打开、关闭它们。

文件指针 默认的含义
stdin 标准输入 键盘
stdout 标准输出 屏幕
stderr 标准错误 屏幕

重定向(redirection)

操作系统允许通过重定向机制来改变标准流默认的含义。

例如:

demo < in.data

称为输入重定向(input redirection),即把stdin流表示为文件in.dat,而非键盘。对于程序demo而言,它并不知道输入流是来自键盘还是文件。

这样子是输出重定向(output redirection)

demo > out.dat

如此一来,写入stdout的内容将不再输出到屏幕,而是文件out.dat。

文本文件与二进制文件

文件就是字节的序列。

文本文件中,字节表示字符。

二进制文件中,字节就是字节,可以用于表示任意类型的数据。

DOS系统中,这两种文件之间有如下差异:

  • 行的结尾。文本文件写入换行符时,换行符扩展成一对字符,即回行符和跟随的回车符。如果把换行符写入二进制文件时,它就是一个单独的字符(换行符)。

  • 文件末尾。文本文件中,文件的结束标记是CTRL+Z字符(\x1a)。二进制文件中,此字符没有特别的含义,跟其它任何字符一样。

在Unix操作系统中,二进制文件和文本文件不进行区分,其存储方式一样。

文件操作

打开文件

使用 fopen 函数。

关闭文件

使用 fclose 函数。

从命令行获取文件名

当程序需要打开一个文件时,通常通过命令行参数把文件名传给程序,这样更具灵活性。

主函数:

int main(int argc, char *argv[]);

argc是命令行实际参数的数量(非数组长度),argv是参数字符串数组。

argv[0]是程序名,argv[1] ~ argv[argc-1]是剩余参数。

argv[argc]是空指针。

临时文件

tmpfile 函数生成临时文件。

tmpnam 函数生成一个临时的文件名。

文件缓冲

向磁盘直接读写数据相对比内存读写慢。使用缓冲区(buffer)来解决这个问题。写入流的数据首先放到缓冲区里面,当缓冲区满了(或关闭流)时,刷新缓冲区,把数据写入文件。

输入流可以使用类似的方法进行缓冲:缓冲区包含来自输入设备的数据。

使用 fflush 函数刷新缓冲区。

其它文件操作

remove 函数删除文件,rename 函数重命名文件。如果是用 fopen 和 tmpnam 产生的临时文件,可以使用 remove 把它删除,或者用 rename 使其成为永久文件。

格式化的输入与输出

即 ...printf 类函数 和 ...scanf 类函数的使用。

检测文件末尾和错误条件

每个流都有与之相关的两个指示器:错误指示器(error indicator),文件末尾指示器(end-of-file indicator)。

打开流时,会清除这些指示器;流上的操作失败时会设置某个指示器。

遇到文件末尾就设置文件末尾指示器,遇到错误就设置错误指示器。

一旦设置了指示器,它就会保持这种状态,直到可能由 clearerr 调用而引发的明确清除操作为止。 clearerr 可以清除文件末尾指示器和错误指示器。

如果设置了文件末尾指示器, feof 返回非零值。

如果设置了错误指示器, ferror 返回非零值。

字符的输入/输出

输入输出的字符类型应使用int,原因之一是由于函数通过返回EOF说明文件末尾or错误情况,EOF是一个负的整型常量。

输出函数

int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);

输入函数

int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);

行的输入/输出

输出函数

int fputs(const char *s, FILE *stream);
int puts(const char *s);

puts函数向标准输出输出一行字符串,会自动添加一个换行符。

fputs不会自动添加换行符。

输入函数

char *fgets(char *s, int size, FILE *stream);
char *gets(char *s);

gets函数逐个读取字符,存储到s中,直到读到换行符时停止,并把换行符丢弃。

fgets当读入了size-1个字符时或读到换行符时停止,且会存储换行符。

如果出现错误,或者在存储任何字符之前达到了输入流的末尾,函数返回空指针。否则返回第一个实参。

函数会在字符串的末尾存储空字符。

块的输入输出

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);

fread函数和fwrite函数允许程序在单步中读写大的数据块。

fwrite函数被设计用来把数组复制给流。第一个参数是数组首元素的地址,第二个参数是每个数组元素的大小(以字节为单位),第三个参数是要写的元素的数量,第四个参数是文件指针,说明了要写的数据位置。

fwrite返回实际写入的元素数量,如果写入错误,此数就会小于第三个参数。

fread函数从流读入数组的元素。其参数类似fwrite。

fread返回实际读入的元素数量,此数应该等于第三个参数。否则可能达到了文件末尾或者出现了错误。使用feof和ferror确定出问题的原因。

检查fread的返回值是非常重要的。

文件的定位

每个流都有相关联的文件位置(file position)。打开文件时,根据模式可以在文件的起始处或者末尾处设置文件位置。

在执行读或写操作时,文件位置会自动推进。

stdio.h提供了一些函数,用于确定当前的文件位置或者改变文件位置:

int fseek(FILE *stream, long offset, int whence);

long ftell(FILE *stream);

void rewind(FILE *stream);

int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, fpos_t *pos);

fseek函数改变第一个参数相关的文件的位置。第二个参数说明新位置是根据文件的起始处、当前位置还是文件末尾来计算,也就是第三个参数来计算。

第三个参数可取值为:

  • SEEK_SET,文件的起始处。

  • SEEK_CUR,文件的当前位置。

  • SEEK_END,文件的末尾处。

ftell函数返回当前文件位置。如果发生错误,ftell返回-1L,并且把错误码存储到errno。

rewind函数会把文件位置设置到起始处。rewind还会为fp清除错误指示器。

fgetposfsetpos用于处理大的文件,使用fpos_t表示文件位置,它可能是一个结构。函数成功返回0,失败返回非0值并把错误码存放到errno中。

字符串的输入/输出

sprintf和snprintf函数将按写到数据流一样的方式写字符到字符串。

sscanf函数从字符串中读出数据就像从数据流中读数据一样。

输出函数

int sprintf(char *str, const char *format, ...);
int snprintf(char *str, size_t size, const char *format, ...);

类似于printf函数,唯一不同是sprintf函数把输出写入字符数组而不是流。当完成向字符串写入的时候,sprintf函数会添加一个空字符,并返回所存储的字符数量(不计空字符)。如果遇到错误,返回负值。

snprintf写入的字符数量不会超过size-1,结尾空字符不计。只要size不是0,都会有空字符。

输入函数

int sscanf(const char *str, const char *format, ...);

sscanf与scanf类似,唯一的不同就是sscanf从字符数组中读取数据而不是流。

sscanf函数返回成功读入并存储的数据项的数量,如果在找到第一个数据项之前到达了字符串的末尾,那么sscan函数返回EOF。