首页 > 编程笔记

C++ vector容器详解

在 C++ 中,vector 容器定义在标准头文件<vector>中,其行为非常类似于数组,存储在其中的数据在内存中也是连续的,而且可以通过下标进行随机存取。

C++ 数组最大的优点就是可以使用下标随机访问数组元素。但其缺点也很明显,不仅需要时刻防备下标越界的危险,还要注意空间不足的情况。使用 vector 的初衷往往就是用来替换数组。

vector 同数组最大的区别在于其内存空间可以动态增长,即当需要更大的内存空间时,vector 可以自动增长,同时又不破坏原来的数据。

具体来讲,当 vector 发现其容量不够时,首先开辟一块儿新的空间,新空间要比原有的空间大,足可以容纳 vector 原有的元素以及新添加的元素,然后再把原有的元素和新添加的元素复制到新的内存空间中。

vector容器的构造

本质上,vector 是一个模板类,它提供了多种构造函数,功能基本上都是设置 vector 容器的初始长度,以及初始化容器内各个元素的值。包括:
// 默认构造函数,长度为 0
explicit vector (const allocator_type& alloc = allocator_type());

// 长度为 n,所有元素被默认初始化
explicit vector (size_type n);

// 长度为 n,所有元素初始化为 val  
vector (size_type n, const value_type& val, const allocator_type& alloc = allocator_type());

// 使用迭代器 first 和 last 指定的范围内的元素来初始化 vector
template <class InputIterator>  vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());

// 拷贝构造函数,创建一个与 x 相同的新 vector 对象。
vector (const vector& x);
vector (const vector& x, const allocator_type& alloc);

// 移动构造函数,创建一个新的 vector 对象,并从 x 中“移动”数据,而不进行深度拷贝。
vector (vector&& x);
vector (vector&& x, const allocator_type& alloc);

// 初始化列表构造函数,使用给定的初始化列表 il 来初始化 vector。
vector (initializer_list<value_type> il, const allocator_type& alloc = allocator_type());
下面是一个完整的 C++ 示例,演示了用各种构造函数来创建 vector 容器。
#include <iostream>
#include <vector>
#include <iterator>

int main() {
    // 使用默认构造函数创建一个空 vector
    std::vector<int> vec1;
   
    // 创建一个大小为 5 的 vector,所有元素默认初始化(对于 int,这通常是0)
    std::vector<int> vec2(5);
   
    // 创建一个大小为 5 的 vector,所有元素初始化为 1
    std::vector<int> vec3(5, 1);
   
    // 使用迭代器创建一个 vector
    int arr[] = {1, 2, 3, 4, 5};
    std::vector<int> vec4(arr, arr + 5);
   
    // 使用拷贝构造函数创建一个新 vector
    std::vector<int> vec5(vec4);
   
    // 使用移动构造函数创建一个新 vector
    std::vector<int> vec6(std::move(vec5));
   
    // 使用初始化列表创建一个 vector
    std::vector<int> vec7 = {1, 2, 3, 4, 5};

    return 0;
}

vector容器的使用

有关 vector 元素的操作,无非是增、删、改、查,可以借助 vector 模板类提供的成员函数来实现,如下表所示。

表 1 vector 容器的成员函数
函数成员 函数功能
begin() 返回指向容器中第一个元素的迭代器。
end() 返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
rbegin() 返回指向最后一个元素的迭代器。
rend() 返回指向第一个元素所在位置前一个位置的迭代器。
cbegin() 和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend() 和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin() 和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend() 和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size() 返回实际元素个数。
max_size() 返回元素个数的最大值。这通常是一个很大的值,一般是 232-1,所以我们很少会用到这个函数。
resize() 改变实际元素的个数。
capacity() 返回当前容量。
empty() 判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
reserve() 增加容器的容量。
shrink _to_fit() 将内存减少到等于当前元素实际所使用的大小。
operator[ ] 重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。
at() 使用经过边界检查的索引访问元素。
front() 返回第一个元素的引用。
back() 返回最后一个元素的引用。
data() 返回指向容器中第一个元素的指针。
assign() 用新元素替换原有内容。
push_back() 在序列的尾部添加一个元素。
pop_back() 移出序列尾部的元素。
insert() 在指定的位置插入一个或多个元素。
erase() 移出一个元素或一段元素。
clear() 移出所有的元素,容器大小变为 0。
swap() 交换两个容器的所有元素。
emplace() 在指定的位置直接生成一个元素。
emplace_back() 在序列尾部生成一个元素。

有关表示各个成员函数的语法格式,读者不需要死记硬背,需要时直接去查 C++ 标准库即可,这里不再过多赘述。

下面的 C++ 代码演示了表中部分成员函数的用法:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    //初始化一个空vector容量
    vector<char>value;
    //向value容器中的尾部依次添加 S、T、L 字符
    value.push_back('S');
    value.push_back('T');
    value.push_back('L');
    //调用 size() 成员函数容器中的元素个数
    printf("元素个数为:%d\n", value.size());
    //使用迭代器遍历容器
    for (auto i = value.begin(); i < value.end(); i++) {
        cout << *i << " ";
    }
    cout << endl;
    //向容器开头插入字符
    value.insert(value.begin(), 'C');
    cout << "首个元素为:" << value.at(0) << endl;
    return 0;
}
输出结果为:

元素个数为:3
S T L
首个元素为:C

完整实例

以下是一个 C++ 示例程序,演示了如何使用 std::vector 容器进行各种操作,包括初始化、添加元素、访问元素、遍历、以及删除元素。
#include <iostream>
#include <vector>

int main() {
    // 1. 初始化:创建一个空的 vector 容器
    std::vector<int> vec1;

    // 2. 初始化:使用列表初始化
    std::vector<int> vec2 = {1, 2, 3, 4, 5};

    // 3. 初始化:使用构造函数,创建一个包含 5 个元素为 0 的 vector
    std::vector<int> vec3(5, 0);

    // 4. 添加元素:使用 push_back() 在尾部添加元素
    vec1.push_back(10);
    vec1.push_back(20);
    vec1.push_back(30);

    // 5. 访问元素:使用下标运算符
    std::cout << "First element of vec2: " << vec2[0] << std::endl;

    // 6. 访问元素:使用 at() 函数
    std::cout << "Second element of vec2: " << vec2.at(1) << std::endl;

    // 7. 遍历元素:使用范围 for 循环
    std::cout << "Elements of vec1: ";
    for (const auto &elem : vec1) {
        std::cout << elem << ' ';
    }
    std::cout << std::endl;

    // 8. 遍历元素:使用迭代器
    std::cout << "Elements of vec2: ";
    for (auto it = vec2.begin(); it != vec2.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << std::endl;

    // 9. 删除元素:使用 pop_back() 删除最后一个元素
    vec1.pop_back();

    // 10. 删除元素:使用 erase() 删除第一个元素
    vec2.erase(vec2.begin());

    // 11. 获取容器大小:使用 size() 函数
    std::cout << "Size of vec3: " << vec3.size() << std::endl;

    return 0;
}
运行结果为:

First element of vec2: 1
Second element of vec2: 2
Elements of vec1: 10 20 30
Elements of vec2: 1 2 3 4 5
Size of vec3: 5

总结

vector 的内存空间是连续的,查找元素很快,可以像数组一样用下标进行访问,内存空间可以随需要增长。vector 的缺点也很明显,就是插入和删除元素较慢。

因此,vector 适用于在元素数量变化不大或者只需要增加元素的情况下,如果需要频繁地插入、删除元素,则应当考虑使用别的容器。

推荐阅读