需要指出的是:printf不限于每次只能输出一个格式化字符。这就好像在同学会的例子中,我们的表格中可能有不止一个地方是"等回复"一样。当我们想要同时输出多个格式化字符时,我们要按照顺序给出这些格式字符的值。比如下面的这个例子:
int i = 15;
float f = 15.0f;
char c = 'c';
printf("i = %d, f = %f, c = %c.", i, f, c);
显示结果如下:
i = 15, f = 15.000000, c = c.
请读者注意printf后面几个变量的顺序,他们和格式控制字符的顺序(整型,浮点,字符)的顺序必须是一致的。
解释完了printf,用于格式化输入的scanf就很好理解了。scanf函数用来从控制台读入用户的输入信息。假设我们希望用户输入自己的身高:
printf("请输入你的身高\n");
int height;
scanf("%d", &height);
printf("你的身高是%dcm.\n", height);
这个程序运行起来之后效果是这样的:
请输入你的身高
172
你的身高是172cm.
我们看到这里出现了之前大家从未见过的scanf语句:
scanf("%d", &height);
scanf用来从控制台读入用户的输入。和printf一样,%d在这里表示从控制台上要读入的是一个整数。后面的height表示将读入的整数保存在height变量中。和printf稍有不同的是,我们发现height前面还有一个新符号&,这个符号,加上后面跟着的height,表示我们要取得height变量在内存中的地址,这是printf函数和scanf函数不太一样的地方。
对于新接触计算机的读者来说,这里的&往往会让人无比困惑。没有关系,我们将在之后的函数和指针等章节中为大家详细介绍&的含义和用法。现在读者们只需要知道,当我们想要调用scanf的时候,我们需要告诉scanf一个变量的地址,让scanf把读入的输入保存在这个地址当中。
为了方便大家理解和记忆printf和scanf的用法,我们在这里举一个不太准确的例子来类比一下:
在计算机市有一条内存大街,内存大街上住着很多很多变量居民,每一个变量都有自己的门牌号(变量在内存中的地址)。计算机市还有一家控制台邮局,这个邮局负责收发变量居民们的信件。
变量weight住在内存大街的196号。有一天,它给控制台邮局写了一封信,信的内容就是它自己的名字62(变量的值)。它把信封好之后,写上
控制台邮局 收,
内存大街196号 寄
交给了邮递员printf.不幸的是,printf送信送到一半的时候下起大雨,信封上的寄件人地址被淋湿看不清了。可是对于printf来说,寄件人的地址重要吗?不重要!printf只要把信件本身送到控制台邮局就可以了!从printf的观点来看,这两段代码没有什么区别:
代码一:
int weight = 62;
printf("我的体重是%d.", weight);
代码二:
printf("我的体重是%d.", 62);
对于printf来说,传进去一个变量weight后,不用关心这个变量叫什么名字,也不关心变量住在哪里,只关心这个变量里面的值是多少,把变量值取出来,打印在控制台上就可以了!
然而,有一天控制台邮局收到了外地寄来的一封信:
XX用户 寄,
内存大街224号, height 收
这个时候,控制台邮局的另一名邮递员scanf负责把这封信交给住在内存大街224号的变量height.printf每次送信可以不关心内存大街上那些寄信人的地址,但是scanf不能!如果不知道height的具体住址,scanf就不能把这封信准确地投递给height家里。对于scanf来说,变量名height不重要,height里面存的值也不重要,scanf只想知道的是自己要把寄来的信(用户从控制台的输入)投递到哪一家住户那里(保存在内存中的什么位置)。所以在使用scanf的时候,我们必须要用&来取出变量的地址,并把这个地址告诉scanf.
以上是对printf和scanf的一个粗糙类比,仅仅用于帮助大家理解。读者可以耐心地等到学习后面的内容时再回想printf和scanf的调用过程,现在我们只要知道怎么使用它们就可以了!
scanf也可以一次读取多个变量:
int i;
float f;
char c;
scanf("%d %f %c", &i, &f, &c);
printf("i = %d, f = %f, c = %c\n", i, f, c);
在我的电脑上,运行结果如下:
17 32.5 h
i = 17, f = 32.500000, c = h
和printf一样,scanf当中格式控制字符的顺序和随后变量的顺序也必须一致。
顺序结构实例
怪兽大学是一所致力于培养惊吓专员的大学。惊吓专员的目的是恐吓人类婴儿,利用婴儿的尖叫声充满电量瓶来获取惊吓能量。小怪兽麦克华斯基明天就要参加期末考试了。它要潜入模拟房间去恐吓床上的机器婴儿。机器婴儿受到惊吓后发出的分贝声越高,华斯基的考试分数就越高。在考试前,主考官郝刻薄院长公布了今年的量化评分标准:
考生得分 = 婴儿分贝 / 40 + 2.5
其中,婴儿分贝的取值范围为0到100间(含0和100)的整数。
郝刻薄院长希望今年的评分能够用计算机在现场完成:只要输入一个婴儿的分贝数,计算机就可以返回该名考生的得分。然而怪兽大学没有计算机系,因此郝刻薄院长希望你能够写一个小程序来帮助她。
程序输入:一个0到100之间(包含0和100)的整数
程序输出:考生的得分,用浮点数表示。
程序示例:
程序输入:100
程序输出:5.000000
(本题背景来自皮克斯工作室和迪斯尼合作的3D喜剧动画片Monster University)
分析:
这个程序的结构非常清晰。我们可以把整个程序分解成三个部分:
从控制台读入一个整数;
计算考生得分;
输出考生的得分;
按照这个思路,我们首先写出整体的框架。首先,我们定义两个变量voice和score:
#include
int main()
{
int voice;
float score;
//从控制台读入一个整数
//计算考生得分
//输出考生的得分
return 0;
}
我们接下来一步一步来完成整个程序。首先从第一步开始:根据前面的知识,读入一个整数可以用scanf来完成:
scanf("%d", &voice);
接下来,我们来计算考生的得分:
score = voice / 40 + 2.5;
最后,我们把score输出到控制台上:
printf("%f", score);
我们的程序完成了!
#include
int main()
{
int voice;
float score;
//从控制台读入一个整数
scanf("%d", &voice);
//计算考生得分
score = voice / 40 + 2.5;
//输出考生的得分
printf("%f", score);
return 0;
}
来运行一下:
100
4.500000Press any key to continue . . .
这个结果不太对啊,输入100的时候输出应该是5分,怎么会是4.5呢?我们来一步一步看:
printf会有错吗?不太可能,我们写的是%f,变量名也没有写错。这说明到了这一步score的值确实是4.5;
那这个4.5是怎么计算出来的呢?回头看看程序:
score = voice / 40 + 2.5;
score如果是4.5的话,那voice / 40的计算结果就是2,难道voice是80 可是我们输入的确实是100.是scanf把输入的100改成了80吗?也不太可能,我们用了%d,也写了&voice,scanf不太可能把值改掉。
那就只剩下最后一种可能了:100 / 40 = 2 哦,原来我们在这里犯了错误:我们使用的100是一个int类型的整数,除数40也是一个整数,当两个整数相除时,结果会自动取整(请回忆4.2.4节)!难怪我们算出来的2.5被硬生生地改成了2!
明白了错误在哪里,程序就很好改了:我们只需要把100强制转换成浮点数,浮点数除以整数的时候会按照浮点数除法来运算,这样我们就可以得到正确的结果了:
#include
int main()
{
int voice;
float score;
//从控制台读入一个整数
scanf("%d", &voice);
//计算考生得分
score = (float)voice / 40 + 2.5;
//输出考生的得分
printf("%f", score);
return 0;
}
运行一下试试:
100
5.000000
OK!这次结果就对了。
尽管这是一个非常简单的例子,它还是可以给我们带来很多启示的:首先,如果拿到了一个程序不知从何下手,不妨把它分解成几个部分,一步一步来完成;第二,程序出错了不要紧,要逐步缩小出错的范围,找到错误,改正它!
本书附录中介绍了程序调试的技巧,读者现在就可以对照附录开始学习如何进行程序的调试,并且从写自己的第一个程序开始实践程序调试的方法。
:动手体验:
盖住上面的源代码,把这个例题重新写一遍,你能够一次通过吗?如果不能的话,是哪里出错了?利用附录中的调试技巧找出错误并改正。
if判断结构
回到那个出租车司机的例子。当我们在路上遇到三岔路口或是十字路口时,我们需要从多种可能中选择一条路前进。在程序中也有这样的"岔路口".当遇到这些岔路口时,程序的执行顺序就不再是简单地一条一条从上往下顺序执行,而是会按照一定的规则来决定接下来执行哪一条语句。这就是本节的if判断结构和下一节要介绍的switch判断结构。它们的基本思路是:首先判断一个条件的真假,随后根据判断的结果选择接下来执行哪一部分语句。
基本if结构
我们首先来介绍if-else语句。让我们从最简单的if语句开始:
if (条件判断)
{
…
}
if语句应该这样翻译:如果条件判断的值为真,那么就进入花括号内的语句块去执行里面的程序。当然,如果条件判断的值为假,那就跳过if的这一部分,继续执行后面的代码。我们还是来看一个简单的例子:
int number;
scanf("%d", &number);
if (number % 2 == 0)
{
printf("%d是个偶数\n", number);
}
让我们来一句一句地解释这一段代码在干什么:
int number;
这没有什么问题,我们定义了一个int类型的变量number;
scanf("%d", &number);
这个也不难,这是我们上一节学习的scanf语句,用户从控制台上输入一个整数,我们把它保存在number中;
if (number % 2 == 0)
这就有点意思了。回忆一下%和==都是什么意思?%表示的是整数间的取模运算,而==用来判断左右两边的值是否相等。我们把这句话翻译成汉语那就是:number这个变量的值除以2的余数等于0.如果这句话为真了,我们就进入if语句下面的花括号:
{
printf("%d是个偶数\n", number);
}
我们在这里打印了一行字:number代表的这个数是一个偶数。现在我们把if语句的这一块连在一起理解,这段代码的功能就是:如果number除以2的余数是0,我们就在屏幕上输出:number是个偶数。
M脚下留心:==与=
初学者最容易犯的错误是忘记区分==和=两个符号,尤其是在if语句当中,一不小心就会写出下面这样的语句:
if (number % 2 = 0)
{
…
}
再次强调,==和=的含义是完全不一样的!==是判断左右两边的值是否相等,而=是将右边的值赋给左边。上面的这种错误还可以在编译时检查出来,因为number % 2 是不能被赋值的。更隐蔽的错误是这种:
int value = number % 2; //value保存了number除以2的余数
if (value = 0)
{
…
}
你会发现,无论number是奇数还是偶数,你都进入不了if里面去执行语句。为什么呢?因为value=0的真值是value最终的值,而value的值已经被赋为0了,所以,它的值永远都是假。此外,在条件判断时,非零即为真。所以,不仅仅if(value=1)是一个永真的判断,只要value被赋予了一个非零的值,比如if(value=2)或者if(value=10)都是永真的判断。如果你的程序中条件判断的执行总是和你的预期不一致,请首先检查一下自己是不是犯了上述错误。
if…else…结构
解释完了if之后,我们接下来介绍else:
if (条件判断)
{
…
}
else
{
…
}
不要被else吓到,它只是补充了一下if当中条件判断为假时执行的代码:如果条件判断为真,我们就进入if;如果条件判断为假,我们就进入else.无论真假,我们总要执行,且仅执行if和else当中的一个。
还是回到奇偶数的例子上来。我们将刚刚的例子稍加改动:
int number;
scanf("%d", &number);
if (number % 2 == 0)
{
printf("%d是个偶数\n", number);
}
else
{
printf("%d是个奇数\n", number);
}