Skip to content

Latest commit

 

History

History
1074 lines (553 loc) · 41.8 KB

File metadata and controls

1074 lines (553 loc) · 41.8 KB

四、具有选择逻辑的程序

在本章中,我们将解释以下内容:

  • 什么是布尔表达式
  • C 如何表示布尔值
  • 如何用if写程序
  • 如何用if...else写程序
  • 哪里需要分号,哪里是可选的,哪里不能放分号
  • 如何测试一个程序
  • 为什么符号常量有用以及如何在 C 程序中使用它们

4.1 导言

在上一章中,我们展示了如何使用顺序逻辑编写程序——程序的语句是从第一个到最后一个“按顺序”执行的。

在这一章中,程序将使用选择逻辑——它们将测试一些条件,并根据条件是真还是假采取不同的行动。在 C 语言中,选择逻辑是使用ifif...else语句实现的。

4.2 布尔表达式

布尔表达式(以著名的英国数学家乔治·布尔命名)要么为真,要么为假。最简单的布尔表达式是比较两个值的表达式。一些例子是:

k is equal to 999

a is greater than 100

a2 + b2 is equal to c2

b2 is greater than or equal to 4ac

s is not equal to 0

这些都可能是真的或假的。这些是一种叫做关系表达式的特殊布尔表达式的例子。这种表达式只是检查一个值是否等于、不等于、大于、大于或等于、小于以及小于或等于另一个值。我们使用关系运算符来编写它们。

C 关系运算符(带示例)有:

| `==` | `equal to` | `k == 999, a*a + b*b == c*c` | | `!=` | `not equal to` | `s != 0, a != b + c` | | `>` | `greater than` | `a > 100` | | `>=` | `greater than or equal to` | `b*b >= 4.0*a*c` | | `<` | `less than` | `n < 0` | | `<=` | `less than or equal to` | `score <= 65` |

布尔表达式通常用于控制程序执行的流程。例如,我们可能有一个变量(h,比方说)以值0开始。我们一直以1递增它,我们想知道它的值何时达到100。我们说我们希望知道条件h == 100何时为真。条件是布尔表达式的通用名称。

编程的真正力量在于程序测试一个条件并决定它是真还是假的能力。如果为真,程序可以执行一组动作;如果它是假的,它可以执行另一套或干脆什么都不做。

例如,假设变量score保存学生在一次测试中获得的分数,如果她的分数为 50 或更高,则该学生通过,如果低于 50,则该学生失败。可以编写一个程序来测试这个条件

score >= 50

如果是真的,学生通过;如果它是假的,学生失败。在 C 语言中,这可以写成:

if (score >= 50) printf("Pass\n");

else printf("Fail\n");

当计算机到达该语句时,它将比较当前值score50。如果值大于或等于50,我们说条件score >= 50为真。在这种情况下,程序打印Pass。如果score的值小于50,我们说条件score >= 50为假。在这种情况下,程序打印Fail

在这一章中,我们将看到布尔表达式如何在ifif...else语句中使用,在下一章中,我们将看到它们如何在while语句中使用。

4.2.1 和,&&

使用关系运算符,我们可以创建简单的条件。但是有时候,我们需要问一件事是不是真的,另一件事是不是真的。我们可能还需要知道两件事情中是否有一件是真的。对于这些情况,我们需要复合条件。为了创建复合条件,我们使用逻辑运算符 AND、OR 和 NOT。

例如,假设我们想知道h的值是否在 1 和 99 之间,包括 1 和 99。我们想知道h是否大于或等于1,以及h是否小于或等于99。在 C 语言中,我们将此表示为:

(h >= 1) && (h <= 99)

在 C 语言中,和的符号是&&

请注意以下几点:

  • 变量h在两种情况下都必须重复。写h >= 1 && <= 99 //this is wrong很诱人,但却是错误的

  • h >= 1h <= 99周围的括号是不需要的,但是放上去也没有错。这是因为&&(和||,见下一页)的优先级低于关系运算符。如果没有括号,h >= 1 && h <= 99会被 C 这样解释:(h >= 1) && (h <= 99)

  • 这与括号的情况相同。

4.2.2 或者,||

如果 n 是一个表示一年中某个月的整数,我们可以通过测试 n 是小于 1 还是大于 12 来检查 n 是否无效。在 C 语言中,我们将此表示为:

(n < 1) || (n > 12)

在 C # 中,或的符号是||。如上所述,括号不是必需的,我们可以将表达式写成

n < 1 || n > 12

这将测试 n 是否无效。当然,我们可以通过测试 if 来测试 n 是否有效

n >= 1 && n <= 12

我们使用哪种测试取决于我们希望如何表达我们的逻辑。有时使用有效测试很方便,有时使用无效测试。

4.2.3 不,!

如果p是某个布尔表达式,那么NOT p反转 p 的真值,换句话说,如果p为真那么NOT p为假;如果p为假,那么NOT p为真。在 C 中,表示不的符号是感叹号,!。使用上面的例子,因为

n >= 1 && n <= 12

