3.3.3 其他vector操作(2)
结果显示:没有成绩在30分以下的,30分至39分有1个、40分至49分有1个、50分至59分没有、60分至69分有2个、70分至79分有3个、80分至89分有2个、90分至99分有4个,还有1个是满分。
在具体实现时使用一个含有11个元素的vector对象,每个元素分别用于统计各个分数段上出现的成绩个数。对于某个成绩来说,将其除以10就能得到对应的分数段索引。注意:两个整数相除,结果还是整数,余数部分被自动忽略掉了。例如,42/10=4、65/10=6、100/10=10等。一旦计算得到了分数段索引,就能用它作为vector对象的下标,进而获取该分数段的计数值并加1:
- // 以10分为一个分数段统计成绩的数量:0--9, 10--19, ... 90--99, 100
- vector<unsigned> scores(11, 0); // 11个分数段,全都初始化为0
- unsigned grade;
- while (cin >> grade) { // 读取成绩
- if (grade <= 100) // 只处理有效的成绩
- ++scores[grade/10]; // 将对应分数段的计数值加1
- }
在上面的程序中,首先定义了一个vector对象存放各个分数段上成绩的数量。此例中,由于初始状态下每个元素的值都相同,所以我们为vector对象申请了11个元素,并把所有元素的初始值都设为0。while语句的条件部分负责读入成绩,在循环体内部首先检查读入的成绩是否合法(即是否小于等于100分),如果合法,将成绩对应的分数段的计数值加1。
执行计数值累加的那条语句很好地体现了C++(www.cppentry.com)程序代码的简洁性。表达式
- ++scores[grade/10]; // 将当前分数段的计数值加1
等价于
- auto ind = grade/10; // 得到分数段索引
- scores[ind] = scores[ind] + 1; // 将计数值加1
上述语句的含义是:用grade除以10来计算成绩所在的分数段,然后将所得的结果作为变量scores的下标。通过运行下标运算获取该分数段对应的计数值,因为新出现了一个属于该分数段的成绩,所以将计数值加1。
如前所述,使用下标的时候必须清楚地知道它是否在合理范围之内(参见3.2.3节,第95页)。在这个程序里,我们事先确认了输入的成绩确实在0到100之间,这样计算所得的下标就一定在0到10之间,属于0到scores.size()-1规定的有效范围,一定是合法的。
不能用下标形式添加元素
刚接触C++(www.cppentry.com)语言的程序员也许会认为可以通过vector对象的下标形式来添加元素,事实并非如此。下面的代码试图为vector对象ivec添加10个元素:
- vector<int> ivec; // 空 vector对象
- for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
- ivec[ix] = ix; // 严重错误:ivec不包含任何元素
然而,这段代码是错误的:ivec是一个空vector,根本不包含任何元素,当然也就不能通过下标去访问任何元素!如前所述,正确的方法是使用push_back:
- for (decltype(ivec.size()) ix = 0; ix != 10; ++ix)
- ivec.push_back(ix); // 正确:添加一个新元素,该元素的值是ix
vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。
提示:只能对确知已存在的元素执行下标操作!
关于下标必须明确的一点是:只能对确知已存在的元素执行下标操作。例如,
- vector<int> ivec; // 空 vector对象
- cout << ivec[0]; // 错误:ivec不包含任何元素
- vector<int> ivec2(10); // 含有10个元素的vector对象
- cout << ivec2[10]; // 错误:ivec2元素的合法索引是从0到9
试图用下标的形式去访问一个不存在的元素将引发错误,不过这种错误不会被编译器发现,而是在运行时产生一个不可预知的值。
不幸的是,这种通过下标访问不存在的元素的行为非常常见,而且会产生很严重的后果。所谓的缓冲区溢出(buffer overflow)指的就是这类错误,这也是导致PC及其他设备上应用程序出现安全问题的一个重要原因。
确保下标合法的一种有效手段就是尽可能使用范围for语句。
3.3.3节练习
练习3.16:编写一段程序,把练习3.13中vector对象的容量和具体内容输出出来。检验你之前的回答是否正确,如果不对,回过头重新学习3.3.1节(第97页)直到弄明白错在何处为止。
练习3.17:从cin读入一组词并把它们存入一个vector对象,然后设法把所有词都改写为大写形式。输出改变后的结果,每个词占一行。
练习3.18:下面的程序合法吗?如果不合法,你准备如何修改?
- vector<int> ivec;
- ivec[0] = 42;
练习3.19:如果想定义一个含有10个元素的vector对象,所有元素的值都是42,请列举出三种不同的实现方法。哪种方法更好呢?为什么?
练习3.20:读入一组整数并把它们存入一个vector对象,将每对相邻整数的和输出出来。改写你的程序,这次要求先输出第1个和最后1个元素的和,接着输出第2个和倒数第2个元素的和,以此类推。