输入输出函数
ANSI的标准要求编译器必须支持一组规定的函数,具有规范所要求的接口
错误报告
void perror(char const *message)
#include <erno.h> ==> eron变量,在错误发生之后存储错误信息,库函数失败的时候被设置
1 #include <stdio.h>
2 #include <stdlib.h>
3 FILE *input;
4 int main(void)
5 {
6 input = fopen("data3", "r");
7 if(input == NULL)
8 {
9 perror("data3");
10 exit(EXIT_FAILURE);
11 }
12 }
result;
data3: No such file or directory
终止执行
void exit(int status)
status的值返回给操作系统,用于提示程序是否完成,和main函数返回的整形值相同,定义了EXIT_SUCCESS,EXIT_FAILURE分别代表成功失败
经常与perror函数一同使用
标准I/O函数库
旧版函数库实现了对函数的扩展,但是存在两个缺陷,首先是在特定的机器上实现,没有好的移植姓,移植的时候进行修改,会导致函数不再标准
ANSI的进步是对于可移植性和性能的改进,通过增加实现的方式,不影响可移植性
ANSI I/O 函数
包含在
流
当前的计算机具有大量不同的设备,许多都和I/O操作有关,软盘硬盘驱动,网络连接,通信端口,每种设备具有不同的特性和操作协议,操作系统负责不同系统的通讯细节,对于C语言而言,所有的I/O操作都是简单的从程序移入移出字节,这种字节流被称为流
- 流分为两种类型,二进制流、文本流
绝大多数的流是完全缓冲的,实际上意味着是从一块称为缓冲区的区域来回复制数据,只有写满的时候才会把他写入文件设备之中,当输入缓冲区为空的时候才会读取设备文件
使用标准输入输出的时候,缓冲可能会引起混淆,只有当操作系统判定他和交互设备没有联系的时候才会进行完全缓冲
最常用的策略是,提示用户吧输入输出联系在一起,当请求输入的同时刷新缓冲区,把之前输入的信息进行打印
常见的错误就是
printf
的输出被写入到缓冲区不立刻显示到显示器,程序失败的时候不会被实际写入每次写入之后使用
fflush
刷新缓冲区
printf("something or other");
fflush(stdout);
文本流
标准要求文本流最小不小于254字节,文本行的结束不同系统不同,MS-DOS使用回车加换行,UNIX使用的是一个换行符
标准输出流把文本定义为零或多个字符,以一个换行符表示结束,对于那些文本在外的表现方式不同的操作系统库函数进行翻译,在MS-DOS系统中换行符被转换为回车加换行,输入时候丢弃回车,增加可移植性
二进制流
二进制流中的字节将会完全依照程序编写的顺序写入文件设备之中,并且完全根据文件设备的读取写入到程序之中,如果你不希望改变文件行末的字符,也可以用来处理文本文件
文件
stdio.h
声明的FILE结构是一个数据结构,用于访问一个流,如果你同时激活了多个流,每一个流都有和它对应的FILE,为了在流上操作,调用相关的函数,并且传递一个和流相关的FILE参数
每一个程序至少有三个流标准输入stdin
,标准输出stdout
,标准错误stuerr
,都是指向FILE结构的指针,通常是缺省状态下的输入输出,为键盘和屏幕
操作系统通常支持重定向
$ program <data> answer
从文件进行读取,写入到answer文件之中
标准I/O常量
- EOF标志文件到达结尾,实际值比一个字节要大,避免二进制被错误的解释为EOF
- FOPEN_MAX可以打开的文件的最大数量,最少为8
- FILENAME_MAX提示一个字符应该以多大以便容纳编译器支持的最长的合法名字
流I/O总览
文件I/O总览
- 程序为每一个活动的文件声明一个指针变量,类型为FILE *,处于活动状态由流使用
- 流通过
fopen
打开你必须指定需要访问的文件或者设备,以及访问的方式 - 对文件读取写入
fclose
关闭文件,关闭一个流防止再次访问,保证缓冲区的数据正确写入,释放FILE
标准的I/O更简单,不需要打开关闭
基本处理方式:单个字符,文本文件,二进制文件
每种形式都有特定的处理函数
数据类型 | 输入 | 输出 | 描述 |
---|---|---|---|
字符 | getchar |
putchar |
读取(写入)单个字符 |
文本行 | get scanf |
puts printf |
文本行未格式化的输入(输出),格式化的输入输出 |
二进制数据 | fread |
fwrite |
读取(写入)二进制文件 |
- 只用于
stdin、stdout
- 操作作为参数的流使用
- 使用内存中的字符串
输入输出函数家族
家族名 | 目的 | 可用于所有值 | 只用于stdin stdout |
内存中的字符串 |
---|---|---|---|---|
getchar |
字符输入 | fgetc gets |
getchar |
① |
putchar |
字符输出 | fputc putc |
putchar |
① |
gets |
文本行输入 | fgets |
gets |
② |
puts |
文本行输出 | fputs |
puts |
② |
scanf |
格式化输入 | fscanf |
scanf |
sscanf |
printf |
格式化输出 | fprintf |
printf |
sprintf |
① 对使用的下标引用或者间接访问内存
②strcpy
函数从内存中读取文本行
打开流
fopen
打开一个特定的文件,并把一个流和这个文件相关联
FILE *fopen(char const *name, char const *mode)
读取 | 写入 | 添加 | |
---|---|---|---|
文本 | “r” | “w” | “a” |
二进制 | “rb” | “wb” | “ab” |
a+:即可以读也可以写,追加,如果已经读入一些数据要使用
fseek, fsetpos, rewind
等指令进行定位
如果打开成功,就会返回一个FILE指针,失败的话就会返回NULL
FILE *input;
input = fopen("data3", "r");
if(input == NULL)
{
perror("data3");
exit(EXIT_FAILURE);
}
- 打开或者重新打开一份文件流
FILE *freopen(char const *filename, char const *mode ,FILE *stream)
成功返回一个函数的第三个参数
关闭流
int fclose(FILE *f)
- 对于输出流
fclose
关闭文件之前刷新缓冲区,执行成功返回0,否则返回EOF - 文件可能在关闭之前的操作中失败,使得input转换为NULL
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int main(int ac, char **av)
5 {
6 int exit_status = EXIT_SUCCESS;
7 FILE *input;
8 while(*++av != NULL)
9 {
10 input = fopen(*av, "r");
11 if(input == NULL)
12 {
13 perror(*av);
14 exit_status = EXIT_FAILURE;
15 continue;
16 }
17
18 if(fclose(input) != 0)
19 {
20 perror("fclose");
21 exit(EXIT_FAILURE);
22 }
23 }
24 return exit_status;
25 }
字符I/O
一个流被打开之后,他可以用来输入输出,最简单的值字符的输入输出
int fgetc(FILE* stream);
int gets(FILE *stram);//这两个使用没啥区别
int getchar(void);//标准输入流
返回值是一个int是为了返回错误EOF,它在任何可能出现的字符之外,为0xffffffff
int fputc(int character, FILE *stream);
int putc(int character, FILE *stream);
int putchar(int character, FILE *stream);
写入一个字符,失败EOF
字符I/O宏
fgetc fputc
都是真正的函数
getc putc getchar putchar
都是定义的宏
两者结果甚微
撤销字符I/O
在实际读取之前不知道下一个字符,读取之后不希望丢弃这个字符
int ungetc(int character, FILE *stream);
把前一个读取的字符返回流中,可以在之后重新读入。
1 #include <stdio.h>
2 #include <ctype.h>
3
4 int read_init(void){
5 int value;
6 int ch;
7 value = 0;
8 while((ch = getchar()) != EOF && isdigit(ch)){
9 value *= 10;
10 value += ch - '0';
11 }
12 ungetc(ch, stdin);
13 return value;
14 }
15 int main(void)
16 {
17 int value;
18 value = read_init();
19 printf("-------main--------\n");
20 printf("value = %d", value);
21 printf("%c\n", getchar());
22 return 0;
23 }
result
jiao@jiao-virtual-machine:~/桌面/point_and_c/15$ ./a.out
123321
-------main--------
value = 123321
jiao@jiao-virtual-machine:~/桌面/point_and_c/15$ vim main2.c
返回到接收区一个换行符
- 返回的位置和当前的位置有关,使用
fseek, fsetpos, rewind
改变位置退回的字符被丢弃
未格式化的I/O
行I/O可以使用两种方式执行—未格式化的和格式化的,都用于操纵字符串
区别在于未格式化的简单的读取或写入字符串,格式化的执行数字和其他变量的内部外部表示形式的转换
char *fgets(char *buffer, int buffer_size, FILE *stream);
char *gets(char *buffer);
int fputs(char const *buffer, FILE *stream);
int puts(char const *buffer);
fgets
:读取到字符复制到buffer中,当读取到一个换行符,并存储到缓冲区之后就不再读取。读取到buffer_size-1的时候也停止读取,不会出现数据丢失,下一次调用的时候从下一字节开始
如果读取到文件尾返回NULL,否则返回的第一个参数
注:不能使用小于2的缓冲区,会把最后一位转换为NUL字节,让他成为字符串
fputs
:传递给缓冲区一个字符串,预计以NUL字符结尾,如果不包含换行符不会写入,有多个全部写入出现错误返回EOF,否则返回一个非负值
fgets
:一次读取一行 fputs
:写入可以不是一行,写入几行,半行都是可以的
gets和puts和上面几乎相同,但是他们允许向后兼容,gets读取一行不再缓冲区中存储结尾的换行符
puts写入的时候,写完再添加一个换行符
注:gets没有指定缓冲区长度的参数
格式化的I/O
int fscanf(FILE *stream, char const *format, ...)
int scanf(char const *formot, ...)
int sscanf(char const *string, char const *formot, ...)//从字符串之中读取数据
从输入源读取数据,按照formot
格式对字符串进行转换
当格式字符串达到末尾或者读取的输入不再匹配格式字符串指定的类型的时候,输入停止。
返回值是输入的得数目,在任何输入值转换之前文件到达末尾,函数返回常量EOF
- 如果参数不是指定的格式,会导致结果是不能预测的
- 给他传递的参数指针,为了对对用的地址进行更改
scanf格式代码
format的参数
- 空白字符,和输入中的空白匹配
- 格式代吗,指定函数如何解释
- 其他
格式
- 百分号
- 可选的星号:转换后的值丢弃,不进行转换
- 可选的宽度:非负整数,限制读取的字数,没有就直到下一个空白字符
- 可选的限定符:
- 格式代码
scanf的限定符
格式码 | h | l | L |
---|---|---|---|
d, i, n |
short |
long |
|
o, u, x |
unsigned short |
unsigned long |
|
e, f, g |
double |
long double |
字符格式
c |
char * |
读取存储单个字符,不跳过空白字符,如果给出宽度,就读取对应宽度的字符,最后不会添加NUL字节参数指向的数组要足够大 |
---|---|---|
i d |
int * |
一个可选的有符号的整形,d为十进制,i根据第一个字符决定值得基数 |
u o x |
unsigned * |
一个可选的有符号的整形转换,按照无符号数存储,十进制u,八进制o, 十六进制x、X |
e f g |
float * |
一个浮点型,小数点不是必须的 |
s | char * |
一串非空的字符串,必须指向足够大的数组,发现空白停止,自动添加NUL |
[xxx] |
char * |
按照给定的组合进行读取,知道读取到第一个不在组合之众的字符,最后自动添加NUL,使用^补集[^abc] ,[a-z]范围 |
p |
void * |
预期为字符串 |
n |
int * |
到目前为止的读取的参数的数量返回 |
scanf函数把换行当做空白字符跳过
可以使用sscanf对输入的参数进行格式的检测,通过返回值的个数是否正确来判断
void function(char *buffer)
{
int a, b, c;
if(sscanf(buffer, "%d %d %d", &a, &b, &c) != 3){
a = ...;
if(sscanf(buffer, "%d %d", &b, &c) != 2){
b = ...;
if(sscanf(buffer, "%d", &c) != 1){
fprintf(stderr, "Bad input %s", buffer);
exit(EXIT_FAILURE);
}
}
}
}
printf
int fprintf(FILE * stream, char const *format, ...);
int printf(char const *format, ...);
int sprintf(char *buffer, char const *format, ...);
sprintf
可以吧他输出到指定的buffer中,并添加一个NUL
c |
int |
参数被剪裁为unsigned int 进行打印 |
---|---|---|
i d |
int |
十进制打印,指定位数不足用0填充 |
u o x X |
unsigned int |
一个无符号的值,按照无符号数存储,十进制u,八进制o, 十六进制x打印出小写、X打印大写 |
e E |
double |
参数按照指数的方式打印,参数决定小数点之后的位数 |
f |
double |
浮点格式打印,精度决定小数点之后,缺省值为6 |
g G |
double | 按照f或者e的格式打印,指数大于等于-4但是小于精度字段就使用f |
s | char * |
字符串 |
p |
void * |
预期为字符串 |
n |
int * |
到目前为止的读取的参数返回 |
格式标志
标志 | 含义 |
---|---|
- | 中左对齐,没有中右对齐 |
0 | 为右对齐的时候,缺省使用空格填充左侧未使用的值,这个标志用0来填充可用于d,i,u,o,x,X,e,E,f,g,G.其中d,i,u,o,x,X出现精度被忽略 |
+ | 显示符号 |
空格 | 转换有符号数,非负时候加一个空格到开头 |
# | 选择某些代码的另一种形式 |
- 如果宽度用一个*代替,那么由下一个参数决定
- 参数在传递的时候短整型转化为整形
修改代码
修改符 | 用于…的时候 | 参数的表示 |
---|---|---|
h | d, i, u, o, x, X |
short整形 |
h | n |
指向short的指针 |
l | d, i, u, o, x, X | long整形 |
l | n |
指向long的指针 |
L | e, E, f, g, G |
long double整形 |
printf其他格式(和#连用)
用于 | #标志 |
---|---|
o | 保证产生的值以0开头 |
x, X | 非零值前加0x 或0X |
e, E, f | 结果有小数点 |
g, G | 同上,尾部的0不去除 |
二进制I/O
效率更高
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);
buffer:保存数据的内存位置的指针
size:缓冲区每一个元素的字节数
count:读取写入的字节数
stream:流
实际读取的数量有可能会少于请求的元素
可以使用一个或者多个数组、数列组成的数列进行接收
刷新定位函数
int fflush(FILE *stream);
立刻迫使输出流缓冲区的数据进行物理写入,而不是直到以后才进行打印
正常情况下文件的写入是按照顺序来进行的,C语言支持随机访问,通过在访问之前定位实现
long ftell(FILE *stream);//返回当前的位置,距离文件开头的偏移量
int fseek(FILE *stream, long offset, int from)//定位
ftell
:在二进制的时候表示的是距离开头得字节数,在文本流的时候不一定是字节数,根据不同的系统进行转换,但是可以作用于fseek之中
fseek
:第一个参数==>流 第二个参数==>定位的字节 第三个参数==>定位的基地址
- 定位到起始位置之前是非法的,定位到文件尾写入扩展文件,读取获得到达文件尾
- 在二进制的时候文件尾定位是不可取的
- 文本流从SEEK_CUR或SEEK_END开始的offset必须是零,SEEK_SET开始的必须是
ftell
返回的值
如果是from… | 你得到的定位 |
---|---|
SEEK_SET | 从流的起始位置的offest字节,非负数 |
SEEK_CUR | 从当前位置开始的offset字节 |
SEEK_END | 从流的末尾开始,可正可负 |
其他的函数
void rewind(FILE *stream);//回到起始位置
int fgetpos(FILE *stream, fpos_t *position);//把文件当前位置存储到指定的位置
int fgetpos(FILE *stream, fpos_t const *position);//把文件指定为这的位置的地址
改变缓冲的格式
void setbuf(FILE *stream, char *buf);
int setvbuf(FILE *stream, char *buf, int mode, size_t size)
setbuf:设置一块新的缓冲区,大小BUFSIZ,防止分配动态的,NULL为第二个参数,关闭所有缓存
setvbuf:mode设置缓冲的模式, _IOFBF
完全缓冲 _IONBF
不缓冲 _IOLBF
行缓冲,最好不要更改别的
流错误函数
int feof(FILE *stream);//是不是在文件未
int ferror(FILE *stream);//报告流错误状态,有的话为True
void clearerr(FILE *stream);//错误标志重置
临时文件
使用文件保存数据,程序结束删除
FILE *tmpfile(void);
创建一个文件,程序结束的时候删除,以wb+的格式打开
如果文件要用其他方式打开或其他程序访问,就用fopen,最后remove函数
临时文件的文件名:char *tmpnam(char *name);
,参数是NULL的时候返回一个指向静态数组的指针
文件操纵函数
int remove(char const *filename);
int rename(char const *oldname, char const *newname);
成功的话返回0