测试有效n,条件NOT (n >=1 && n <= 12)测试无效n。这是用 C 写的

!(n >= 1 && n <= 12)

这相当于n < 1 || n > 12。熟悉德摩根定律的人会知道

not (a and b) = (not a) or (not b)

not(a or b) = (not a) and (not b)

一般来说,如果pq是布尔表达式,我们有如下表达式:

  • pq都是truefalsep && qtrue,否则;
  • 只有当pq都是false时,当pq之一是truefalsep || q才是true
  • pfalse!p为真,当ptruefalse为真。

下表对此进行了总结(用T表示true,用F表示false):

| `P` | `q` | `&&` | `||` | `!p` | | `T` | `T` | `T` | `T` | `F` | | `T` | `F` | `F` | `T` | `F` | | `F` | `T` | `F` | `T` | `T` | | `F` | `F` | `F` | `F` | `T` |

本书中的大多数程序将使用简单的条件。少数会使用复合条件。

4.2.3.1 C99 中的数据类型bool

最初的 C 标准和后来的 ANSI C 标准没有定义布尔数据类型。传统上,C 使用表达式值的概念来表示真/假。数值表达式可用于任何需要真/假值的上下文中。如果表达式的值非零,则认为该表达式为真,如果值为零,则认为该表达式为假。

最新的 C99 标准定义了类型bool。然而,在本书中,我们将使用传统的方法,主要是因为许多流行的 C 编译器还不支持 C99 标准。同样,正如你将看到的,没有bool我们也能轻松生活。我们的绝大多数布尔表达式都是在ifwhile语句中使用的关系表达式。如果我们需要一个“布尔”变量,我们可以使用一个int变量,其中1代表true,而0代表false

4.3if构造

让我们为下面的问题写一个程序:

一家电脑维修店每小时收取 100 美元的人工费,外加维修中使用的任何零件的费用。然而,任何工作的最低收费是 150 美元。提示输入工作小时数和零件成本(可能是$0 ),并打印工作费用。

我们将编写程序,使其工作如下:

Hours worked? 2.5

Cost of parts? 20

Charge for the job: $270.00

或者

Hours worked? 1

Cost of parts? 25

Charge for the job: $150.00

以下算法描述了解决问题所需的步骤:

prompt for and read the hours worked

prompt for and read the cost of parts

calculate charge = hours worked * 100 + cost of parts

if charge is less than 150 then set charge to 150

print charge

这是用伪代码编写的算法的另一个例子——一种指定编程逻辑的非正式方式。

该算法引入了一个新语句——if语句。表情

charge is less than 150

是条件的一个例子。如果条件为true,则执行then之后的语句(称为 then 部分);如果是false,则不执行then之后的语句。

程序 P4.1 展示了如何用 C 程序来表达这个算法。

程序 P4.1

//print job charge based on hours worked and cost of parts

#include <stdio.h>

int main() {

double hours, parts, jobCharge;

printf("Hours worked? ");

scanf("%lf", &hours);

printf("Cost of parts? ");

scanf("%lf", &parts);

jobCharge = hours * 100 + parts;

if (jobCharge < 150) jobCharge = 150;

printf("\nCharge for the job: $%3.2f\n", jobCharge);

}

对于这个程序,我们选择使用三个变量— hourspartsjobCharge,都是double类型,因为我们可能需要输入工作时间和零件成本的浮点值。

非常重要的是,你要额外努力去理解if语句,因为它是编程中最重要的语句之一。是if语句可以让程序看起来像是在思考。

条件

charge is less than 150

伪代码算法的表达式在我们的程序中表示为

jobCharge < 150

执行程序时,以正常方式(hours * 100 + parts)计算作业费用。然后,if语句测试这个值jobCharge是否小于150;如果是,那么jobCharge被设置为150。如果不小于150jobCharge保持原样。声明

if (jobCharge < 150) jobCharge = 150;

if结构的一个简单例子。注意到在 C 语言中没有使用单词then。一般来说,C 语言中的构造采用以下形式:

if (<condition>) <statement>

c 需要单词if和围绕<condition>的括号。您必须提供<condition><statement>,其中<condition>是一个Boolean表达式,而<statement>可以是一行语句或一个块——由{}包围的一个或多个语句。如果<condition>true,则执行<statement>;如果<condition>false,则不执行<statement>。在任何一种情况下,程序在<statement>之后继续执行语句(如果有的话)。

在程序中,<condition>

jobCharge < 150

and <statement> is

jobCharge = 150;

举个例子,其中<statement>是一个块,假设我们想要交换两个变量ab的值,但前提是a大于b。这可以通过以下方式来实现,例如,假设a = 15b = 8c是临时变量:

if (a > b)

{

c = a;  //store a in c; c becomes 15

a = b;  //store b in a; a becomes 8

b = c;  //store old value of a, 15,in b

}

这里,<statement>是从{}的部分,一个包含三个赋值语句的块。如果a大于b,则执行该块(并交换值);如果a不大于b,则不执行该块(并且值保持不变)。顺便提一下,要知道交换两个变量的值需要三个赋值语句;两个人是做不到的。如果你不相信,试试看。

