第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(string, expr_1, expr_2, ...)
要展示的值可以是常数、变量或者更复杂的表达式。
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会遇到哪些陷阱?

  1. 程序员必须要检查转换符的个数跟输入变量的个数是否匹配,每个转换说明是否适合对应的变量,编译器不会强制检查错误的匹配;
  2. &通常都是必须要的,记得使用&是程序员的责任;

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依次读取字符-20.;由于整数不包含小数点,则scanf将-20存到j中,将.号放回去;
  • 转换说明:%f
    scanf依次读取字符.3-;由于浮点数在数字之后不能包含-号,所以scanf将0.3保存到x中,将-号放回去;
  • 转换说明:%f
    scanf依次读取字符-4.0e3\n;由于浮点数不能包含\n号,所以scanf将-4\times 10^3保存到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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容