参考资料:
- 《C++ Primer》第5版
- 《C++ Primer 习题集》第5版
3.3 标准库类型vector
(P86)
vector
表示对象的序列,其中所有对象的类型相同,每个对象都有一个与之对应的索引。vector
容纳着其他对象,所以常被称作“容器”。
vector
是一个类模板,使用模板时,我们需要提供一些信息来指定模板需要实例化成什么类,提供信息的方式为:在模板名字后跟一对尖括号,在括号内放入信息:
vector<int> ivec;
由于引用不是对象,所以不存在包含引用的 vector
。
在早期的 C++ 版本中,嵌套 vector
需要写成 vector<vector<int> >
,而非 vector<vector<int>>
。
3.3.1 定义和初始化vector
对象(P87)
最常见的初始化方法就是定义一个空 vector
。使用拷贝初始化时,必须保证两个 vector
对象的类型相同。
列表初始化vector
对象
C++11 为了 vector
提供了列表初始化方法:
vector<string> articles{"a", "an", "the"};
列表初始化只能使用花括号。
创建指定数量的元素
可以提供 vector
对象容纳的元素数量和统一初始值来初始化 vector
对象:
vector<int> ivec(10, -1);
值初始化
如果我们只提供元素数量而不提供元素初始值,库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。如果 vector
对象的元素是内置类型,则元素初始值为 0
,如果元素是某种类类型,则元素由类默认初始化。
这种初始化方法有两个限制:
- 如果有些类要求提供初始值,只提供元素数量就无法完成初始化操作。
- 只能使用直接初始化。
vector<int> ivec1(10); // 正确
vector<int> ivec2 = 10; // 错误
列表初始值还是元素数量?
某些情况下,初始化的真实含义依赖于传递初始值时使用的时花括号还是圆括号。如果使用圆括号,可以理解为提供的值是用来构造 vector
对象的;如果使用花括号,可以理解为我们想列表初始化 vector
对象,所以初始化过程会尽可能把花括号内的值当作元素初始值的列表,在无法执行列表初始化时才会考虑其他方式。
vector<int> v1; // 空
vector<int> v2(10); // 10个值为0的元素
vector<int> v3(10, 42); // 10个值为42的元素
vector<int> v4{10}; // 1个值为10的元素
vector<int> v5{10, 42}; // 两个元素,值分别为10和42
vector<string> v6{10}; // 10个空字符串
vector<string> v7{"a", "an"}; // 两个元素,值分别为"a"和"an"
3.3.2 向vector
对象中添加元素(P90)
push_back
可以把一个值当成 vector
对象的为元素压到 vector
对象的尾端。
C++ 要求
vector
能在运行时高效添加元素,所以在创建vector
时指定其大小是没有必要的,且往往会导致性能下降。例外情况是所有元素的值都一样。
向 vector 对象添加元素蕴含的编程假定
范围 for
循环不应改变其遍历序列的大小。
3.3.3 其他vector
操作(P91)
size
的返回类型是由 vector
定义的 size_type
类型:
vector<int>::size_type // 正确
vector::size_type // 错误
计算vector
内对象的索引
不能使用下标形式添加元素
vector<int> ivec(10, 0);
ivec[10] = 1; // 错误
下标运算符用于访问已经存在的元素。使用下标访问不存在的元素不会被编译器发现,而是在运行时产生一个不可预知的值,并常常导致缓冲区溢出。
3.4 迭代器介绍(P95)
所有标准库容器都可以使用迭代器(iterator)访问其元素。迭代器类似于指针类型,提了对象的间接访问,迭代器可以访问某个元素,也能从一元素移动到另一个元素。迭代器有无效和有效之分,有效的迭代器指向某个元素,或者指向尾元素的下一个位置。
3.4.1 使用迭代器(P95)
有迭代器的类型同时拥有返回迭代器类型的成员:
vector<int> ivec(10, 0);
auto b = ivec.begin(), e = ivec.end();
begin
用于返回指向第一个元素的迭代器,end
用于返回指向尾后元素的迭代器。特别地,如果 vector
为空,则 begin
和 end
返回的是同一个迭代器。
迭代器运算符
试图解引用尾后迭代器和无效迭代器是未定义行为。
将迭代器从一个元素移动到另一个元素
所有标准库容器的迭代器都定义了 ==
和 !=
,但只有少数定义了 <
运算符,所以我们常常使用 it != v.end()
作为循环条件。
迭代器类型
拥有迭代器的标准库类型使用 iterator
和 const_iterator
来表示迭代器。const_iterator
和常量指针类似。
begin
和end
运算符
begin
和 end
的返回类型有对象是否是常量决定:
vector<int> v;
const vector<int> cv;
auto it1 = v.begin(); // it1的类型为vector<int>::iterator
auto it2 = cv.begin(); // it2的类型为vector<int>::const_iterator
为了得到 const_iterator
类型,C++11 引入了 cbegin
和 cend
。
结合解引用和成员访问操作
it->mem
等价于 (*it).mem
某些对vector
对象的操作可能会使迭代器失效
任何可能改变 vector
对象容量(如 push_back
)的操作都会导致该对象的迭代器失效。
使用了迭代器的循环体,不要向迭代器所属的容器添加元素。
3.4.2 迭代器运算(P99)
所有标准库容器的迭代器都支持 ++
、==
和 !=
。string
和 vector
的迭代器还提供了更多额外的迭代器运算:
迭代器的算术运算
两个迭代器相减得到的是类型为 difference_type
的带符号整数。
使用迭代器运算
[外链图片转存中…(img-u5ChYVMZ-1694591574896)]
迭代器的算术运算
两个迭代器相减得到的是类型为 difference_type
的带符号整数。
使用迭代器运算
用 mid = (beg+end) /2
代替 mid = beg + (end-beg)/2
可行吗?不可行,因为迭代器的加法不存在。