第七章 输入和输出
输入和输出功能并不是 C 语言本身的一部分,故到目前为止,本书都没有对其着重说明。然而,程序与其环境之间交互的方式,比书中之前所展示的更为复杂。本章我们会详描述标准库,即一系列为 C 程序提供输入输出、字符串处理、内存管理、数学例程及其他各种服务的函数。
ANSI 标准精确定义了这些库函数,因此在任何有 C 语言的系统中也都有这些标准库函数,并且是互相兼容的。如果一个程序只使用了标准库提供的功能来与系统进行交互,那么这个程序不用修改就可以从一个系统移植到另一个系统。
库函数的特性在超过一打的头文件中指定【C89时是15个,C11时将近30个】;有些我们已经见过,如 <stdio.h>、<string.h> 和 <ctype.h>。本章不会介绍库的全部内容,因为我们更感兴趣的是如何使用库来写 C 程序。库的细节在附录 B 描述。
7.1 标准输入和输出
如第一章所述,库函数实现了文本输入输出的简单模型。文本流包含了行的序列;每行以一个换行字符结束。如果系统不是按这个方式操作的,库会采取必要的措施,使其看起来像是如此。例如,库可以在输入中把回车符加换行符转换成换行符,并在输出中做反向的转换。
最简单的输入方式是使用 getchar 从“标准输入”(通常是键盘)中每次读一个字符:
int getchar(void)
getchar 每次被调用时返回下一个输入字符,若遇到文件结尾时返回 EOF。符号常量 EOF 在<stdio.h> 中定义。通常其值为 -1,但写判断时应该用 EOF,这样才不依赖特定的值。
在很多环境下,使用输入重定向符号 < ,能把键盘替换成文件:如果程序 prog 使用了 getchar,则如下命令行
prog <infile
使 prog 改从 infile 中读取字符。prog 程序本身不感知输入的切换;特别注意 “infile” 字符串并不包含在 argv 的命令行参数列表中。如果通过管道机制使输入来自另一个程序,输入切换也是不可见的:在某些系统上,命令行
otherprog | prog
运行 otherprog 和 prog 两个程序,并把 otherprog 的标准输出通过管道送到 prog 的标准输入中。
函数
int putchar(int)
用于输出:putchar(c) 将 c 输出到 “标准输出”中,默认为屏幕。putchar 返回已输出的字符,若遇到错误则返回 EOF。和输入一样,通常可以使用 >filename 将输出重定向到文件中:如果 prog 使用了 putchar,则
prog >outfile
把标准输出写到 outfile 文件。如果系统支持管道机制,则
prog | anotherprog
把 prog 的标准输出送到 anotherprog 的标准输入中。
printf 产生的输出也是送往标准输出。我们可以交替调用putchar 和 printf ——输出显示的顺序就是调用的顺序。
每个引用标准输入输出库函数的源文件,都必须在首次引用之前包含下面这行
#include <stdio.h>
当文件名用尖括号括起来时,会在一些标准的文件路径集合中搜索头文件(例如,在UNIX系统中,通常在 /usr/include 目录下)。
很多程序只读一个输入流且只写一个输出流;对这样的程序,使用 getchar,putchar 和 printf 来做输入输出也许就完全够了,而对入门来说是肯定够的。如果使用重定向把一个程序的输出连接到下一个程序的输入,则更是如此。例如,考虑这个把输入转换成小写的程序 lower:
#include <stdio.h>
#include <ctype.h>
main() /* lower:把输入转换成小写 */
{
int c;
while((c = getchar()) != EOF)
putchar(tolower(c));
return 0;
}
tolower 函数定义在 <ctype.h> 中;它将一个大小字母转换成小写,而其他字符则原封不动地返回。我们前面提到过,如 <stdio.h> 中的 getchar 和 putchar 以及 <ctype.h> 中的 tolower,这样的“函数” 通常是宏定义,因此可以避免对每个字符做函数调用带来的开销。我们将在8.5节展示如何做到这一点。无论在特定的机器上 <ctype.h> 中的函数是如何实现的,标准库都对使用这些函数的程序屏蔽了具体的字符集。
练习7-1、写一个程序,可以将大写转换成小写、或将小写转换成大写,具体要做哪种转换,是根据该程序被调用时的名字(通过 argv[0] 获取)来决定的。
7.2 格式化输出——printf
输出函数 printf 将内部的值转换为字符串。在前面的章节中我们不太正式地使用了 printf。本节覆盖了它最典型的用法,但不是全部内容;完整内容参见附录 B。
int printf(char *format, arg1, arg2, ...)
printf 在 format 的控制下,对其参数做转换、格式化,并打印到标准输出。它返回已打印的字符数量。
格式化字符串包含两种对象:拷贝到输出流的普通字符,以及转换规格:每个转换规格都会使printf 的下一个参数被转换并打印出来。每个转换规格以一个 % 开头,并以一个转换字符结尾。在 % 和转换字符之间,依次可以为:
- 减号,表明被转换的参数要左对齐。
- 数字,指定了最小的域宽度。被转换的参数会打印在最小为该宽度的域中。若宽度不够,则会在左边填充到足够的宽度。(如果指定了左对齐,则在右边填充)
- 点号,分隔域宽度和精度。
- 数字,表示精度,有三种情况。对于字符串:指定了字符串中最多打印的字符数。对于浮点数:小数点后最多的位数。对于整数:最少的位数。
- 字母 h 或 l,用于整数打印,前者表示打印为 short,后者表示打印为 long
转换字符见表 7-1。若 % 之后不是转换说明,则该行为是未定义的。
字符 | 类型 | 打印为 |
---|---|---|
d,i | int | 十进制数 |
o | int | 无符号八进制数(无前导0) |
x,X | int | 无符号十六进制数(无前导0x或0X),使用abcdef或ABCDEF表示 10到15。 |
u | int | 无符号十进制数 |
c | int | 单个字符 |
s | char * | 打印字符串中的字符,直到遇到'\0',或者精度给出的字符数。 |
f | double | [-]m.dddddd,其中d的数量由精度决定(默认为6) |
e,E | double | [-]m.dddddde+-xx 或 [-]m.ddddddE+-xx,其中d的数量由精度决定(默认为6) |
g,G | double | 如果幂小于-4或大等于精度时,使用 %e或%E;否则使用 %f。末尾的0和结尾的小数点不打印。 |
p | void * | 指针(由具体实现来决定如何呈现) |
% | 无转换参数 | 打印% |
宽度和精度可以指定为 * ,这种情况下它的值是通过转换下一个参数(必须为int)计算出来的。例如,要在字符串 s 中最多打印 max 个字符,写为:
printf("%.*s", max, s);
大部分的格式转换已经在前面的章节中说明过了。不过还有字符串的精度没讲过。下表显示了不同规格下打印 “hello, world”(12个字符)的效果。我们在两边加上了冒号,以便读者看清其范围:
警告:printf 使用它的首个参数来确定其后跟着多少个参数,以及这些参数各自的类型。如果没有足够的参数,或者类型错误,则 printf 会被搞混,而你的代码将得到错误的结果。还应该注意下面两个调用的不同之处:
printf(s); /* 若s包含%则有问题 */
printf("%s", s); /* 安全 */
sprintf 函数做与 printf 同样的转换,不过它把输出储存到一个字符串里:
int sprintf(char *string, char *format, arg1, arg2, ...)
sprintf 也是同样根据 format 将参数 arg1, arg2 等格式化,不过是将结果保存在 string 里面,而不是标准输出;string 必须足够大以接收这个结果。
练习7-2、写个程序,以合理的方式打印任意输入。最低限度下,它能够按本地习惯用八进制或十六进制打印非图形字符,且能够断开长文本行。