一般来说,如果一个条件为真,我们想做几件事;我们必须将它们包含在{}中以创建一个块。这将确保我们满足 C 的规则,即<statement>是单个语句或块。

在块中缩进语句是很好的编程习惯。这使得一眼就能看出块中有哪些语句。如果我们按如下方式编写上面的代码,代码块的结构就不那么容易看出来了:

if (a > b)

{

c = a;  //store a in c; c becomes 15

a = b;  //store b in a; a becomes 8

b = c;  //store old value of a, 15,in b

}

当我们编写伪代码时,我们通常使用以下格式:

if <condition> then

<statement1>

<statement2>

etc.

endif

构造以endif结束,这是许多程序员使用的约定。再次注意,如果<condition>为真,我们缩进要执行的语句。我们强调endif不是一个 C 语言单词,而仅仅是程序员在编写伪代码时使用的一个方便的单词。

该示例说明了在if语句中编写块的一种风格。这种风格与{}的搭配如下:

if (<condition>)

{

<statement1>;

<statement2>;

etc.

}

这里,{}if排成一行,语句缩进。这样就很容易辨认出体内的东西。对于一个小程序来说,这可能无关紧要,但是随着程序大小的增加,代码的布局反映其结构将变得更加重要。在本书中,我们将使用下面的风格(正如你现在所知道的,编译器不关心使用哪种风格):

if (<condition>) {

<statement1>;

<statement2>;

etc.

}

我们将{放在右括号后的第一行,让}if匹配;块中的语句是缩进的。我们相信这和第一种风格一样清晰,并且在程序中少了一行!你用哪种风格是个人喜好问题;选择一个,坚持使用。

4.3.1 求两个长度的和

假设长度以米和厘米为单位,例如 3m 75cm。给你两对代表两个长度的整数。编写一个程序,提示输入两个长度并打印它们的和,使厘米值小于 100。

比如3m 25cm2m 15cm之和是5m 40cm,但是3m 75cm5m 50cm之和是9m 25cm

假设程序如下工作:

Enter values for m and cm: 3 75

Enter values for m and cm: 5 50

Sum is 9m 25cm

请注意,数据必须仅由数字输入。例如,如果我们输入3m 75cm,我们将得到一个错误,因为3m不是一个有效的整数常量。我们的程序将假设输入的第一个数字是米值,第二个数字是厘米值。

我们将两个米值相加,再将两个厘米值相加,得到总和。如果厘米值小于 100,就没什么可做的了。但如果不是,我们必须从中减去 100,并在米值上加 1。这个逻辑表达如下:

m = sum of meter values

cm = sum of centimeter values

if cm >= 100 then

subtract 100 from cm

add 1 to m

endif

作为边界情况,我们必须检查我们的程序是否工作,如果cm正好是100。作为一个练习,验证它确实如此。

程序 P4.2 解决了上述问题。

程序 P4.2

//find the sum of two lengths given in meters and cm

#include <stdio.h>

int main() {

int m1, cm1, m2, cm2, mSum, cmSum;

printf("Enter values for m and cm: ");

scanf("%d %d", &m1, &cm1);

printf("Enter values for m and cm: ");

scanf("%d %d", &m2, &cm2);

mSum = m1 + m2;    //add the meters

cmSum = cm1 + cm2; //add the centimeters

if (cmSum >= 100) {

cmSum = cmSum - 100;

mSum = mSum + 1;

}

printf("\nSum is %dm %dcm\n", mSum, cmSum);

}

我们使用变量m1cm1表示第一长度,m2cm2表示第二长度,mSumcmSum表示两个长度的总和。

该程序假设给定长度的厘米部分小于 100,如果是这样,它就能正确工作。但是如果长度是3m 150cm2m 200cm呢?

程序将打印6m 250cm。(作为练习,按照程序的逻辑来看为什么。)虽然这是正确的,但格式不正确,因为我们要求厘米值小于 100。我们可以通过使用整数除法和%(余数运算符)来修改我们的程序,使其在这些情况下也能工作。

以下伪代码显示了如何操作:

m = sum of meter values

cm = sum of centimeter values

if cm >= 100 then

add cm / 100 to m

set cm to cm % 100

endif

使用上面的例子,m被设置为5cm被设置为350。由于cm大于100,我们用整数除法算出350 / 100(这样算出cm里有多少个100 s)就是3;这个加到m,给出8。下一行将cm设置为350 % 100,也就是50。所以我们得到的答案是8m 50cm,是正确的,格式也是正确的。

请注意,“then 部分”中的语句必须按所示顺序书写。我们必须使用cm的(原始)值计算出cm / 100,然后在下一条语句中将它改为cm % 100。作为练习,请计算出如果将这些陈述颠倒过来,总和将计算出什么值。(答案会是5m 50cm,不对。你能看出为什么吗?)

这些变化反映在程序 P4.3 中。

程序 P4.3

//find the sum of two lengths given in meters and cm

#include <stdio.h>

