数组和循环
本节阅读量:在本章第一节中,我们介绍了当存在许多相关的单个变量时会出现的可扩展性挑战。本课将重新回顾该问题,然后讨论数组如何帮助我们优雅地解决此类问题。
可变与可扩展性挑战
考虑这样一种情况:想要计算一个班级学生的平均考试成绩。为了让示例简洁,假设这个班只有5个学生。
下面是可以使用单个变量解决此问题的方法:
|
|
这需要大量变量和大量输入。想象一下,如果要为30名学生或600名学生做同样的事情,会有多少工作。此外,如果添加了新的成绩,就必须声明并初始化新变量,并将其添加到平均值计算中。还记得更新除数吗?如果忘记了,就会产生语义错误。每当必须修改现有代码时,都有引入错误的风险。
现在,您已经知道,当有一组相关变量时,应该使用数组。因此,可以用std::vector替换这些单独变量:
|
|
这要好得多,定义的变量数量显著减少,平均值计算中的除数现在也直接由数组长度确定。
但平均值计算仍然有问题,因为必须手动逐个列出每个元素。由于必须显式列出每个元素,这段代码只适用于固定长度的数组。如果还希望计算其他长度数组的平均值,就需要为每种不同的数组长度编写新的平均值计算代码。
真正需要的是一种能访问每个数组元素、但不必显式列出每个元素的方法。
循环
在前面的课程中,我们注意到数组下标不需要是常量表达式,它们也可以是运行时表达式。这意味着可以使用变量的值作为索引。
还请注意,前一示例的平均值计算中使用的数组索引是升序:0、1、2、3、4。因此,如果有某种方法能让变量依次取值0、1、2、3和4,就可以直接使用该变量作为数组索引。我们已经知道如何做到这一点:使用for循环。
下面使用for循环重写上面的示例,其中循环变量用作数组索引:
|
|
这应该相当直观。索引从0开始,testScore[0]被加到average中,然后索引增加到1。testScore[1]被加到average中,索引增加到2。最终,当索引增加到5时,index < length为false,循环终止。
此时,循环已经将testScore[0]、testScore[1]、testScore[2]、testScore[3]和testScore[4]的值添加到average中。
最后,通过将累积值除以数组长度来计算平均值。
这个解决方案在可维护性方面比较理想。循环迭代次数由数组长度确定,循环变量用于完成所有数组索引操作。不再需要手动列出每个数组元素。
如果想添加或删除测试分数,只需要修改数组初始值设定项的数量,其余代码仍然可以工作,无需进一步更改!
按某种顺序访问容器的每个元素称为遍历容器。遍历通常也称为迭代,或者称为在容器上迭代、在容器中迭代。
注
由于容器类使用类型size_t作为长度和索引,因此本课也会采用相同做法。后面会讨论如何使用有符号长度和索引。
模板、数组和循环释放了可扩展性
数组提供了一种存储多个对象的方法,而不必命名每个元素。
循环提供了一种遍历数组的方法,而不必显式列出每个元素。
模板提供了一种参数化元素类型的方法。
模板、数组和循环结合在一起,使我们能够编写可操作元素容器的代码,而不必关心容器中的元素类型或元素数量!
为了进一步说明这一点,下面重写上面的示例,将平均值计算重构为函数模板:
|
|
在上面的示例中,我们创建了函数模板calculateAverage(),它接受任意元素类型和任意长度的std::vector,并返回平均值。在main()中,我们演示了当使用5个int元素的数组或4个double元素的数组调用该函数时,它都能很好地工作!
calculateAverage() 适用于任何支持函数内所用运算符(operator+=(T),operator/=(int))的类型T。如果尝试使用不支持这些运算符的T,编译器会在尝试编译实例化函数模板时报错。
可以对数组和循环做什么
既然已经知道如何使用循环遍历元素容器,接下来看看容器遍历最常用于哪些事情。我们通常通过遍历容器来执行以下四类操作之一:
- 根据现有值计算一个新值(平均值、求和等)
- 查询存在的元素(例如是否存在精确匹配、匹配的个数、最大值等)
- 对每个元素进行操作(例如打印每个元素、将每个元素乘2等)
- 对容器重新排序
前三项相当简单。可以使用单个循环遍历数组,并根据需要检查或修改每个元素。
重新排序容器元素要复杂得多,因为这通常涉及在一个循环中使用另一个循环。虽然可以手动完成此操作,但通常最好使用标准库中的现有算法。未来讨论算法时会更详细地介绍这一点。
数组和off-by-one错误
当使用索引遍历容器时,必须注意确保循环执行正确的次数。Off-by-one错误(循环体多执行或少执行一次)很容易发生。
通常,当使用索引遍历容器时,索引会从0开始,并循环直到索引<长度。
新程序员有时会不小心将索引<=长度用作循环条件。这会导致在索引==长度时仍执行循环,从而造成越界下标和未定义行为。