总结——标准IO函数与UNIX IO函数

概述

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
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>
FILE * fopen(const char * path, const char * mode);
/*
功能:
以某种模式打开一个文件
参数:
path:路径名以及文件名
mode:打开模式
返回值:
文件顺利打开后,指向该流的文件指针就会被返回。
如果文件打开失败则返回 NULL,并把错误代码存在errno中。
*/

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
2
3
4
5
6
7
8
9
10
11
12
FILE *freopen( const char *filename, const char *mode, FILE *stream );
/*
功能:
以指定模式将一个文件流重定向到另一个文件。
参数:
filename:需要重定向到的文件名或文件路径。
mode:代表文件访问权限的字符串。例如,"r"表示“只读访问”、"w"表示“只写访问”、
"a"表示“追加写入”。
stream:需要被重定向的文件流。
返回值:
如果成功,则返回该指向该输出流的文件指针,否则返回为NULL。
*/

举个例子,将in.txt的内容使用重定向全部输出到out.txt中:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
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
2
3
4
5
6
7
8
9
10
int getc(FILE *stream);
int fgetc(FILE *stream);
/*
功能:
从文件流中读取一个字符。
参数:
stream:需要读取的文件流。
返回值:
如果读取失败或者到了文件结束标志返回EOF(-1),否则返回读到的字符。
*/

有没有注意到它返回一个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的意思。基于这个原因,其实在使用时还有有一点区别的:

  1. getc的参数不应当是具有副作用的表达式。(宏定义的鸟性大家都懂)
  2. 因为fgetc一定是一个函数,所以可以得到其地址。这就允许将fgetc的地址作为一个参数传送给另一个函数。
  3. 调用fgetc所需时间很可能长于调用getc,因为调用函数通常所需的时间长于调用宏。(因为调用函数会涉及到压栈和出栈)

putc & fputc

1
2
3
4
5
6
7
8
9
10
11
int putc(int ch, FILE *fp);
int fputc(int ch,FILE*fp);
/*
功能:
将一个字符写入到指定的流的当前位置,成功后位置后移一位
参数:
ch:指定要写入的字符。
fp:指定的流。
返回值:
如果写入成功,返回写入的字符的ASCII码值,如果写入错误,返回EOF
*/

返回类型的原因同上,put与fput的区别也同上。

getchar & getch

1
2
3
4
5
6
#include<stdio.h>
int getchar(void);
/*
功能:
从标准输入中读取一个字符
*/

getchar的功能就像getc(stdin),当键盘输入字符时,它们被存储在缓冲区中,当用户键入回车之后,getchar才开始从stdio流中每次读入一个字符。返回值是用户输入的字符的ASIC码,如出错返回-1,且将用户输入的字符回显到屏幕。为什么是int与上述原因相同。getch与getchar的不同之处在于,getch不带缓冲区,也就是说,不需要按下回车,每键入一个字符他就立马读取。

putchar

1
2
3
4
5
6
7
8
9
#include<stdio.h>
int putchar(int ch);
/*
功能:
将指定的表达式的值所对应的字符输出到标准输出终端上。
参数:
ch可以是一个用单引号括起来的字符,
也可以是一个0~127之间的整数,也可以是一个事先用char定义好的字符变量。
*/

功能就像putc(ch,stdout);

gets (不会读取到回车)

1
2
3
4
5
6
7
8
9
10
11
char *gets ( char *buffer);
/*
功能:
从stdio流中读取字符串,直至接受到换行符或EOF时停止,
并将读取的结果存放在buffer指针所指向的字符数组中。
换行符不作为读取串的内容,读取的换行符被转换为‘\0’空字符,并由此来结束字符串。
参数:
buffer:声明的一个用于存放输入的字符串的一个字符数组。
返回值:
读入成功,返回与参数buffer相同的指针;读入过程中遇到EOF或发生错误,返回NULL指针。
*/

注意:由于其可以无限读取,不会判断上限,不会检查是否溢出,所以buffer的大小异常关键,如果读取的字符串长度大于buffer的大小,会覆盖后面的内容,造成内存泄漏。

fgets (会读取到回车)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
char *fgets(char *buf, int buffsize, FILE *stream);
/*
功能:
从stream流中最多读取buffsize-1个字符(有一个用来放'\0'),
存放到buf字符数组中。或者说,每次要么读一行,要么读buffsize-1个字符,
这取决于行长度
参数:
*buf: 字符型指针,指向用来存储所得数据的地址。
bufsize: 整型数据,指明存储数据的大小。
*stream: 文件结构体指针,将要读取的文件流。
返回值:
成功将返回buf指针,失败或读到文件结尾返回NULL。

*/

puts

1
2
3
4
5
int puts(const char *string);
/*
功能:
把字符串输出到标准输出设备,将'\0'转换为回车换行
*/

fputs

1
2
3
4
5
6
7
int fputs(const char *str, FILE *stream);
/*
功能:
把字符串str的内容写入到指定的流( stream) 中,但不包括空字符\0。
返回值:
该函数返回一个非负值,如果发生错误则返回 EOF(-1)。
*/

参考资料:
《深入理解计算机系统 3th》(CSAPP)
百度百科 fopen
百度百科 freopen
C++ reference getc
fgetc的返回值
百度百科 getchar
百度百科 putchar
百度百科 putc&fputc
百度百科 gets
百度百科 fgets
百度百科 fputs