int main() {

int m1, cm1, m2, cm2, mSum, cmSum;

printf("Enter values for m and cm: ");

scanf("%d %d", &m1, &cm1);

printf("Enter values for m and cm: ");

scanf("%d %d", &m2, &cm2);

mSum = m1 + m2; //add the meters

cmSum = cm1 + cm2; //add the centimeters

if (cmSum >= 100) {

mSum = mSum + cmSum / 100;

cmSum = cmSum % 100;

}

printf("\nSum is %dm %dcm\n", mSum, cmSum);

}

以下是该程序的运行示例:

Enter values for m and cm: 3 150

Enter values for m and cm: 2 200

Sum is 8m 50cm

敏锐的读者可能会意识到我们甚至不需要if语句。

考虑一下这个:

mSum = m1 + m2; //add the meters

cmSum = cm1 + cm2; //add the centimeters

mSum = mSum + cmSum / 100;

cmSum = cmSum % 100;

其中最后两个语句来自于if语句。

因此,我们知道,如果cmSum大于或等于100,这将起作用,因为在这种情况下,这四个语句将被执行。

如果cmSum小于100怎么办?最初,最后两条语句不会被执行,因为if条件为假。现在他们被处决了。让我们看看会发生什么。以3m 25cm2m 15cm为例,我们得到mSum5cmSum40

在下一个语句中40 / 1000,所以mSum不变,在最后一个语句中40 % 10040,所以cmSum不变。因此,答案将被正确地打印为

Sum is 5m 40cm

现在你应该开始意识到,通常有不止一种方式来表达程序的逻辑。有了经验和学习,你会知道哪些方式更好,为什么。

4.4if...else构造

让我们为下面的问题写一个程序:

一个学生要参加 3 次考试,每次满分为 100 分。如果学生的平均分大于或等于 50 分,他就通过了;如果他的平均分小于 50 分,他就不及格。提示 3 个分数,如果学生通过,则打印Pass,如果学生未通过,则打印Fail

我们将假设程序如下工作来编写程序:

Enter 3 marks: 60 40 56

Average is 52.0 Pass

或者

Enter 3 marks: 40 60 36

Average is 45.3 Fail

以下算法描述了解决问题所需的步骤:

prompt for the 3 marks

calculate the average

if average is greater than or equal to 50 then

print "Pass"

else

print "Fail"

endif

ifendif的部分是if...else结构的一个例子。

条件

average is greater than or equal to 50

是关系表达式的另一个例子。如果条件为true,则执行then之后的语句(then 部分);如果是false,则执行else(else 部分)之后的语句。

整个构造以endif结束。

当你写伪代码的时候,重要的是你要表达的逻辑非常清楚。再次注意缩进是如何帮助识别then部分和else部分的。

不过,最终你必须用某种编程语言来表达代码,这样它才能在计算机上运行。程序 P4.4 显示了如何为上述算法做到这一点。

程序 P4.4

//request 3 marks; print their average and Pass/Fail

#include <stdio.h>

int main() {

int mark1, mark2, mark3;

double average ;

printf("Enter 3 marks: ");

scanf("%d %d %d", &mark1, &mark2, &mark3);

average = (mark1 + mark2 + mark3) / 3.0;

printf("\nAverage is %3.1f", average);

if (average >= 50) printf(" Pass\n");

else printf(" Fail\n");

}

仔细研究程序中的if...else结构。它反映了上一页表达的逻辑。再次注意,c 中省略了单词then

一般来说,C 中的if...else构造采用如下所示的形式。

if (<condition>) <statement1> else <statement2>

单词ifelse以及括号是 c 需要的,你必须提供<condition><statement1><statement2><statement1><statement2>中的每一个都可以是一行语句或一个块。如果<condition>true,则执行<statement1>,跳过<statement2>;如果<condition>false,则跳过<statement1>,执行<statement2>。当执行if构造时,要么执行<statement1>要么执行<statement2>,但不能同时执行两者。

如果<statement1><statement2>是单行语句,可以使用以下布局:

if (<condition>) <statement1>

else <statement2>

如果<statement1><statement2>是块,可以使用以下布局:

if (<condition>) {

...

}

else {

...

}

在描述 C 中的各种结构时,我们通常使用短语“其中<statement>可以是一行语句或一个块。”

请记住,在 C 中,对于单行语句,分号被视为语句的一部分。例如:

a = 5;

printf("Pass\n");

scanf("%d", &n);

因此,在使用单行语句的情况下,分号作为语句的一部分,必须存在。在程序 P4.4 中,在if...else语句中,

<statement1> is

printf("Pass\n");

<statement2>

printf("Fail\n");

但是,对于块或复合语句,右大括号}结束块。因此,在使用块的情况下,不需要额外的分号来结束块。

有时候,记住整个if...else结构(从if<statement2>)被 C 认为是一个语句,可以在任何需要一个语句的地方使用,这是很有用的。

4.4.1 计算工资

对于需要块的示例,假设我们有工作小时数和工资率(每小时支付的金额)的值,并希望根据以下内容计算一个人的正常工资、加班工资和总工资:

