第二十二章 输入 输出
流
流意味着任意输入的源或任意输出的目的地。输入流通常和键盘相关,输出流通常和屏幕相关。
流还可以表示为磁盘上的文件,以及其他设备。
文件指针
流的访问是通过 文件指针(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清除错误指示器。
fgetpos和fsetpos用于处理大的文件,使用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。