博主介绍:爱打游戏的计算机专业学生
博主主页:夏驰和徐策
所属专栏:夏驰和徐策带你从零开始学C++
思维导图:
3.3 标准库类型vector
概念与特性:
- vector: 一种标准库容器,用于存放同一类型的对象集合。
- 索引访问: 每个存储在vector中的对象都有一个对应的索引,用于访问。
- 容器: 由于vector存放其他对象,因此也被称为容器。
使用前提:
- 头文件: 使用vector前需包含头文件
<vector>
。 - 命名空间声明: 常用
using std::vector;
以便简化代码。
类模板与实例化:
- 类模板: vector是一个类模板,需对C++有深入理解才能自定义模板。
- 实例化: 使用模板时,需指定编译器将类或函数实例化成何种类型。
示例:
- 定义vector实例:
vector<int> ivec;
// 存储int类型对象vector<Sales_item> Sales_vec;
// 存储Sales_item类型对象vector<vector<string>> file;
// 存储vector<string>类型对象
注意事项:
- 类型规定: vector是模板而非类型。由vector生成的类型必须包含元素的类型,如
vector<int>
。 - 不支持引用: 由于引用不是对象,因此不能有包含引用的vector。
- 嵌套vector: vector的元素也可以是其他vector,但早期C++标准和某些编译器可能需要在尖括号之间添加空格。
警告:
- 对于元素为vector的vector(即嵌套vector),某些编译器可能需要老式的声明方式,如
vector<vector<int> >
。
3.3.1 定义和初始化vector对象
基本定义和初始化方法:
-
空vector:
vector<T> v1;
- 默认初始化,不含任何元素,类型为T。
-
拷贝初始化:
vector<T> v2(v1);
- v2包含所有v1的元素副本。vector<T> v2 = v1;
- 等价于v2(v1),含有所有v1元素的副本。
-
数量和值初始化:
vector<T> v3(n, val);
- 包含n个重复元素,每个值为val。vector<T> v4(n);
- 包含n个执行值初始化的对象。
-
列表初始化:
vector<T> v5{a, b, c...};
- 包含初始值个数的元素,每个被赋予相应的初始值。vector<T> v5 = {a, b, c...};
- 等价于v5{a, b, c...}。
使用场景和注意事项:
- 空vector的用途:虽然空vector无元素,但常用于运行时动态添加元素。
- 类型一致性:拷贝给另一个vector时,两个vector的类型必须相同。
- 列表初始化注意点:
- C++11引入,使用花括号
{}
。 - 例如:
vector<string> articles = {"a", "an", "the"};
包含三个字符串元素。
- C++11引入,使用花括号
特殊情况与限制:
- 初始元素值的列表:如果提供的是元素值列表,应使用花括号进行列表初始化。
- 创建指定数量的元素:可使用元素数量和统一初始值初始化vector。
- 值初始化:如果元素是内置类型,如int,则自动初始化为0。如果是类类型,如string,则由类默认初始化。
- 特殊限制:
- 某些类可能要求显式提供初始值。
- 仅提供元素数量时,使用直接初始化而非拷贝初始化。
列表初始化与元素数量的歧义:
- 花括号与圆括号的区别:花括号尝试列表初始化,圆括号用于构造vector对象。
- 示例:
vector<int> v1(10);
- 10个值为0的元素。vector<int> v2{10};
- 1个值为10的元素。vector<int> v3(10, 1);
- 10个值为1的元素。vector<int> v4{10, 1};
- 2个元素,值分别为10和1。
3.3.2 向vector对象中添加元素
常见情况和基本方法:
- 直接初始化:适用于初始值已知且数量较少,或所有元素初始值相同的情况。
- 动态添加:更常见的情况是元素数量和值未知,或者虽然已知但数量庞大且各不相同。
使用push_back成员函数:
- 基本使用:
v2.push_back(i);
将整数i
添加到v2
的尾端。 - 适用场景:
- 当无法提前知道元素数量。
- 当元素值在运行时确定。
- 当初始化过程繁琐或不确定。
实例:
-
顺序添加固定范围元素:
vector<int> v2; for(int i = 0; i != 100; ++i) v2.push_back(i); // 向v2添加0到99
-
实时读取数据添加到vector:
vector<string> text; string word; while(cin >> word) { text.push_back(word); // 将输入的word添加到text }
性能考虑:
- 动态增长效率:C++标准要求vector能高效快速地动态增长。
- 避免预设大小:预设大小可能导致性能降低,特别是元素值不同的情况。
编程假定和要求:
- 循环准确性:确保添加元素的循环逻辑正确,特别是当循环可能改变vector容量时。
- 避免在范围for循环内修改大小:范围for语句体内不应改变遍历序列的大小。
3.3.3 其他vector操作
基本操作及用途:
-
空判断:
v.empty()
: 判断vectorv
是否为空,为空返回真,否则假。
-
大小获取:
v.size()
: 返回vectorv
中元素的个数。
-
添加元素:
v.push_back(t)
: 向vectorv
的尾端添加一个值为t
的元素。
-
元素访问:
v[n]
: 返回vectorv
中第n
个位置上元素的引用。
-
赋值操作:
v1 = v2
: 用vectorv2
中元素的拷贝替换v1
中的元素。v1 = {a, b, c...}
: 用列表中元素的拷贝替换v1
中的元素。
-
比较操作:
v1 == v2
,v1 != v2
: 比较两个vector是否相等或不等。<
,<=
,>
,>=
: 按字典顺序比较两个vector。
访问元素和迭代:
- 使用范围for语句(如
for(auto i : v) {...}
)处理vector中所有元素,更安全且易于理解。 - 当需要修改vector中元素时,控制变量应定义为引用类型,以直接影响原元素。
索引和安全性:
- 计算vector内对象的索引时,确保下标在合法范围内,避免缓冲区溢出和未定义行为。
- 不要使用下标添加新元素。
ivec[ix] = ix;
在ivec
为空时是错误的。正确的方法是使用push_back
。 - 只能对确知已存在的元素执行下标操作,否则可能导致运行时错误。
注意事项:
- 下标安全:访问不存在的元素会引发错误。编译器不会检查下标的合法性,需程序员自行保证。
- 范围for与安全:使用范围for语句可以确保不会访问无效的下标,是处理vector元素的安全方式之一。
理解C++标准库中的Vector
C++标准库中的vector
是一种序列容器,它封装了能够存储和动态管理对象数组的功能。作为最常用的动态数组,它提供了丰富的功能和灵活性,使得处理序列数据变得更加容易。本文将深入探讨vector
的特性、用法、优缺点以及如何有效地使用它。
Vector的基本概念
在C++中,vector
定义在<vector>
头文件中,它是一个模板类,可以存储任何类型的对象。与普通数组相比,vector
最大的优势在于它可以动态地调整大小。
#include <vector>
int main() {
std::vector<int> vec; // 创建一个空的int类型的vector
}
常用操作
初始化
vector
提供了多种初始化方式,包括使用初始值列表、复制另一个vector
的内容,或者指定元素的数量和值。
std::vector<int> vec1; // 空vector
std::vector<int> vec2(4, 100); // 4个元素,每个都为100
std::vector<int> vec3(vec2.begin(), vec2.end()); // 复制vec2
std::vector<int> vec4 {1, 2, 3, 4, 5}; // 初始化列表
访问元素
可以使用[]
运算符或者at()
方法访问vector
的元素。at()
方法与[]
不同,它在索引超出范围时会抛出一个异常。
std::vector<int> vec = {10, 20, 30, 40};
int first = vec[0]; // 第一个元素
int last = vec.at(vec.size() - 1); // 最后一个元素
修改元素
vector
提供了push_back()
和pop_back()
等方法来添加和移除元素。
std::vector<int> vec;
vec.push_back(10); // 添加一个元素到末尾
vec.pop_back(); // 移除最后一个元素
大小和容量
可以使用size()
来获取vector
中的元素数量,使用capacity()
来获取它在不重新分配内存的情况下可以容纳的元素数量。
std::vector<int> vec = {1, 2, 3};
size_t count = vec.size(); // 元素数量
size_t capacity = vec.capacity(); // 容量
性能考量
动态增长
当新元素被添加到vector
中,并且当前的存储空间不足以容纳它时,vector
会自动分配一个更大的存储空间,并将所有元素复制到这个新空间中。这个过程叫做重新分配,可能是一个昂贵的操作。为了减少重新分配的次数,可以使用reserve()
方法来预分配足够的空间。
访问效率
vector
提供了快速的随机访问能力,访问任何元素的时间复杂度都是O(1)。但是,在vector
的开始或中间插入或删除元素的效率较低,因为这可能涉及到移动大量的元素。
实用技巧
- 预分配内存:如果你知道将要存储多少元素,使用
reserve()
预分配空间可以避免多次重新分配。 - 使用范围for循环:C++11引入的范围for循环可以简化对
vector
的遍历。 - 注意迭代器失效:在进行插入或删除操作后,现有的迭代器可能会失效。
- 选择合适的数据结构:虽然
vector
非常灵活,但在某些特定情况下,其他容器如list
或deque
可能更合适。
总结:
3.3.1 定义和初始化vector对象
重点:
- 理解各种初始化方式:掌握空vector的创建,拷贝初始化,列表初始化等多种vector初始化方法及其用途。
- size_type的使用:了解如何正确使用
vector<T>::size_type
来表示大小和索引。
难点:
- 列表初始化 vs. 元素数量初始化:区分使用花括号
{}
和圆括号()
进行初始化的区别,以及如何选择。 - 理解类型一致性:了解在拷贝初始化中vector类型必须相同的规则。
易错点:
- 错误的初始化方式:混淆使用
=
初始化和使用括号初始化的场景,导致不正确的初始化。 - 忽略size_type:使用普通的int类型作为索引,而不是使用
vector<T>::size_type
,可能导致类型不匹配问题。
3.3.2 向vector对象中添加元素
重点:
- push_back方法:熟练使用
push_back
向vector动态添加元素。 - 理解动态增长的效率:知道vector的大小是动态增长的,理解其对程序性能的影响。
难点:
- 循环控制和逻辑:确保在使用循环向vector添加元素时循环控制逻辑正确无误,尤其是在可能改变vector容量的情况下。
易错点:
- 修改循环中的vector大小:在范围for循环中改变vector的大小,导致未定义行为。
- 预设vector大小的误解:错误地认为预设vector大小会提高性能,而忽略了动态增长的效率和实际用途。
3.3.3 其他vector操作
重点:
- 掌握常用操作:理解并记住
empty
,size
,[]
等方法的使用和语义。 - 索引和迭代:正确使用索引访问元素和通过范围for循环迭代vector。
难点:
- 安全访问元素:理解并确保在使用下标访问vector元素时的安全性,特别是对新添加的元素进行操作。
易错点:
- 越界访问:尝试访问不存在的元素,特别是在未检查vector大小的情况下使用下标访问。
- 混淆下标和push_back:试图使用下标方式添加元素到vector,而不是使用
push_back
。 - 忽视比较操作的前提:在对vector进行比较操作时忽略了元素类型必须支持相应的比较操作。
结语
vector
是C++中极其重要的部分,理解并有效使用它对于编写高效和可靠的C++代码至关重要。通过本文的学习,希望你能更好地理解vector
的工作原理和使用方法,从而在实际编程中更加得心应手。