C++ vector容器的用法(非常详细)
在 STL 尚未诞生的年代,如果想在程序中保存和管理大量同类型的数据,只能采用数组这种方式。
例如,要保存公司中所有的员工对象,需要如下定义一个数组:
同时,对数组元素的访问也“简单粗暴”:
随着 STL 的出现,其中的 vector 容器提供了更为优雅的解决方案,它无论是在内存管理上还是在对数据元素的访问上,都要优于数组:
因此,vector 容器成为了数组的最佳替代者,是我们最为常用的 STL 容器。Vector 容器的使用方法具有一定的典型性,学会了使用 vector 容器,依葫芦画瓢,基本上也就学会了使用 STL 中其他容器的使用方法。
例如,如果程序中需要管理大量的 Employee 对象,就需要一个 vector 容器来对它们进行管理:
除空容器之外,如果想得到一个已保存默认数据的 vector 容器,则可以在创建容器对象时,利用它的构造函数指定默认数据及默认数据的个数。当容器对象创建完成后,这些默认数据也就保存在容器中了。例如:
例如,当需要保存一组固定数量、相同类型的数据时,例如一年 12 个月的平均气温、一周 7 天的 PM 2.5 数值等。如果使用 vector 容器来存放这些固定数量的数据,vector 容器的动态内存分配可能会申请比实际所需更多的空间,从而浪费宝贵的内存资源。此外,vector 容器提供了 push_back() 函数,可以动态地向容器中添加数据,如果无意中调用了 push_back() 函数,可能会破坏这组数据的固定数量。
为了既能利用容器的各种优势,又能避免 vector 容器在保存这类固定数量的数据时的各种劣势,STL 提供了 array 容器,它可以作为 vector 容器的“备胎”。
array 是一个支持随机访问且大小固定的容器。与 vector 容器为元素预留空间不同,array 容器并不预留多余空间,只为元素分配必要的空间,因此它克服了 vector 容器在保存固定数量的数据时浪费内存资源的劣势。
与普通数组不同的是,array 容器保存了自己的 size 信息,并提供了必要的操作函数,可以方便、安全地对数据进行操作,这弥补了普通数组的劣势。
正是这些特点,使得 array 容器特别适合在嵌入式系统和有类似限制(性能敏感、安全关键、内存紧张)的系统中使用。
与 vector 容器相似,array 容器同样是一个类模板,只不过它需要两个特化参数,第一个参数表示这个容器可以保存的数据类型,第二个参数表示这个容器可以保存的数据个数。
例如:
同样,我们也可以使用迭代器访问 array 容器中的数据:
vector 容器提供的常用操作函数如下表所示:
在上表所述的这些操作函数中,常用的是 push_back() 函数,我们通常使用它将数据保存到 vector 容器中。当使用 push_back() 函数向 vector 容器中添加数据时,它会接受一个与 vector 容器模板参数数据类型相同的数据,然后将其复制为一个新元素并添加到 vector 容器的末尾,也就是将数据“推送(push)”到 vector 容器的“末尾(back)”。
例如:
本质上,vector 容器就是一个数组,访问数组中元素的方式与访问 vector 容器中数据的方式相似,我们同样可以使用“[]”符号,以元素在容器中的位置作为下标来访问 vector 容器中的数据。
例如:
但是,为了保证访问元素的安全性,vector 容器更多的是使用迭代器或者 at() 函数来访问容器中的数据元素。例如:
从这里也可以看出,vector 容器的使用非常简单,比数组更加便利和安全。当我们在程序中需要保存相同类型的数据序列时,vector 容器是“最佳选择”。
例如,要保存公司中所有的员工对象,需要如下定义一个数组:
// 定义数组的容量 const int MAX_COUNT = 100000; // 定义保存 Employee 对象的数组 Employee arrEmp[MAX_COUNT];这种方式虽然可以解决问题,但显得非常“粗鲁”:
- 如果定义的最大容量大于实际的需要,就会造成内存空间的浪费;
- 如果定义的最大容量小于实际需要,又无法满足程序的需要,且不利于程序的扩展。
同时,对数组元素的访问也“简单粗暴”:
- 数组无法对索引值进行检查,很容易造成数组的访问越界等严重的错误;
- 数组无法动态地插入或者删除其中的数据;
- 无法通过传值的方式在函数之间传递数组等。
随着 STL 的出现,其中的 vector 容器提供了更为优雅的解决方案,它无论是在内存管理上还是在对数据元素的访问上,都要优于数组:
- vector 容器动态增减的容量大小替代了数组的固定容量,可以适应各种不同的需要;
- vector 容器提供的迭代器和操作函数使得访问操作数据元素的方式更加安全和丰富,这种方式替代了数组通过“[]”运算符直接存取数据元素的方式。
因此,vector 容器成为了数组的最佳替代者,是我们最为常用的 STL 容器。Vector 容器的使用方法具有一定的典型性,学会了使用 vector 容器,依葫芦画瓢,基本上也就学会了使用 STL 中其他容器的使用方法。
创建并初始化vector对象
本质上,vector 容器是一个类模板vector<T>
,因此要想使用 vector 容器,首先需要根据所要保存的数据类型实例化,得到一个特定类型的模板类,然后创建相应的 vector 容器对象,最后利用它提供的功能函数对容器进行添加、删除、插入等常见操作,完成对数据的管理。例如,如果程序中需要管理大量的 Employee 对象,就需要一个 vector 容器来对它们进行管理:
#include <vector> // 包含 vector 类模板所在的头文件 using namespace std; // 使用 vector 所在的命名空间 std // ... // 因为要保存 Employee 对象,所以先使用 Employee 类型实例化 vector 类模板 // 然后创建实例对象 vecEmp vector<Employee> vecEmp;这样,我们就得到了一个空的 vector 容器,可以保存 Employee 类型的数据。
除空容器之外,如果想得到一个已保存默认数据的 vector 容器,则可以在创建容器对象时,利用它的构造函数指定默认数据及默认数据的个数。当容器对象创建完成后,这些默认数据也就保存在容器中了。例如:
Employee emp; // 默认数据 // 创建 4 个 emp 对象的副本并保存到容器中 vector<Employee> vecEmp(4, emp); // 使用 Employee 类的默认构造函数创建 4 个对象并保存到容器中 vector<Employee> vecDefEmp(4);在创建一个 vector 容器时,除可以指定其中元素的个数以及默认的元素外,vector 容器还提供了一个接受初始化列表(initializer list)为参数的初始化列表构造函数,这意味着,我们可以利用初始化列表在创建容器对象同,将任意多个相应类型的数据添加到容器中,以此来完成 vector 容器的初始化工作。例如:
// 创建一个保存学生成绩的vector容器 // 在创建的同时利用初始化列表添加初始数据 vector<int> vecScores({ 85, 92, 63, 91});虽然 vector 容器的内存是随着元素个数的增加而动态增加的,但如果已预先知道vector容器可能的最大容量,则可以使用reserve()函数预先申请足够的内存,为vector容器预留足够多存储元素的位置,避免在元素添加过程中要动态申请内存。例如:
// 大约有1000名员工,所以为vector容器预留1000个元素位置 vecEmp.reserve( 1000 );
vector容器的“备胎”:array容器
作为数组的最佳替代者,vector 容器有着众多优势。然而,在某些特殊情况下,这些优势却反而变成了劣势。例如,当需要保存一组固定数量、相同类型的数据时,例如一年 12 个月的平均气温、一周 7 天的 PM 2.5 数值等。如果使用 vector 容器来存放这些固定数量的数据,vector 容器的动态内存分配可能会申请比实际所需更多的空间,从而浪费宝贵的内存资源。此外,vector 容器提供了 push_back() 函数,可以动态地向容器中添加数据,如果无意中调用了 push_back() 函数,可能会破坏这组数据的固定数量。
为了既能利用容器的各种优势,又能避免 vector 容器在保存这类固定数量的数据时的各种劣势,STL 提供了 array 容器,它可以作为 vector 容器的“备胎”。
array 是一个支持随机访问且大小固定的容器。与 vector 容器为元素预留空间不同,array 容器并不预留多余空间,只为元素分配必要的空间,因此它克服了 vector 容器在保存固定数量的数据时浪费内存资源的劣势。
与普通数组不同的是,array 容器保存了自己的 size 信息,并提供了必要的操作函数,可以方便、安全地对数据进行操作,这弥补了普通数组的劣势。
正是这些特点,使得 array 容器特别适合在嵌入式系统和有类似限制(性能敏感、安全关键、内存紧张)的系统中使用。
与 vector 容器相似,array 容器同样是一个类模板,只不过它需要两个特化参数,第一个参数表示这个容器可以保存的数据类型,第二个参数表示这个容器可以保存的数据个数。
例如:
// 包含定义array 容器的头文件 #include <array> using namespace std; // 定义一个只能保存12 个double类型数据元素的array容器 array<double, 12> arrMonths; // 像使用普通数组一样,使用下标对容器中对应位置的数据元素进行赋值,将数据保存到容器中 arrMonths[0] = 15.4; arrMonths[1] = 17.6; // ... arrMonths[11] =19.2;
同样,我们也可以使用迭代器访问 array 容器中的数据:
// 输出 array 容器中的所有数据 for(auto it = arrMonths.begin(); it != arrMonths.end(); ++it ) { // 使用迭代器访问 array 容器中的数据 cout << *it << endl; }概括来说,array 容器除无法动态地改变它的大小外,在操作上,array 容器与 vector 容器并无太大差别。因此,如果需要保存一组固定数量且相同类型的数据,array 容器是比 vector 容器更好的选择。
vector容器的常用操作
对于数组,我们通常通过指针或它的下标来访问它的元素。这种操作方式虽然简便,但很容易导致数组访问越界的错误。作为数组的最佳替代者,vector 容器当然不会采用这么“粗鲁”的方式来操作容器中的元素。取而代之的是,它提供了多种操作函数,使用这些函数可以灵活而安全地对 vector 容器进行各种操作。vector 容器提供的常用操作函数如下表所示:
函数 | 说明 |
---|---|
v.empty() | 判断容器 v 是否为空。如果 v 为空,则返回 true,否则返回 false |
v.size() | 返回容器 v 中已保存元素的个数 |
v.push_back(val) | 在容器 v 的末尾增加一个值为 val 的元素 |
v.pop_back() | 返回容器 v 的最后一个元素 |
v.insert(pos,val) | 在容器 v 的 pos 位置插入一个值为 val 的元素 |
v.erase(pos) | 删除容器 v 中 pos 位置的元素 |
v.clear() | 删除容器 v 中的所有元素 |
v.at(pos) | 返回容器 v 中 pos 位置的元素 |
v1 = v2 | 将 v2 赋值给 v1,也就是把容器 v2 中的元素全部复制到容器 v1 中 |
v1 == v2 | 判断容器 v1 和容器 v2 是否相等,也就是两个容器中的元素是否相等,如果 v1 与 v2 相等,则返回 true |
在上表所述的这些操作函数中,常用的是 push_back() 函数,我们通常使用它将数据保存到 vector 容器中。当使用 push_back() 函数向 vector 容器中添加数据时,它会接受一个与 vector 容器模板参数数据类型相同的数据,然后将其复制为一个新元素并添加到 vector 容器的末尾,也就是将数据“推送(push)”到 vector 容器的“末尾(back)”。
例如:
// 创建一个空的可以保存整型数的 vector 容器 vector<int> vecSalary; int nSalary; // 接受用户输入 // 循环读取用户输入 while(cin >> nSalary) { // 调用 push_back() 函数将用户输入的数据保存到 vector 容器的对象 vecSalary 中 vecSalary.push_back(nSalary); } // 在容器的开始位置插入一个数据 auto it = vecSalary.begin(); // 获得指向开始位置的迭代器 vecSalary.insert(it,2200); // 在容器的开始位置插入数据 // 输出当前容器中的元素个数 cout << "当前容器中的元素个数是: " << vecSalary.size() << endl;在这段代码中,因为需要保存用户输入的 int 类型的工资数据,所以首先以 int 作为 vector 类模板的类型参数,定义了一个空的能够保存 int 类型数据的 vector 容器 vecSalary。然后通过循环语句,从标准输入读取用户输入的数据,并添加到 vecSalary 容器的末尾位置。当循环结束时,vecSalary 容器就包含了所有输入的数据。
访问vector容器中的数据
如果我们已经使用 push_back() 函数将数据保存到容器中,接下来当然希望能够访问容器中的数据,对其进行相应的处理。本质上,vector 容器就是一个数组,访问数组中元素的方式与访问 vector 容器中数据的方式相似,我们同样可以使用“[]”符号,以元素在容器中的位置作为下标来访问 vector 容器中的数据。
例如:
// 将vecSalary容器中的第一个数据元素赋值为3000 vecSalary[0] = 3000;
但是,为了保证访问元素的安全性,vector 容器更多的是使用迭代器或者 at() 函数来访问容器中的数据元素。例如:
// 定义索引值变量,用于访问容器中的数据 vector<int>::size_type nIndex = 0; // 循环遍历 vector 容器 for( auto it = vecSalary.begin(); it != vecSalary.end(); ++it, ++nIndex ) { // 通过迭代器读取容器中的数据 cout << "当前工资是: " << *it << endl; // 通过 at() 函数修改容器中元素的值 vecSalary.at(nIndex) += 1000; // 涨工资了,每个人加1000元 cout << "涨工资之后是: " << *it << endl; }这里,我们分别通过迭代器和 at() 函数对 vector 容器中的数据进行了读写操作。虽然两者在效果上是相同的,但在条件允许的情况下,为了保持代码的一致性,应优先通过迭代器对容器中的元素进行访问。
从这里也可以看出,vector 容器的使用非常简单,比数组更加便利和安全。当我们在程序中需要保存相同类型的数据序列时,vector 容器是“最佳选择”。