本章讲讲述标准库,介绍输出/输出、字符串处理、存储管理与数学函数以及其他一些服务的函数。本章的重点将放在输入/输出上。
7.1 标准输入/输出如第1章所述,标准库实现了简单的文本输入/输出模型。文本流由一系列行组成,每一行以一个换行符结尾。如果系统没有遵循这种模型,则标准库会通过一些措施使其看起来遵循了这种模型。例如,标准库可能在输入时将回车符(carriage return, CR, r)和换行符(line feed, LF, n)都转换为换行符,而在输出时进行反向转换。
注:不同操作系统的默认换行符是不同的,Windows是CRLF (rn),Unix/Linux是LF (n),macOS是CR (r),参考:
- Carriage Return, Line Feed and New Line
- The Great Newline Schism
最简单的输入机制是使用getchar函数从标准输入(一般为键盘)一次读取一个字符。getchar函数在每次被调用时返回下一个输入字符,如果遇到文件结尾则返回EOF。符号常量EOF定义在
函数putchar(c)将字符c送至标准输出(默认为屏幕)。putchar返回输出的字符,如果发生错误则返回EOF。函数printf也将输出发送到标准输出,可以和putchar函数交替使用。
使用输入/输出库函数的源文件必须包含#include
在命令行中,可以使用<符号来重定向输入,将键盘输入替换为文件输入:如果程序prog中使用了函数getchar,则命令行
prog将使得程序prog从输入文件infile(而不是键盘)读取字符。
类似地,可以使用>符号来重定向输出:
prog >outfile将程序prog的标准输出重定向到文件outfile。
字符串 “
outfile” 不包含在命令行参数argv中。 管道(pipe)机制也可以用于重定向标准输入/输出:
prog | otherprog将程序prog的标准输出重定向到程序otherprog的标准输入。
注:Windows CMD和Linux Shell命令行都支持输入/输出重定向和管道机制。
对于只从一个输入流读取数据、只向一个输出流写入数据的程序,使用getchar、putchar和printf函数实现输入/输出就足够了。考虑程序lower,将输入转换为小写形式:
将输入转换为小写形式
例如,假设编译出的可执行文件为lower.out,则命令行
./lower.out将文件a.txt的内容转换为小写并打印到标准输出;
利用打印文件的Linux命令cat或Windows命令type(1.5.1节编写过类似功能的程序)和管道机制,以下命令和上面的等价:
cat a.txt | ./lower.out # Linux type a.txt | lower.exe # Windows输入/输出重定向可以同时使用:
./lower.outb.txt 将文件a.txt的内容转换为小写并输出到文件b.txt。
练习7-1 编写一个程序,根据它被调用的名字(argv[0])实现将大写字母转换为小写或将小写字母转换为大写的功能。
7.2 格式化输出——printf输出函数printf将内部值(整数、浮点数等)转换为字符。本节将介绍该函数最典型的用法。
int printf(char *format, arg1, arg2, ...)printf函数在format的控制下,对其参数进行转换与格式化,并在标准输出上打印出来。它返回打印的字符数。
格式字符串包含两种对象:普通字符和转换说明(conversion specification)。普通字符将被原样复制到输出流,而每一个转换说明将转换并打印printf的下一个参数。每个转换说明以%开始、以转换字符结束。在%和转换字符之间可能依次包含:
- 负号,指定左对齐
- 数字,指定最小宽度,如果大于实际宽度则用空格填充(默认右对齐)
- 小数点,用于分隔宽度和精度
- 数字,指定精度,即字符串要打印的最大字符数、浮点数小数点后的位数,或者整数要打印的最少数字数目
- 字母h或l,h表示将整数作为short类型打印,l表示将整数或浮点数作为long类型打印
下表列出了所有的转换字符。如果%后面的字符不是转换说明,则行为是未定义的。
转换字符 参数类型 输出形式 d, i int 十进制整数 o int 无符号八进制整数(没有前导0) x, X int 无符号十六进制整数(没有前导0x或0X) u int 无符号十进制整数 c int 字符 s char * 字符串,直到遇到' '或由精度指定的字符数为止 f double 浮点数,其中小数位数由精度指定(默认为6) e, E double 浮点数科学记数法,小数位数由精度指定(默认为6) g, G double 浮点数,有效数字位数由精度指定(默认为6),如果指数小于-4或大于等于精度则使用%e或%E,否则使用%f,尾部的0和小数点不打印 p void * 指针 % 不转换参数,打印一个% 在转换说明中,宽度或精度可以指定为*,此时宽度或精度的值由下一个参数(必须是int类型)指定。例如,下列语句打印字符串s的至多max个字符:
printf("%.*s", max, s);(注:转换说明%.*s将“消耗”两个参数)
下表展示了在打印字符串"hello, world"(12个字符)时不同转换说明的效果(在两端添加了冒号):
%s :hello, world: %10s :hello, world: %15s : hello, world: %-10s :hello, world: %-15s :hello, world : %.10s :hello, wor: %.15s :hello, world: %15.10s : hello, wor: %-15.10s :hello, wor :下表是打印浮点数1234.5678时不同转换说明的效果:
%f :1234.567800: %8f :1234.567800: %15f : 1234.567800: %-8f :1234.567800: %-15f :1234.567800 : %.2f :1234.57: %.8f :1234.56780000: %15.2f : 1234.57: %-15.2f :1234.57 : %e :1.234568e+03: %8e :1.234568e+03: %15e : 1.234568e+03: %-8e :1.234568e+03: %-15e :1.234568e+03 : %.2e :1.23e+03: %.8e :1.23456780e+03: %15.2e : 1.23e+03: %-15.2e :1.23e+03 : %g :1234.57: %8g : 1234.57: %15g : 1234.57: %-8g :1234.57 : %-15g :1234.57 : %.2g :1.2e+03: %.8g :1234.5678: %15.2g : 1.2e+03: %-15.2g :1.2e+03 :下表是打印整数12345678时不同转换说明的效果:
%d :12345678: %6d :12345678: %15d : 12345678: %-6d :12345678: %-15d :12345678 : %.6d :12345678: %.10d :0012345678: %15.10d : 0012345678: %-15.10d :0012345678 :注意:printf函数使用第一个参数(格式字符串)判断后面参数的个数及类型。如果参数的个数不够或者类型错误,则将得到错误的结果。注意下面两个函数调用之间的区别:
printf(s); printf("%s", s);函数sprintf执行与函数printf相同的转换,但将结果保存到一个字符串中,而不是标准输出:
int sprintf(char *output, char *format, arg1, arg2, ...);字符串output必须足够大以存放输出结果。
练习7-2 编写一个程序,以合理的方式打印任意输入。该程序至少能够根须用户习惯以八进制或十六进制打印非图形字符,并截断长文本行。
7.3 变长参数表本节通过实现printf函数的一个最简单版本minprintf来介绍如何以可移植的方式编写处理变长参数表的函数。由于重点关注参数处理,minprintf只处理格式字符串和参数,而通过调用printf函数实现格式转换。
printf函数的正确声明形式为
int printf(char *format, ...);其中省略号表示这些参数的数量和类型是可变的。省略号只能出现在参数表的尾部。
minprintf函数声明为
void minprintf(char *format, ...);编写minprintf函数的关键在于如何处理一个甚至连名字都没有的参数表。 标准头文件
包含一组宏定义,它们定义了如何遍历参数表。该头文件在不同机器上的实现是不同的,但提供的接口是一致的。
- va_list类型用于声明一个变量,该变量将依次引用各参数(在minprintf中,该变量称为ap,意思是“参数指针”)
- 宏va_start将ap初始化为指向第一个无名参数的指针。在使用ap之前,该宏必须被调用一次。参数表必须至少包括一个有名参数,因为va_start将最后一个有名参数作为起点。
- 每次调用va_arg都将返回一个参数,并将ap指向下一个参数
- va_end完成必要的清理工作,它必须在函数返回之前调用
简化的printf函数实现如下:
minprintf函数
练习7-3 改写minprintf函数,是它能完成printf函数的更多功能。
7.4 格式化输入——scanf 7.5 文件访问 7.6 错误处理——stderr和exit 7.7 行输入和输出 7.8 其他函数