如果工作时间少于或等于 40 小时,正常工资的计算方法是工作时间乘以工资率,加班工资为 0。如果工作时间超过 40 小时,正常工资的计算方法是 40 乘以工资率,加班工资的计算方法是超过 40 小时乘以工资率 1.5。总工资是通过将正常工资和加班工资相加计算出来的。

例如,如果小时数为36,费率为每小时20美元,则正常工资为$720 ( 36乘以20),加班工资为$0。总工资是$720

而如果小时数为50,费率为每小时12美元,则正常工资为$480 ( 40乘以12),加班工资为$180(加班时间10乘以12乘以1.5)。毛工资是$660 ( 480 + 180)。

上述描述可以用伪代码表示如下:

if hours is less than or equal to 40 then

set regular pay to hours x rate

set overtime pay to 0

else

set regular pay to 40 x rate

set overtime pay to (hours – 40) x rate x 1.5

endif

set gross pay to regular pay + overtime pay

我们使用缩进来突出显示条件“小时数小于或等于 40”为真时要执行的语句,以及条件为假时要执行的语句。整个构造以endif结束。

下一步是将伪代码转换成 C。当我们这样做时,我们必须确保我们坚持 C 编写if...else语句的规则。在这个例子中,我们必须确保thenelse部分都被写成块,因为它们都包含不止一个语句。

使用变量hours(工作时间)rate(工资率)regPay(正常工资)ovtPay(加班工资)和grossPay(总工资),我们编写 C 代码,因此:

if (hours <= 40) {

regPay = hours * rate;

ovtPay = 0;

} //no semicolon here; } ends the block

else {

regPay = 40 * rate;

ovtPay = (hours - 40) * rate * 1.5;

} //no semicolon here; } ends the block

grossPay = regPay + ovtPay;

请注意这两条注释。在第一个}后面放一个分号是错误的,因为if语句继续有一个else部分。如果我们放一个,它有效地结束了if语句,C 假设没有else部分。当它找到单词else时,将没有与之匹配的if,程序将给出一个“放错地方”的错误。

在最后一个}后面不需要分号,但是加一个也没什么坏处。

问题:写一个程序来提示工作时间和工资率。然后,该程序根据上述说明计算并打印正常工资、加班工资和总工资。

以下算法概述了解决方案的整体逻辑:

prompt for hours worked and rate of pay

if hours is less than or equal to 40 then

set regular pay to hours x rate

set overtime pay to 0

else

set regular pay to 40 x rate

set overtime pay to (hours – 40) x rate x 1.5

endif

set gross pay to regular pay + overtime pay

print regular pay, overtime pay and gross pay

该算法在程序 P4.5 中实现。所有变量都声明为double,以便输入工作时间和工资率的小数值。

程序 P4.5

#include <stdio.h>

int main() {

double hours, rate, regPay, ovtPay, grossPay;

printf("Hours worked? ");

scanf("%lf", &hours);

printf("Rate of pay? ");

scanf("%lf", &rate);

if (hours <= 40) {

regPay = hours * rate;

ovtPay = 0;

}

else {

regPay = 40 * rate;

ovtPay = (hours - 40) * rate * 1.5;

}

grossPay = regPay + ovtPay;

printf("\nRegular pay: $%3.2f\n", regPay);

printf("Overtime pay: $%3.2f\n", ovtPay);

printf("gross pay: $%3.2f\n", grossPay);

}

该程序的运行示例如下所示:

Hours worked? 50

Rate of pay? 12

Regular pay: $480.00

Overtime pay: $180.00

Gross pay: $660.00

你应该核实结果确实是正确的。

注意,尽管hoursratedouble,但是它们的数据可以以任何有效的数字格式提供——这里我们使用整数5012。这些值在存储到变量中之前会被转换成double格式。例如,如果我们愿意,我们可以键入50.012.00

4.5 关于程序测试

当我们写一个程序时,我们应该彻底地测试它,以确保它正确地工作。至少,我们应该测试程序中的所有路径。这意味着我们必须选择测试数据,使得程序中的每条语句至少执行一次。

对于程序 P4.5,样本仅在工作时间大于 40 小时时运行测试。仅仅基于这个测试,我们不能确定如果工作时间少于或等于 40 小时,我们的程序将正确工作。可以肯定的是,我们必须运行另一个测试,其中工作时间少于或等于 40。下面是这样一个运行示例:

Hours worked? 36

Rate of pay? 20

Regular pay: $720.00

Overtime pay: $0.00

Gross pay: $720.00

这些结果是正确的,这使我们更加确信我们的程序是正确的。我们还应该在时间正好是40时进行测试;我们必须总是在程序的“边界”测试它对于这个程序,40是一个界限——它是开始支付加班工资的值。

如果结果不正确怎么办?比如假设加班费是错的。我们说程序包含一个 bug(一个错误),我们必须调试程序(从程序中移除错误)。在这种情况下,我们可以查看计算加班工资的报表,看看我们是否正确地指定了计算方法。如果这不能发现错误,我们必须使用产生错误的测试数据,费力地手工“执行”程序。如果操作得当,这通常会揭示错误的原因。

