第3章 格式化输入和输出
英文原版:P37
scanf
函数和printf
函数是C语言里使用频率最高的两个函数,支持格式化读和写。
如本章所展示的,两个函数功能都很强大,但要想合理使用需要技巧的。
- 3.1节描述了
printf
函数; - 3.2节描述了
scanf
函数; - 两节都没有展示出完整的细节,要到第22章才展示两个函数的完整的细节内容;
3.1节 printf
函数
源文件tprintf.c
/* Prints int and float values in various formats */
#include <stdio.h>
int main(void)
{
int i;
float x;
i = 40;
x = 839.21f;
printf("|%d|%5d|%-5d|%5.3d\n", i, i, i, i);
printf("|%10.3f|%10.3e|%-10g\n", x, x, x);
}
输出结果:
|40| 40|40 | 040
| 839.210| 8.392e+02|839.21
printf
函数的作用是展示格式化字符串的内容,在字符串中指定位置插入对应的值。
调用printf
函数时,必须提供格式化字符串,后面接着在输出时待插入到字符串中的值,格式如下:
要展示的值可以是常数、变量或者更复杂的表达式。
对printf
函数的一次调用里可输出的值的数量是没有限制的。
格式化字符串可同时包含普通字符串和以%
开头的转换说明。
什么是转换说明?
转换说明是指一个占位符,表示一个在输出时待填充的值。
紧跟在%
后的信息描述了该值是如何从内部格式(二进制)转换成待输出格式(字符串)。
比如,%d
描述的是将一个整数值从二进制形式转换成十进制数字组成的字符串,%f
描述的是将一个float
值从二进制格式转换成十进制数字组成的字符串。
在格式化字符串中的普通字符串被原样输出,转换说明将被替换成待输出的值。
转换说明
转换说明给予程序员对输出样式的许多控制,有可能会导致说明写的很复杂,很难理解。
在本书很早就详细地描述转换说明是很困难的,以致于于不可能完成。现在就简单地了解一下转换说明更为重要的功能。
在第2章里,已看到过:一个转换说明可包含格式化的信息。曾使用%1f
来输出一个小数点后只有1位数字的float
值。
为了更具一般性,转换说明有两种格式%m.pX
和%-m.pX
,其中m和p都是整数型常数,X是一个字母;m和p都是可选的,如果p省略了,则分隔m和p的.
也可以去掉;
比如,在%10.2f
中,m是10,p是2,X是f;在%10f
中,m是10,p和点号.
省略了;在%.2f
中,p是2,m省略了。
m表示最小字段宽度,描述了要输出的字符的最小数量。如果要输出的值小于m个字符,则该值向右对齐,即左边补齐。比如,在%4d
中,将输出123为123
。如果要输出的值超过m个字符,则该字段的宽度自动拓展为相应的大小。比如,在%4d
中,将输出12345为12345
,没有数字丢失。在m前放置一个减号-
,则表示向左对齐,即右边补齐。比如,在%-4d
中,将输出123为123
。
p表示精度,取决于转换描述符X的选择。
X是转换描述符,表示对待输出的值应用哪一种转换。常见的转换描述符有:
- d
表示以十进制的形式输出一个整数;
p表示要输出的数字的最小数量(如有必要,则在数字的开头补0);
如果省略了p,则p的默认值是1,即%d
等价于%.1d
; - e
表示以指数形式输出浮点数;
p表示在小数点后该输出多少位数字,默认是6;
如果p是0,则不输出小数点; - f
表示以没有指数的形式输出固定格式的浮点数;
p表示在小数点后该输出多少位数字,默认是6;
如果p是0,则不输出小数点; - g
表示根据数字的大小,要么以指数形式,要么以固定十进制形式输出浮点数;
p表示待输出的浮点数中有效数字的最大个数;
跟f
格式不同,g
格式不输出尾部的0;
如果待输出的浮点数小数点以后没有数字,则g
就不输出小数点;
当在编码时不能确定要输出的数的大小时,或者数的变化范围倾向于很大时,最好使用g
描述符。当待输出一个中等大或者适度小的数时,g
描述符使用固定十进制格式;当待输出的数是一个非常大或者非常小的数时,g
描述符会使用指数根式,以便使用较少的字符可以表示很大或者很小的数。
转义序列
转义序列可使字符串包含一些对编译器有特殊意义的字符,比如"
,包含一些会给编译器带来问题的字符,比如非打印字符。
转移序列示例1
\a Alert(bell)
\b Backspace
\n New line
\t Horizontal tab
当转移序列出现在printf的格式化字符串中时,转移序列会在输出时执行其代表的操作。比如,输出\b
将游标回退一个位置;输出\t
移动游标到下一个tab停止处。
一个字符串可以包含任意数量的转移序列。
转移序列示例2
printf("Item\tUnit\tPurchase\n\tPrice\tDate\n");
\"
是常用的转移序列,表示字符"
。因为字符"
表示一个字符串的开始和结束,所以如果在字符串中没有使用转移字符,则不会输出字符"
。
转移序列示例2
printf("\"Hello!\"");
如何输出字符\
?
在字符串中放入\\
,即
printf("\\");
3.2 scanf
函数
scanf根据具体的格式来读取输入。
一个scanf字符串可同时包含普通字符串和转换说明。
scanf支持的转换说明跟printf一样。
在许多情形里,一个scanf格式字符串只包含转换说明符,比如
int x, j;
float x, y;
scanf("%d%d%f%f", &i, &j, &x, &y);
使用scanf会遇到哪些陷阱?
- 程序员必须要检查转换符的个数跟输入变量的个数是否匹配,每个转换说明是否适合对应的变量,编译器不会强制检查错误的匹配;
-
&
通常都是必须要的,记得使用&
是程序员的责任;
scanf的工作原理
scanf本质上是一个模式匹配函数,尽力让输入字符组跟转换说明匹配。
scanf由格式化字符串控制。当调用scanf时,scanf从做开始处理字符串中的信息。对格式字符串中的每个转换说明,scanf尽力去输入数据中定位合适类型的项,如有必要可以跳过空格。然后,scanf开始读取转换项的值,直到碰到一个不属于该转换项的字符。如果成功读取了转换项,则scanf继续处理格式化字符串中的剩余信息。如果有任何一个转换项没有读取成功,则scanf就立即返回,不再处理剩余信息。
当scanf开始搜索一个数时,scanf会忽略空白字符(空格、水平tab、垂直tab、form-feed、换行符)。因此,数可被放在一行上,或者分散在多行。
scanf根据什么规则来识别一个整数或者一个浮点数?
读取整数时,scanf会首先搜索一个数字,一个加号+
或者一个减号-
,然后一直读取数字,直到遇到一个非数字为止。
读取浮点数时,scanf会做哪些事?
步骤1 搜索一个加号+
或者一个减号-
(可选);
步骤2 紧跟着搜索一系列的数字(可能包含一个小数点);
步骤3 搜索一个指数位(可选):一个指数由字母e或者E、可选符号、一位或者多位数字组成(可选);
使用scanf时,%e
、%f
、%g
之间是相互可替换的;对于这3种格式,scanf识别浮点数使用的规则是相同的。
scanf识别整数和浮点数示例
1-20.3-4.0e3\n
scanf("%d%d%f%f", &i, &j ,&x, &y);
- 转换说明:
%d
第一个非空白字符是1;由于整数可从1开始,则scanf继续读取下一个字符-
;由于减号-
不能出现在整数内部,所以scanf会向i中存值1,并把字符-
放回去; - 转换说明:
%d
scanf依次读取字符-
、2
、0
、.
;由于整数不包含小数点,则scanf将-20存到j中,将.
号放回去; - 转换说明:
%f
scanf依次读取字符.
、3
、-
;由于浮点数在数字之后不能包含-
号,所以scanf将0.3保存到x中,将-
号放回去; - 转换说明:
%f
scanf依次读取字符-
、4
、.
、0
、e
、3
、\n
;由于浮点数不能包含\n
号,所以scanf将保存到y中,将\n
号放回去;
源文件addfrac.c
/* Adds two fractions */
#include <stdio.h>
int main(void)
{
int num1, denom1, num2, denom2, result_num, result_denom;
printf("Enter first fraction: ");
scanf("%d/%d", &num1, &denom1);
printf("Enter second fraction: ");
scanf("%d/%d", &num2, &denom2);
result_num = num1 * denom2 + num2 * denom1;
result_denom = denom1 * denom2;
printf("The sum is %d/%d\n", result_num, result_denom);
}
Q&A
Q1:转换说明%i
和%d
有什么区别?
A1:
在printf函数的格式化字符串中,两者没有区别。
对于scanf的格式化字符串来说,%d
仅能匹配十进制数,%i
可匹配8进制数、10进制数、16进制数。
如果输入的数以0开头,比如056
,则%i
将它看做8进制数;
如果输入的数以0x或者0X开头,比如0x56
,则%i
将它看做16进制数;
如果用户不小心在一个数的开头放置了0,则使用%i
读取一个数会有令人吃惊的结果。因此,建议一直使用%d
。
Q2:如果printf将%
看做是转换说明的开始,那么如何打印百分号%
?
A2:
int profit;
profit = 10;
printf("Net profit: %d%%\n", profit);