概述
C语言定义了一组高级输入输出函数,即标准I/O函数,是UNIX I/O的较高级别的替代。这个库提供了打开和关闭文件的含(fopen和fclose)、读和写字节的函数(fread和fwrite)、读和写字符串的函数(fgets和fputs)以及复杂的格式化IO函数(scanf和printf)。
标准IO库将一个打开的文件模型化为一个流,一个流就是一个指向FILE类型的结构的指针。每个ANSI C程序开始时都有三个打开的流 stdin,stdout,stderr
分别对应于标准输入、标准输出和标准错误。C系统在处理文件(文本文件和二进制文件)时,并不区分类型,都看成是字符流,按字节进行处理。当一个进程正常终止时(直接调用exit(),或从main返回)所有打开的标准I/O流都会被关闭,所有带未写缓冲数据的I/O流都会被冲洗。
为什么要定义标准IO函数呢?原因之一在于UNIX I/O函数(open,write,read,close)的开销比较大,我们都知道它是直接与文件打交道的,也就是内存与磁盘的交互,肯定要比内存内缓冲区之间的交互要费时的多。标准I/O就基于UNIX I/O并且完美的解决了这一缺点。比如在使用getc函数,每次调用返回文件的下一个字符。当第一次调用getc时,库通过一次调用read函数将流缓冲区填充,然后缓冲区中的第一个字节返回给应用程序。只要缓冲区中还有未读的字节,接下来对getc的调用就能直接从流缓冲区得到服务。
标准I/O函数
fopen
1 |
|
mode有以下几种选项
字符串 | 说明 |
---|---|
r | 以只读方式打开文件,该文件必须存在。 |
r+ | 以读/写方式打开文件,该文件必须存在。 |
rb+ | 以读/写方式打开一个二进制文件,只允许读/写数据。 |
rt+ | 以读/写方式打开一个文本文件,允许读和写。 |
w | 打开只写文件,若文件存在则长度清为 0,即该文件内容消失,若不存在则创建该文件。 |
w+ | 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 |
a | 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF 符保留)。 |
a+ | 以附加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的 EOF 符不保留)。 |
wb | 以只写方式打开或新建一个二进制文件,只允许写数据。 |
wb+ | 以读/写方式打开或建立一个二进制文件,允许读和写。 |
wt+ | 以读/写方式打开或建立一个文本文件,允许读写。 |
at+ | 以读/写方式打开一个文本文件,允许读或在文本末追加数据。 |
ab+ | 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。 |
上述的形态字符串都可以再加一个 b 字符,如 rb、w+b 或 ab+ 等组合,加入 b 字符用来告诉函数库以二进制模式打开文件。如果不加 b,表示默认加了 t,即 rt、wt,其中 t 表示以文本模式打开文件。
freopen
1 | FILE *freopen( const char *filename, const char *mode, FILE *stream ); |
举个例子,将in.txt的内容使用重定向全部输出到out.txt中:1
2
3
4
5
6
7
8
9
10
11
12
13
int main()
{
int a, b;
freopen("in.txt","r",stdin);
/* 如果in.txt不在连接后的exe的目录,需要指定路径*/
freopen("out.txt","w",stdout); /*同上*/
while (scanf("%d%d", &a, &b) != EOF)
printf("%d\n",a+b);
fclose(stdin);
fclose(stdout);
return 0;
}
getc & fgetc
1 | int getc(FILE *stream); |
有没有注意到它返回一个int类型,而不用char,或者unsigned char,每次读取一个字节的内容,为什么要用一个四个字节的变量来存储呢。原因在于当读取异常或者读取到文件结尾时,会返回EOF(-1),所以unsigned char肯定不行,那么按理说char总行了吧。在计算机内部,所有的数据都是用补码来存储的,-1的补码是0xffffffff,当用char保存返回值时,如果读取到0xff(这也代表一个字符,但不是EOF),然后再和EOF比较时,一个字节的0xff会被转换为4字节的0xffffffff,判断为相等,提前退出。而当用4字节的int保存时,如果读到0xff,转换后也是0x000000ff,并不相等。
用一个例子验证下:
这两个函数的功能其实是一样一样的,区别在于一个是宏定义实现的,一个是函数定义实现的,fgetc的f就表示function的意思。基于这个原因,其实在使用时还有有一点区别的:
- getc的参数不应当是具有副作用的表达式。(宏定义的鸟性大家都懂)
- 因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数。
- 调用fgetc所需时间很可能长于调用getc,因为调用函数通常所需的时间长于调用宏。(因为调用函数会涉及到压栈和出栈)
putc & fputc
1 | int putc(int ch, FILE *fp); |
返回类型的原因同上,put与fput的区别也同上。
getchar & getch
1 |
|
getchar的功能就像getc(stdin),当键盘输入字符时,它们被存储在缓冲区中,当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。返回值是用户输入的字符的ASIC码,如出错返回-1,且将用户输入的字符回显到屏幕。为什么是int与上述原因相同。getch与getchar的不同之处在于,getch不带缓冲区,也就是说,不需要按下回车,每键入一个字符他就立马读取。
putchar
1 |
|
功能就像putc(ch,stdout);
gets (不会读取到回车)
1 | char *gets ( char *buffer); |
注意:由于其可以无限读取,不会判断上限,不会检查是否溢出,所以buffer的大小异常关键,如果读取的字符串长度大于buffer的大小,会覆盖后面的内容,造成内存泄漏。
fgets (会读取到回车)
1 | char *fgets(char *buf, int buffsize, FILE *stream); |
puts
1 | int puts(const char *string); |
fputs
1 | int fputs(const char *str, FILE *stream); |
参考资料:
《深入理解计算机系统 3th》(CSAPP)
百度百科 fopen
百度百科 freopen
C++ reference getc
fgetc的返回值
百度百科 getchar
百度百科 putchar
百度百科 putc&fputc
百度百科 gets
百度百科 fgets
百度百科 fputs