4.6 符号常量

在程序 4.1 中,我们使用了两个常量——100 和 150——分别表示每小时的人工费用和最低工作成本。如果这些值在程序写完之后改变了呢?我们必须找到它们在程序中的所有出现,并将它们更改为新值。

这个程序相当短,所以这不会太难做到。但是想象一下,如果程序包含数百甚至数千行代码,任务会是什么样子。做出所有必要的改变将是困难的、耗时的并且容易出错的。

我们可以通过使用符号常量(也称为显式常量或命名常量)来使生活变得稍微容易一点,符号常量是我们在一个地方设置为所需常量的标识符。如果我们需要改变一个常量的值,只需要在一个地方进行改变。例如,在程序 P4.1 中,我们可以使用符号常量ChargePerHourMinJobCost。我们会将ChargePerHour设为100,将MinJobCost设为150

在 C 语言中,我们使用#define指令来定义符号常量,以及其他用途。我们通过将程序 P4.1 重写为程序 P4.6 来演示如何操作。

程序 P4.6

//This program illustrates the use of symbolic constants

//Print job charge based on hours worked and cost of parts

#include <stdio.h>

#define ChargePerHour 100

#define MinJobCost 150

int main() {

double hours, parts, jobCharge;

printf("Hours worked? ");

scanf("%lf", &hours);

printf("Cost of parts? ");

scanf("%lf", &parts);

jobCharge = hours * ChargePerHour + parts;

if (jobCharge < MinJobCost) jobCharge = MinJobCost;

printf("\nCharge for the job: $%3.2f\n", jobCharge);

}

4 . 6 . 1#define指令

C 语言中的指令通常出现在程序的顶部。出于我们的目的,#define 指令采用以下形式:

#define identifier followed by the "replacement text"

在节目中,我们使用了

#define ChargePerHour 100

注意,这不是一个普通的 C 语句,不需要分号来结束它。这里,标识符是ChargePerHour,替换文本是常量100。在程序体中,我们使用标识符而不是常量。

当程序被编译时,C 执行所谓的“预处理”步骤。它用替换文本替换标识符的所有出现。在程序 P4.6 中,将所有出现的ChargePerHour替换为100,将所有出现的MinJobCost替换为150。完成这些后,程序就编译好了。由程序员负责确保当标识符被替换时,结果语句是有意义的。

实际上,指令说标识符ChargePerHour等价于常量100,标识符MinJobCost等价于150

例如,预处理步骤改变

if (jobCharge < MinJobCost) jobCharge = MinJobCost;

if (jobCharge < 150) jobCharge = 150;

例如,假设最低工作成本从150变为180。我们只需要改变#define指令中的值,因此:

#define MinJobCost 180

不需要其他的改变。

在本书中,我们将使用以大写字母开始符号常量标识符的惯例。但是请注意,C 允许您使用任何有效的标识符。

4.6.2 示例–符号常量

举一个稍微大一点的例子,考虑程序 P4.5。在这里,我们使用了两个常量——40 和 1.5——分别表示最大正常工作时间和加班工资系数。我们使用符号常量MaxRegularHours(设置为 40)和OvertimeFactor(设置为 1.5)将程序 P4.5 重写为程序 P4.7。

程序 P4.7

#include <stdio.h>

#define MaxRegularHours 40

#define OvertimeFactor 1.5

int main() {

double hours, rate, regPay, ovtPay, grossPay;

printf("Hours worked? ");

scanf("%lf", &hours);

printf("Rate of pay? ");

scanf("%lf", &rate);

if (hours <= MaxRegularHours) {

regPay = hours * rate;

ovtPay = 0;

}

else {

regPay = MaxRegularHours * rate;

ovtPay = (hours - MaxRegularHours) * rate * OvertimeFactor;

}

grossPay = regPay + ovtPay;

printf("\nRegular pay: $%3.2f\n", regPay);

printf("Overtime pay: $%3.2f\n", ovtPay);

printf("Gross pay: $%3.2f\n", grossPay);

}

例如,假设最大正常工作时间从40变为35。程序 P4.7 比程序 P4.5 更容易修改,因为我们只需要修改#define指令中的值,就像这样:

#define MaxRegularHours 35

不需要其他的改变。

程序 P4.5 中使用的数字401.5被称为幻数——它们出现在程序中没有明显的原因,就像变魔术一样。幻数是一个很好的迹象,表明一个程序可能是限制性的,依赖于这些数字。尽可能地,我们必须编写没有幻数的程序。使用符号常量有助于使我们的程序更加灵活和易于维护。

4.7 更多示例

我们现在编写程序来解决另外两个问题。他们的解决方案将说明如何使用 if...else 语句来确定从几个选项中选择哪一个。在示例运行中,带下划线的项目由用户键入;其他的都是电脑打印的。

4.7.1 打印字母等级

编写一个程序,请求在测试中获得分数,并根据以下内容打印一个字母等级:

