泛型编程(www.cppentry.com)与C++(www.cppentry.com)标准库
C++(www.cppentry.com)最强大的特性之一就是对泛型编程(www.cppentry.com)的支持。C++(www.cppentry.com)标准库的高度灵活性就是明证,尤其是标准库中的容器、迭代器以及算法部分(最初也称为STL)。
与我的另一本书More Exceptional C++(www.cppentry.com) [Sutter02]一样,本书的开头几条也是介绍STL中一些我们平常熟悉的部件,如vector和string,另外也介绍了一些不那么常见的设施。例如,在使用最基本的容器vector时如何避免常见的陷阱?如何在C++(www.cppentry.com)中进行常见的C风格字符串操纵?我们能够从STL中学到哪些库设计经验(不管是好的、坏的,还是极其糟糕的)?
在考察了STL中的模板设施之后,接着讨论关于C++(www.cppentry.com)中的模板和泛型编程(www.cppentry.com)的一些更一般性的问题。例如,如何让我们的模板代码避免不必要地(且相当不经意地)损失泛型性。为什么说特化函数模板实际上是个糟糕的主意,而我们又应当怎么替换它?在模板的世界中,我们如何才能正确且可移植地完成像授予友元关系这样看似简单的操作?此外还有围绕着export这个有趣的关键字发生的种种故事。
随着我们逐步深入与C++(www.cppentry.com)标准库及泛型编程(www.cppentry.com)相关的主题,就会看到关于上述(以及其他)问题的讨论。
第1条 vector的使用(1) 难度系数:4
几乎每个人都会使用std::vector,这是个好现象。不过遗憾的是,许多人都误解了它的语义,结果无意间以奇怪和危险的方式使用它。本条款中阐述的哪些问题会出现在你目前的程序中呢?
初级问题
1. 下面的代码中,注释A跟注释B所示的两行代码有何区别?
- void f(vector<int>& v) {
- v[0]; // A
- v.at(0); // B
- }
专家级问题
2. 考虑如下的代码:
- vector<int> v;
- v.reserve(2);
- assert(v.capacity() == 2);
- v[0] = 1;
- v[1] = 2;
- for(vector<int>::iterator i = v.begin(); i < v.end(); i++) {
- cout << *i << endl;
- }
- cout << v[0];
- v.reserve(100);
- assert(v.capacity() == 100);
- cout << v[0];
- v[2] = 3;
- v[3] = 4;
- // ……
- v[99] = 100;
- for(vector<int>::iterator i = v.begin(); i < v.end(); i++) {
- cout << *i << endl;
- }
请从代码的风格和正确性方面对这段代码做出评价。
解决方案
访问vector的元素
1. 下面的代码中,注释A跟注释B所示的两行代码有何区别?
- // 示例1-1: [] vs. at
- //
- void f(vector<int>& v) {
- v[0]; // A
- v.at(0); // B
- }
在示例1-1中,如果v非空,A行跟B行就没有任何区别;如果v为空,B行一定会抛出一个std::out_of_range异常,至于A行的行为,标准未加任何说明。
有两种途径可以访问vector内的元素。其一,使用vector
其二,我们也可以使用vector
既然下标越界检查帮助我们避免了许多常见问题,那为什么标准不要求operator[]实施下标越界检查呢?简短的答案是效率。总是强制下标越界检查会增加所有程序的性能开销(虽然不大),即使有些程序根本不会越界访问。有一句名言反映了C++(www.cppentry.com)的这一精神:一般说来,不应该为不使用的东西付出代价(或开销)。所以,标准并不强制operator[]进行越界检查。况且我们还有另一个理由要求operator[]具有高效性:设计vector是用来替代内置数组的,因此其效率应该与内置数组一样,内置数组在下标索引时是不进行越界检查的。如果你需要下标越界检查,可以使用at。
调整vector的大小
现在看示例1-2,该示例对vector