第十五章:输入/输出函数
ANSI C 和早期C相比的最大优点之一就是它在规范里所包含的函数库。每个ANSI编译器必须支持一组规定的函数,并具备规范所要求的接口,而且按照规范的行为工作
ANSI编译器并未被禁止在他们的函数库的基础上增加其他函数。但是,标准函数必须标准所定义的方式执行。
如果你关心可移植性,只要避免使用任何非标准函数就可以。
15.1 错误报告
perror 函数以一种简单的统一的方式报告错误。
stdio.h
void perror(char const *message);
外部整型变量 errno errno.h
提示
良好的编程习惯要求任何可能产生错误的操作都应该在执行之后进行检查。
只有当一个库函数失败时,errno 才会被设置。当函数成功运行时,errno 的值不会被修改。
所以,只有当被调用的函数提示有错误发生时,检查 errno 的值才有意义。
15.2 终止执行
stdlib.h
void exit(int status);
status 参数返回给操作系统,用于提示程序是否完成。这个值和main 函数返回的整型状态值相同。
预定义符号 EXIT_SUCCESS EXIT_FAILURE 分别提示程序的终止是成功还是失败。
你经常会在调用 perrno 之后再调用 exit 终止程序。
15.3 标准 I/O 函数库
流
ANSI C 进一步对 I/O 的概念进行了抽象。就 C 程序而言,所有的I/O操作只是简单的从程序移进或移出字节的事情。
因此,毫不惊奇的是,这种字节流被成为流。
绝大多数流是完全缓冲的,这意味着“读取”和“写入”实际上是从一块被成为缓冲区的内存区域来回复制数据。从内存中来回复制数据是非常快速的。
用于输出流的缓冲区只有当它写满时才会被刷新 flush 到设备或文件中。一次性把写满的缓冲区写入和逐片把程序产生的输出分别写入相比效率更高。
使用标准输入输出时,这种缓冲可能会引起混淆。所以只有当操作系统可以断定他们与交互设备没有联系时,才会进行完全缓冲。
一个常见的策略是把标准输入和输出联系在一起,就是当请求输入是同时刷新输出缓冲区。
警告:
一个常见的调试策略是把一些 printf 函数的调用散布于程序中,确定错误出现的具体位置。但是,这些函数调用的输出结果被写入到缓冲区中,
并不立即显示于屏幕上。事实上,如果程序失败,缓冲区可能不会被实际写入,这就可能使程序员得到关于错误出现位置的不正确结论。
解决方法:
printf("something or other");
fflush(stdout);
文件
stdio.h 声明了 FILE 结构。FILE是一个数据结构,用于访问一个流。
对于每个ANSI C 程序,运行时系统必须提供至少三个流 -- 标准输出stdout, 标准输入stdin, 标准错误stderr。
他们都是指向FILE结构的指针。
许多操作系统支持输入/输出 重定向
$ program < data > answer
标准错误就是错误信息写入的地方。perror 函数它的输出也写入到这个地方。在许多系统中,标准错误和标准输出在缺省情况下是相同的。
标准 I/O 常量
EOF
FOPEN_MAX > 8
FILENAME_MAX
流 I/O 总览
I/O 函数以三种基本形式进行处理:单个字符,文本行和二进制数据。
数据类型 输入 输出 描述
字符 getchar putchar 读取(写入)单个字符
文本行 gets puts 文本行未格式化的输入/输出
scanf printf 格式化
二进制数据 fread fwrite 读取/写入二进制数据
输入输出家族
getchar fgetc getc
putchar fputc putc
gets fgets
puts fputs
scanf fscanf sscanf
printf fprintf sprintf
15.6 打开流
FILE *input;
input = fopen("data3", "r");
if(input == NULL){
perror("data3");
exit(EXIT_FAILTURE);
}
如果失败,输出大概是 data3: No such file or directory
freopen 重新打开
15.7 关闭流
int fclose(FILE *f);
if(fclose(input) != 0){
perror("fclose");
exit(EXIT_FAILTURE);
}
15.10 格式化的I/O
scanf
int fscanf(FILE *stream, char const *format, ...); 输入源是流
int scant(char const *format, ...); 输入源是标准输入
int sscanf(char const *string, char const *format, ...); 输入源是字符串
% 后面可以是 一个可选的星号,一个可选的宽度,一个可选的限定符,格式代码。
星号使转换后的值被丢弃而不是进行存储。这个技术可以用于跳过不需要的输入字符。
宽度是一个非负整数,限制用于转换的输入的字符的个数。
int a, b;
while(fscanf(input, "%d %d", &a, &b) == 2){
/*
** Process the values a and b.
*/
}
nfields = fscanf(input, "%4d %4d %4d", &a, &b, &c);
输入 1 2
a=1, b=2, c的值没有改变
输入 12345 67890 nfields=2
a=1234 b=5 c=6789 nfields=3
/*
** 用sscanf处理行定向(line-oriented)的输入
*/
#include <stdio.h>
#define BUFFER_SIZE 100 /* 我们将要处理的最长行 */
void
function(FILE *input)
{
int a, b, c, d, e;
char buffer[BUFFER_SIZE];
while(fgets(buffer, BUFFER_SIZE, input) != NULL){
if(sscanf(buffer, "%d %d %d %d %d %d", &a, &b, &c, &d, &e) != 4){
fprintf(stderr, "Bad input skipped: %s", buffer);
continue;
}
/* 处理输入 */
}
}
/*
** 使用sscanf处理可变格式的输入
*/
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_A 1 /* 或其他 ... */
#dfiine DEFAULT_B 2
void
function(char *buffer)
{
int a, b, c;
/*
** 看看三个值是否都已给出
*/
if(sscanf(buffer, "%d %d %d", &a, &b, &c) != 3){
a = DEFAULT_A;
if(sscanf(buffer, "%d %d", &b, &c) != 2){
b = DEFAULT_B;
if(sscanf(buffer, "%d", &c) != 1){
fprintf(stderr, "Bad input: %s", buffer);
exit(EXIT_FAILTURE);
}
}
}
/*
** 处理a, b, c
*/
}
5.11 二进制I/O
把数据写入到文件效率最高的方法是用二进制形式写入。二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度损失。
fread fwrite 用于读取和写入二进制数据
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
代码段
struct VALUE {
long a;
float b;
char c[SIZE];
}values[ARRAY_SIZE];
...
n_values = fread(values, sizeof(struct VALUE), ARRAY_SIZE, input_stream);
(处理数组中的数据)
fwrite(values, sizeof(sruct VALUE), n_values, output_stream);
15.12 刷新和定位函数
int fflush(FILE *stream);
迫使一个输出流的缓冲区内的数据进行物理写入,不管它是否写满。
在正常情况下,数据以线性的方式写入。C同时支持随机访问I/O,也就是以任意顺序访问文件的不同位置。
long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int from);
from 参数
SEEK_SET offset 非负值
SEEK_CUR offset 正负
SEEK_END offset 正负
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *position);
int fsetpos(FILE *stream, fpos_t const *position);
15.13 改变缓冲方式
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
setbuf 设置了另一个数组,对流进行缓冲。这个数组的字符长度必须是BUFSIZ(定义在stdio.h)。
为一个流自行指定一个缓冲区可以防止I/O函数库为他动态分配一个缓冲区。如果用一个NULL参数调用这个函数,setbuf函数将关闭流的所有缓冲方式。
警告
为流缓冲区使用一个自动数组是很危险的。
setvbuf 函数更为通用,
mode 缓冲类型 _IOFBF 完全缓冲的流
_IONBF 不缓冲的流
_IOLBF 行缓冲流
buf size 最好使用BUFSIZ长度的字符数组作为缓冲区。
15.14 流错误函数 判断流的状态
int feof(FILE *stream);
int ferror(FILE *stream);
void clearerr(FILE *stream);
15.15 临时文件
FILE *tempfile(void);
当文件被关闭或者程序终止时,这个文件便被自动删除。
char *temnam(char *name);
15.16 文件操纵函数
int remove(char const *filename);
int rename(char const *oldname, char const *newname);
ANSI C 和早期C相比的最大优点之一就是它在规范里所包含的函数库。每个ANSI编译器必须支持一组规定的函数,并具备规范所要求的接口,而且按照规范的行为工作
ANSI编译器并未被禁止在他们的函数库的基础上增加其他函数。但是,标准函数必须标准所定义的方式执行。
如果你关心可移植性,只要避免使用任何非标准函数就可以。
15.1 错误报告
perror 函数以一种简单的统一的方式报告错误。
stdio.h
void perror(char const *message);
外部整型变量 errno errno.h
提示
良好的编程习惯要求任何可能产生错误的操作都应该在执行之后进行检查。
只有当一个库函数失败时,errno 才会被设置。当函数成功运行时,errno 的值不会被修改。
所以,只有当被调用的函数提示有错误发生时,检查 errno 的值才有意义。
15.2 终止执行
stdlib.h
void exit(int status);
status 参数返回给操作系统,用于提示程序是否完成。这个值和main 函数返回的整型状态值相同。
预定义符号 EXIT_SUCCESS EXIT_FAILURE 分别提示程序的终止是成功还是失败。
你经常会在调用 perrno 之后再调用 exit 终止程序。
15.3 标准 I/O 函数库
流
ANSI C 进一步对 I/O 的概念进行了抽象。就 C 程序而言,所有的I/O操作只是简单的从程序移进或移出字节的事情。
因此,毫不惊奇的是,这种字节流被成为流。
绝大多数流是完全缓冲的,这意味着“读取”和“写入”实际上是从一块被成为缓冲区的内存区域来回复制数据。从内存中来回复制数据是非常快速的。
用于输出流的缓冲区只有当它写满时才会被刷新 flush 到设备或文件中。一次性把写满的缓冲区写入和逐片把程序产生的输出分别写入相比效率更高。
使用标准输入输出时,这种缓冲可能会引起混淆。所以只有当操作系统可以断定他们与交互设备没有联系时,才会进行完全缓冲。
一个常见的策略是把标准输入和输出联系在一起,就是当请求输入是同时刷新输出缓冲区。
警告:
一个常见的调试策略是把一些 printf 函数的调用散布于程序中,确定错误出现的具体位置。但是,这些函数调用的输出结果被写入到缓冲区中,
并不立即显示于屏幕上。事实上,如果程序失败,缓冲区可能不会被实际写入,这就可能使程序员得到关于错误出现位置的不正确结论。
解决方法:
printf("something or other");
fflush(stdout);
文件
stdio.h 声明了 FILE 结构。FILE是一个数据结构,用于访问一个流。
对于每个ANSI C 程序,运行时系统必须提供至少三个流 -- 标准输出stdout, 标准输入stdin, 标准错误stderr。
他们都是指向FILE结构的指针。
许多操作系统支持输入/输出 重定向
$ program < data > answer
标准错误就是错误信息写入的地方。perror 函数它的输出也写入到这个地方。在许多系统中,标准错误和标准输出在缺省情况下是相同的。
标准 I/O 常量
EOF
FOPEN_MAX > 8
FILENAME_MAX
流 I/O 总览
I/O 函数以三种基本形式进行处理:单个字符,文本行和二进制数据。
数据类型 输入 输出 描述
字符 getchar putchar 读取(写入)单个字符
文本行 gets puts 文本行未格式化的输入/输出
scanf printf 格式化
二进制数据 fread fwrite 读取/写入二进制数据
输入输出家族
getchar fgetc getc
putchar fputc putc
gets fgets
puts fputs
scanf fscanf sscanf
printf fprintf sprintf
15.6 打开流
FILE *input;
input = fopen("data3", "r");
if(input == NULL){
perror("data3");
exit(EXIT_FAILTURE);
}
如果失败,输出大概是 data3: No such file or directory
freopen 重新打开
15.7 关闭流
int fclose(FILE *f);
if(fclose(input) != 0){
perror("fclose");
exit(EXIT_FAILTURE);
}
15.10 格式化的I/O
scanf
int fscanf(FILE *stream, char const *format, ...); 输入源是流
int scant(char const *format, ...); 输入源是标准输入
int sscanf(char const *string, char const *format, ...); 输入源是字符串
% 后面可以是 一个可选的星号,一个可选的宽度,一个可选的限定符,格式代码。
星号使转换后的值被丢弃而不是进行存储。这个技术可以用于跳过不需要的输入字符。
宽度是一个非负整数,限制用于转换的输入的字符的个数。
int a, b;
while(fscanf(input, "%d %d", &a, &b) == 2){
/*
** Process the values a and b.
*/
}
nfields = fscanf(input, "%4d %4d %4d", &a, &b, &c);
输入 1 2
a=1, b=2, c的值没有改变
输入 12345 67890 nfields=2
a=1234 b=5 c=6789 nfields=3
/*
** 用sscanf处理行定向(line-oriented)的输入
*/
#include <stdio.h>
#define BUFFER_SIZE 100 /* 我们将要处理的最长行 */
void
function(FILE *input)
{
int a, b, c, d, e;
char buffer[BUFFER_SIZE];
while(fgets(buffer, BUFFER_SIZE, input) != NULL){
if(sscanf(buffer, "%d %d %d %d %d %d", &a, &b, &c, &d, &e) != 4){
fprintf(stderr, "Bad input skipped: %s", buffer);
continue;
}
/* 处理输入 */
}
}
/*
** 使用sscanf处理可变格式的输入
*/
#include <stdio.h>
#include <stdlib.h>
#define DEFAULT_A 1 /* 或其他 ... */
#dfiine DEFAULT_B 2
void
function(char *buffer)
{
int a, b, c;
/*
** 看看三个值是否都已给出
*/
if(sscanf(buffer, "%d %d %d", &a, &b, &c) != 3){
a = DEFAULT_A;
if(sscanf(buffer, "%d %d", &b, &c) != 2){
b = DEFAULT_B;
if(sscanf(buffer, "%d", &c) != 1){
fprintf(stderr, "Bad input: %s", buffer);
exit(EXIT_FAILTURE);
}
}
}
/*
** 处理a, b, c
*/
}
5.11 二进制I/O
把数据写入到文件效率最高的方法是用二进制形式写入。二进制输出避免了在数值转换为字符串过程中所涉及的开销和精度损失。
fread fwrite 用于读取和写入二进制数据
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
size_t fwrite(void *buffer, size_t size, size_t count, FILE *stream);
代码段
struct VALUE {
long a;
float b;
char c[SIZE];
}values[ARRAY_SIZE];
...
n_values = fread(values, sizeof(struct VALUE), ARRAY_SIZE, input_stream);
(处理数组中的数据)
fwrite(values, sizeof(sruct VALUE), n_values, output_stream);
15.12 刷新和定位函数
int fflush(FILE *stream);
迫使一个输出流的缓冲区内的数据进行物理写入,不管它是否写满。
在正常情况下,数据以线性的方式写入。C同时支持随机访问I/O,也就是以任意顺序访问文件的不同位置。
long ftell(FILE *stream);
int fseek(FILE *stream, long offset, int from);
from 参数
SEEK_SET offset 非负值
SEEK_CUR offset 正负
SEEK_END offset 正负
void rewind(FILE *stream);
int fgetpos(FILE *stream, fpos_t *position);
int fsetpos(FILE *stream, fpos_t const *position);
15.13 改变缓冲方式
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
setbuf 设置了另一个数组,对流进行缓冲。这个数组的字符长度必须是BUFSIZ(定义在stdio.h)。
为一个流自行指定一个缓冲区可以防止I/O函数库为他动态分配一个缓冲区。如果用一个NULL参数调用这个函数,setbuf函数将关闭流的所有缓冲方式。
警告
为流缓冲区使用一个自动数组是很危险的。
setvbuf 函数更为通用,
mode 缓冲类型 _IOFBF 完全缓冲的流
_IONBF 不缓冲的流
_IOLBF 行缓冲流
buf size 最好使用BUFSIZ长度的字符数组作为缓冲区。
15.14 流错误函数 判断流的状态
int feof(FILE *stream);
int ferror(FILE *stream);
void clearerr(FILE *stream);
15.15 临时文件
FILE *tempfile(void);
当文件被关闭或者程序终止时,这个文件便被自动删除。
char *temnam(char *name);
15.16 文件操纵函数
int remove(char const *filename);
int rename(char const *oldname, char const *newname);