| `score < 50` | `F` | | `50 <= score < 75` | `B` | | `score >= 75` | `A` |

该程序应该如下工作:

Enter a score: 70

Grade B

解决方案如程序 P4.8 所示。

程序 P4.8

//request a score; print letter grade

#include <stdio.h>

int main() {

int score;

printf("Enter a score: ");

scanf("%d", &score);

printf("\nGrade ");

if (score < 50) printf("F\n");

else if (score < 75) printf("B\n");

else printf("A\n");

}

第二个printf打印一个空行,后跟单词Grade,后跟一个空格,但不结束该行。当字母等级确定后,它将打印在同一行上。

我们看到,if...else语句采用以下形式

if (<condition>) <statement1> else <statement2>

其中<statement1><statement2>可以是任意语句。特别是,其中任何一个(或两个)都可以是一个if...else语句。这允许我们编写所谓的嵌套 if 语句。当我们有几个相关的条件要测试时,这特别有用,就像这个例子一样。在节目中,我们可以想到这一部分:

if (score < 50) printf("F\n");

else if (score < 75) printf("B\n");

else printf("A\n");

如同

if (score < 50) printf("F\n");

else <statement>

这里的<statement>是这个if...else语句:

if (score < 75) printf("B\n");

else printf("A\n");

如果score小于50,程序打印F并结束。如果不是,那么score必须大于或等于50

知道了这一点,第一个else部分检查score是否小于75。如果是,程序打印B并结束。如果不是,那么score必须大于或等于75

了解到这一点,第二个else部分(else printf("A\n");与第二个if匹配)打印A并结束。

为了确保程序是正确的,你应该用至少 3 个不同的分数(例如704583)来运行它,以验证 3 个等级中的每一个都被正确打印。你也应该在“边界”数字、5075上测试它。

注意书写else if的首选风格。如果我们遵循我们正常的缩进风格,我们将会书写

if (score < 50) printf("F\n");

else

if (score < 75) printf("B\n");

else printf("A\n");

当然,这仍然是正确的。然而,如果我们有更多的案例,缩进会太深,看起来会很笨拙。此外,由于不同的分数范围实际上是可选的(而不是一个在另一个之内),最好将它们保持在相同的缩进级别。

这里的语句都是单行的 printf 语句,所以我们选择将它们写在 if 和 else 的同一行。但是,如果它们是块,最好这样写:

if (score < 50) {

...

}

else if (score < 75) {

...

}

else {

...

}

作为练习,修改程序以根据以下内容打印正确的分数:

| `score < 50` | `F` | | `50 <= score < 65` | `C` | | `50 <= score < 80` | `B` | | `score >= 80` | `A` |

对三角形进行分类

给定代表三角形边的三个整数值,打印:

  • Not a triangle如果值不能是任何三角形的边。如果任何一个值是负数或零,或者任何一条边的长度大于或等于另外两条边的长度之和,就会出现这种情况;
  • Scalene如果三角形是不规则的(各边不同);
  • Isosceles如果三角形是等腰的(两边相等);
  • Equilateral如果三角形是等边的(三条边相等)。

该程序应该如下工作:

Enter 3 sides of a triangle: 7 4 7

Isosceles

解决方案如程序 P4.9 所示。

程序 P4.9

//request 3 sides; determine type of triangle

#include <stdio.h>

int main() {

int a, b, c;

printf("Enter 3 sides of a triangle: ");

scanf("%d %d %d", &a, &b, &c);

if (a <= 0 || b <= 0 || c <= 0) printf("\nNot a triangle\n");

else if (a >= b + c || b >= c + a || c >= a + b)

printf("\nNot a triangle\n");

else if (a == b && b == c) printf("\nEquilateral\n");

else if (a == b || b == c || c == a) printf("\nIsosceles\n");

else printf("\nScalene\n");

}

第一个任务是确定我们实际上有一个有效的三角形。第一个if检查任一侧是否为负或零。如果是,则打印Not a triangle。如果它们都是肯定的,我们转到else部分,它本身由一个if...else语句组成。

这里,if检查任何一边是否大于或等于其他两边的总和。如果是,则打印Not a triangle。如果不是,那么我们有一个有效的三角形,必须通过执行else部分开始来确定它的类型

