1.4 控制结构
语句顺序地执行:函数的第一条语句首先执行,接着是第二条,依次类推。当然,少数程序——包括我们将要编写的解决书店问题的程序——可以仅用顺序执行语句编写。事实上,程序设计语言提供了多种控制结构支持更为复杂的执行路径。本节将简要地介绍C++提供的控制结构,第6章再详细介绍各种语句。
while语句提供迭代执行。可以用while语句编写一个从1到10(包括10)的求和程序,如下:
#include <iostream>
int main()
{
int sum = 0, val = 1;
// keep executing the while until val is greater than 10
while (val <= 10) {
sum += val; // assigns sum + val to sum
++val; // add 1 to val
}
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
编译并执行后,将输出:
Sum of 1 to 10 inclusive is 55
与前面一样,程序首先包含iostream头文件并定义main函数。在main函数中定义两个int型变量:sum保存总和,val表示从1到10之间的每一个值。我们给sum赋初值0,而val则从1开始。
重要的部分是while语句。while结构有这样的形式:
while (condition) while_body_statement;
while通过测试condition和执行相关的while_body_statement来重复执行,直到condition为假。
condition是一个可求值的表达式,所以可以测试其结果。如果结果值非零,那么条件为真;如果值为零,则条件为假。
如果condition为真(表达式求值不为零),则执行while_body_statement。执行完后,再次测试condition。如果condition仍为真,则再次执行while_ body_statement。While语句一直交替测试condition和执行while_ body_statement,直到condition为假为止。
在这个程序中,while语句是
// keep executing the while until val is greater than 10
while (val <= 10) {
sum += val; // assigns sum + val to sum
++val; // add 1 to val
}
While语句的条件用了小于或等于操作符(<=操作符),将val的当前值和10比较,只要val小于或等于10,就执行while循环体。这种情况下,while循环体是一个包含两个语句的块:
{
sum += val; // assigns sum + val to sum
++val; // add 1 to val
}
块是被花括号括起来的语句序列。C++中,块可用于任何可以用一条语句的地方。块中第一条语句使用了复合赋值操作符(+=操作符),这个操作符把它的右操作数加至左操作数,这等效于编写含一个加法和一个赋值的语句:
sum = sum + val; // assign sum + val to sum
因此第一条语句是把val的值加到sum的当前值,并把结果存入sum,
第二条语句
++val; // add 1 to val
使用了前自增操作符(++操作符),自增操作符就是在它的操作数上加1,++val和val = val + 1是一样的。
执行while的循环体后,再次执行while的条件。如果val的值(自增后)仍小于或等于10,那么再次执行while的循环体。循环继续,测试条件并执行循环体,直到val的值不再小于或等于10为止。
一旦val的值大于10,程序就跳出while循环并执行while后面的语句,此例中该语句打印输出,其后的return语句结束main程序。
关键概念:C++程序的缩排和格式
C++程序格式非常自由,花括号、缩排、注释和换行的位置通常对程序的语义没有影响。例如,表示main函数体开始的花括号可以放在与main同一行,也可以像我们那样,放在下一行的开始,或放在你喜欢的任何地方。唯一的要求是,它是编译器所看到在main的参数列表的右括号之后的第一个非空格、非注释字符。
虽然说我们可以很自由地编排程序的格式,但如果编排不当,会影响程序的可读性。例如,我们可以将main写成单独的一长行。这样的定义尽管合法,但很难阅读。
关于什么是C或C++程序的正确格式存在无休止的争论,我们相信没有唯一正确的风格,但一致性是有价值的。我们倾向于把确定函数边界的花括号自成一行,且缩进复合的输入或输出表达式从而使运算符排列整齐,正如
记住重要的是可能存在其他格式化程序的方式。在选择格式化风格时,要考虑提高程序的可读性,使其更易于理解。一旦选择了某种风格,就要始终如一地使用。
在while循环中,我们使用变量val来控制循环执行次数。每次执行while语句,都要测试val的值,然后在循环体中增加val的值。
由于频频使用像val这样的变量控制循环,因而C++语言定义了第二种控制结构,称为for语句,简化管理循环变量的代码。使用for循环重新编写求1到10的和的程序,如下:
#include <iostream>
int main()
{
int sum = 0;
// sum values from 1 up to 10 inclusive
for (int val = 1; val <= 10; ++val)
sum += val; // equivalent to sum = sum + val
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
在for循环之前,我们定义sum并赋0值。用于迭代的变量val被定义为for语句自身的一部分。for语句
for (int val = 1; val <= 10; ++val)
sum += val; // equivalent to sum = sum + val
包含两部分:for语句头和for语句体。for语句头控制for语句体的执行次数。for语句头由三部分组成:一个初始化语句,一个条件,一个表达式。在这个例子中,初始化语句
int val = 1;
定义一个名为val的int对象并给定初始值1。初始化语句仅在进入for语句时执行一次。条件
val <= 10
将val的当前值和10比较,每次经过循环都要测试。只要val小于或等于10,就执行for语句体。仅当for语句体执行后才执行表达式。在这个for循环中,表达式使用前自增运算符,val的值加1,执行完表达式后,for语句重新测试条件,如果val的新值仍小于或等于10,则执行for语句体,val再次自增,继续执行直到条件不成立。
在这个循环中,for语句体执行求和
sum += val; // equivalent to sum = sum + val
for语句体使用复合赋值运算符,把val的当前值加到sum,并将结果保存到sum中。
扼要重述,for循环总的执行流程为:
(1) 创建val并初始化为1。
(2) 测试val是否小于或等于10。
(3) 如果val小于或等于10,则执行for循环体,把val加到sum中。如果val大于10,就退出循环,接着执行for语句体后的第一条语句。
(4) val递增。
(5) 重复第2步的测试,只要条件为真,就继续执行其余步骤。
在标准化之前的C++中,定义在for语句头的名字在for循环外是可访问的。语言定义中的这一改变,可能会使习惯于使用老式编译器的人,在使用遵循标准的新编译器时感到惊讶。
回顾编译
编译器的部分工作是寻找程序代码中的错误。编译器不能查出程序的意义是否正确, 但它可以查出程序形式上的错误。下面是编译器能查出的最普遍的一些错误。
(1) 语法错误。程序员犯了C++语言中的语法错误。下面代码段说明常见的语法错误;每个注释描述下一行的错误。
// error: missing ')' in parameter list for main
int main ( {
// error: used colon, not a semicolon after endl
std::cout << "Read each file." << std::endl:
// error: missing quotes around string literal
std::cout << Update master. << std::endl;
// ok: no errors on this line
std::cout << "Write new master." <<std::endl;
// error: missing ';' on return statement
return 0
}
(2) 类型错误。C++中每个数据项都有其相关联的类型。例如,值10是一个整数。用双引号标注起来的单词"hello"是字符串字面值。类型错误的一个实例是传递了字符串字面值给应该得到整型参数的函数。
(3) 声明错误。C++程序中使用的每个名字必须在使用之前声明。没有声明名字通常会导致错误信息。最常见的两种声明错误是,从标准库中访问名字时忘记使用“std::”,以及由于疏忽而拼错标识符名:
#include <iostream>
int main()
{
int v1, v2;
std::cin >> v >> v2; // error: uses "v" not "v1"
// cout not defined, should be std::cout
cout << v1 + v2 << std::endl;
return 0;
}
错误信息包含行号和编译器对我们所犯错误的简要描述。按错误报告的顺序改正错误是个好习惯,通常一个错误可能会产生一连串的影响并导致编译器报告比实际多得多的错误。最好在每次修改后或最多改正了一些显而易见的错误后,就重新编译代码。这个循环就是众所周知的编辑-编译-调试。
习题
习题1.9 下列循环做什么? sum的最终值是多少?
int sum = 0;
for (int i = -100; i <= 100; ++i)
sum += i;
习题1.10 用for循环编程,求从50到100的所有自然数的和。然后用while循环重写该程序。
习题1.11 用while循环编程,输出10到0递减的自然数。然后用for循环重写该程序。
习题1.12 对比前面两习题中所写的循环。两种形式各有何优缺点?
习题1.13 编译器不同,理解其诊断内容的难易程度也不同。编写包含本小节“回顾编译”部分讨论的错误的程序。探讨编译器产生的信息,这样你在编译更复杂的程序遇到这些信息时会比较熟悉。
求1到10之间数的和,其逻辑延伸是求用户提供的两个数之间的数的和。可以直接在for循环中使用这两个数,使用第一个输入值作为下界而第二个输入值作为上界。然而, 如果用户首先给定的数较大,这种策略将会失败:程序会立即退出for循环。因此,我们应该调整范围以便较大的数作上界而较小的数作下界。这样做,我们需要一种方式来判定哪个数更大一些。
像大多数语言一样,C++提供支持条件执行的if语句。使用if语句来编写修订的求和程序如下:
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2; // read input
// use smaller number as lower bound for summation
// and larger number as upper bound
int lower, upper;
if (v1 <= v2) {
lower = v1;
upper = v2;
} else {
lower = v2;
upper = v1;
}
int sum = 0;
// sum values from lower up to and including upper
for (int val = lower; val <= upper; ++val)
sum += val; // sum = sum + val
std::cout << "Sum of " << lower
<< " to " << upper
<< " inclusive is "
<< sum << std::endl;
return 0;
}
如果我们编译并执行这个程序并给定输入数为7和3,程序的输出结果将为:
Sum of 3 to 7 inclusive is 25
这个程序中大部分代码我们在之前的举例中已经熟知了。程序首先向用户输出提示并定义四个int变量,然后从标准输入读入值到v1和v2中。仅有if条件语句是新增加的代码:
// use smaller number as lower bound for summation
// and larger number as upper bound
int lower, upper;
if (v1 <= v2) {
lower = v1;
upper = v2;
} else {
lower = v2;
upper = v1;
}
这段代码的效果是恰当地设置upper和lower。if的条件测试v1是否小于或等于v2。如果是,则执行条件后面紧接着的语句块。这个语句块包含两条语句,每条语句都完成一次赋值,第一条语句将v1赋值给lower,而第二条语句将v2赋值给upper。
如果这个条件为假——也就是说,如果v1大于v2——那么执行else后面的语句。这个语句同样是一个由两个赋值语句组成的块,把v2赋值给lower而把v1赋值给upper。