if (a == b ...

最简单的方法是首先检查它是否是等边的。如果两对不同的边相等,那么三条边都相等,我们就有一个等边三角形。

如果它不是等边的,那么我们检查它是否是等腰的。如果任意两条边相等,我们就有一个等腰三角形。

如果它既不是等边的,也不是等腰的,那么它一定是不等边的。

作为练习,修改程序以确定三角形是否成直角。如果两条边的平方和等于第三条边的平方,那么这条线就是直角。

Exercises 4An auto repair shop charges as follows. Inspecting the vehicle costs $75. If no work needs to be done, there is no further charge. Otherwise, the charge is $75 per hour for labor plus the cost of parts, with a minimum charge of $120. If any work is done, there is no charge for inspecting the vehicle. Write a program to read values for hours worked and cost of parts (either of which could be 0) and print the charge for the job.   Write a program that requests two weights in kilograms and grams and prints the sum of the weights. For example, if the weights are 3kg 500g and 4kg 700g, your program should print 8kg 200g.   Write a program that requests two lengths in feet and inches and prints the sum of the lengths. For example, if the lengths are 5 ft. 4 in. and 8 ft. 11 in., your program should print 14 ft. 3 in. (1 ft. = 12 in.)   A variety store gives a 15% discount on sales totaling $300 or more. Write a program to request the cost of three items and print the amount the customer must pay.   Write a program to read two pairs of integers. Each pair represents a fraction. For example, the pair 3 5 represents the fraction 3/5. Your program should print the sum of the given fractions. For example, give the pairs 3 5 and 2 3, your program should print 19/15, since 3/5 + 2/3 = 19/15. Modify the program so that it prints the sum with the fraction reduced to a proper fraction; for this example, your program should print 1 4/15.   Write a program to read a person’s name, hours worked, hourly rate of pay, and tax rate (a number representing a percentage, e.g., 25 meaning 25%). The program must print the name, gross pay, tax deducted, and gross pay. Gross pay is calculated as described in Section 4.4.1. The tax deducted is calculated by applying the tax rate to 80% of gross pay. And the net pay is calculated by subtracting the tax deducted from the gross pay. For example, if the person works 50 hours at $20/hour and the tax rate is 25%, his gross pay would be (40 x 20) + (10 20 1.5) = $1100. He pays 25% tax on 80% of $1100, that is, 25% of $880 = $220. His net pay is 1100 - 220 = $880.   Write a program to read integer values for month and year and print the number of days in the month. For example, 4 2005 (April 2005) should print 30, 2 2004 (February 2004) should print 29 and 2 1900 (February 1900) should print 28. A leap year, n, is divisible by 4; however, if n is divisible by 100 then it is a leap year only if it is also divisible by 400. So 1900 is not a leap year but 2000 is.   In an English class, a student is given three term tests (marked out of 25) and an end-of-term test (marked out of 100). The end-of-term test counts the same as the three term tests in determining the final mark (out of 100). Write a program to read marks for the three term tests followed by the mark for the end-of-term test. The program then prints the final mark and an indication of whether the student passes or fails. To pass, the final mark must be 50 or more. For example, given the data 20 10 15 56, the final mark is calculated by (20+10+15)/75*50 + 56/100*50 = 58   Write a program to request two times given in 24-hour clock format and find the time (in hours and minutes) that has elapsed between the first time and the second time. You may assume that the second time is later than the first time. Each time is represented by two numbers: e.g., 16 45 means the time 16:45, that is, 4:45 p.m. For example, if the two given times are 16 45 and 23 25 your answer should be 6 hours 40 minutes. Modify the program so that it works as follows: if the second time is sooner than the first time, take it to mean a time for the next day. For example, given the times 20:30 and 6:15, take this to mean 8.30 p.m. to 6.15 a.m. of the next day. Your answer should be 9 hours 45 minutes.   A bank pays interest based on the amount of money deposited. If the amount is less than $5,000, the interest is 4% per annum. If the amount is $5,000 or more but less than $10,000, the interest is 5% per annum. If the amount is $10,000 or more but less than $20,000, the interest is 6% per annum. If the amount is $20,000 or more, the interest is 7% per annum. Write a program to request the amount deposited and print the interest earned for one year.   For any year between 1900 and 2099, inclusive, the month and day on which Easter Sunday falls can be determined by the following algorithm: set a to year minus 1900 set b to the remainder when a is divided by 19 set c to the integer quotient when 7b + 1 is divided by 19 set d to the remainder when 11b + 4 - c is divided by 29 set e to the integer quotient when a is divided by 4 set f to the remainder when a + e + 31 - d is divided by 7 set g to 25 minus the sum of d and f if g is less than or equal to 0 then    set month to 'March'    set day to 31 + g else    set month to 'April'    set day to g endif Write a program that requests a year between 1900 and 2099, inclusive, and checks if the year is valid. If it is, print the day on which Easter Sunday falls in that year. For example, if the year is 1999, your program should print April 4.   Write a program to prompt for the name of an item, its previous price, and its current price. Print the percentage increase or decrease in the price. For example, if the previous price is $80 and the current price is $100, you should print increase of 25%; if the previous price is $100 and the current price is $80, you should print decrease of 20%.   A country charges income tax as follows based on one’s gross salary. No tax is charged on the first 20% of salary. The remaining 80% is called taxable income. Tax is paid as follows:

  • 应纳税收入的前 15,000 美元的 10%;
  • 接下来的 20,000 美元应税收入的 20%;
  • 超过 35,000 美元的所有应税收入的 25%。

Write a program to read a value for a person’s salary and print the amount of tax to be paid. Also print the average tax rate, that is, the percentage of salary that is paid in tax. For example, on a salary of $20,000, a person pays $1700 in tax. The average tax rate is 1700/20000*100 = 8.5%.