概述

std::vector 是 STL 提供的内存连续的、可变长度的动态数组容器,可以灵活地管理元素,并且具有许多重要的特性。

  • vector是一个动态数组容器,可以自动调整大小以适应存储元素的需求。它在内部使用连续的内存来存储元素。
  • vector允许在尾部高效地添加和删除元素,通过使用push_backpop_back操作。O(N)
  • vector支持随机访问元素,可像普通数组一样使用索引访问数据。使用[]运算符或at()函数可以获得指定位置的元素。O(1)
  • vector是一个模板类,可以存储任意类型的对象,包括内置类型和自定义类。
  • vector会自动处理内存分配和释放,不需要手动管理内存。
  • vector的迭代器用于遍历容器中的元素,可以进行正向和反向遍历。
  • vector可以通过拷贝构造函数和赋值运算符进行复制和赋值,使得可以方便地创建副本或对容器进行复制。
  • vector的缺点:在尾部添加或删除元素时效率较高,但在中间或开头位置插入或删除元素会导致数据的移动,影响性能。

在这里插入图片描述

构造函数

  1. 默认构造函数:创建一个空的vector对象。
    vector();
    
  2. 带初始大小和值的构造函数:创建一个包含count个元素的vector对象,每个元素都被初始化为value的副本。
    vector(size_type count, const T& value = T());
    
  3. 带初始范围的构造函数:创建一个包含范围在[first, last)内的元素的vector对象。可以使用迭代器、指针或者数组来指定范围。
    template <class InputIterator>
    vector(InputIterator first, InputIterator last);
    
  4. 拷贝构造函数:根据另一个vector对象other创建一个新的vector对象,包含与other相同的元素。
    vector(const vector& other);
    
  5. 移动构造函数:根据另一个可移动的vector对象other创建一个新的vector对象, other中的元素会被移动到新的vector对象中,原vector对象将处于有效但未指定状态。
    vector(vector&& other);
    
  6. 初始化列表构造函数:使用初始化列表初始化一个vector对象,列表中的元素被复制到vector中。
    vector(std::initializer_list<T> init);
    

示例:

// 1. 创建空vector; 常数复杂度
vector<int> v0;
// 这句代码可以使得向vector中插入前3个元素时,保证常数时间复杂度
v0.reserve(3);

// 2. 创建一个初始空间为3的vector,其元素的默认值是0; 线性复杂度
vector<int> v1(3);

// 3. 创建一个初始空间为3的vector,其元素的默认值是2; 线性复杂度
vector<int> v2(3, 2);

// 4. 创建一个初始空间为3的vector,其元素的默认值是1,
// 并且使用v2的空间配置器; 线性复杂度
vector<int> v3(3, 1, v2.get_allocator());

// 5. 创建一个v2的拷贝vector v4, 其内容元素和v2一样; 线性复杂度
vector<int> v4(v2);

// 6. 创建一个v4的拷贝vector v5,其内容是{v4[1], v4[2]}; 线性复杂度
vector<int> v5(v4.begin() + 1, v4.begin() + 3);

// 7. 移动v2到新创建的vector v6,不发生拷贝; 常数复杂度; 需要 C++11
vector<int> v6(std::move(v2));  // 或者 v6 = std::move(v2);

赋值

  1. 使用等号赋值:将另一个vector的内容赋值给当前的vector。

    vector<T>& operator=(const vector<T>& other);
    

    示例:

    vector<int> v1;
    vector<int> v2 = {1, 2, 3};
    v1 = v2; // 将v2中的元素赋值给v1
    
    for (int num : v1) {
        cout << num << " ";
    }
    // 输出结果: 1 2 3
    
  2. 使用assign()函数赋值:接受输入迭代器范围或元素数量与值,并用其进行赋值。

    void assign(InputIt first, InputIt last);
    void assign(size_type count, const T& value);
    

    示例:

    vector<int> v1;
    vector<int> v2 = {4, 5, 6};
    v1.assign(v2.begin(), v2.end()); // 将v2中指定范围内的元素赋值给v1
    
    for (int num : v1) {
    	cout << num << " ";
    }
    // 输出结果: 4 5 6
    
    vector<int> v3;
    v3.assign(3, 100); 	// 将v3中的元素数量设置为3,并将每个元素的值设置为100
    
    for (int num : v3) {
    	cout << num << " ";
    }
    // 输出结果: 100 100 100
    
  3. 使用拷贝构造函数进行赋值:使用另一个vector创建一个新的vector,并将其元素拷贝到新的vector中。

    vector(const vector& other);
    

    示例:

    vector<int> v2 = {7, 8, 9};
    vector<int> v1(v2); // 创建一个新的vector v1,并将v2中的元素拷贝给v1
    
    for (int num : v1) {
    	cout << num << " ";
    }
    // 输出结果: 7 8 9
    
  4. 使用初始化列表进行赋值:使用初始化列表创建一个新的vector,并将列表中的元素赋值给它。

    vector(std::initializer_list<T> init);
    

    示例:

    vector<int> v1 = {10, 11, 12}; // 使用初始化列表赋值给v1
    
    for (int num : v1) {
        cout << num << " ";
    }
    // 输出结果: 10 11 12
    
  5. 二维初始化

    vector<int> v[5];//定义可变长二维数组
    //注意:行不可变(只有5行), 而列可变,可以在指定行添加元素
    //第一维固定长度为5,第二维长度可以改变
    
    //初始化二维均可变长数组
    vector<vectot<int>> v;//定义一个行和列均可变的二维数组
    
    //行列长度均固定 n + 1行 m + 1列初始值为0
    vector<vector<int>> a(n + 1, vector<int>(m + 1, 0));
    
    //c++17或者c++20支持的形式(不常用)
    vector a(n + 1, vector(m + 1, 0));
    

方法函数

方法 含义
长度/容量
size() 返回当前vector中元素的数量。 O(1)
empty() 检查当前vector是否为空。如果为空,返回true;否则返回false。 O(1)
resize(n, v) 改变数组大小为n,新增空间数值赋为v,如果没有默认赋值为0
max_size() 返回容器的最大可能长度。
capacity() 返回vector当前所分配的存储空间的容量(即可以容纳的元素数量),而不是实际存储的元素数量。一般情况下,容量会大于或等于size()函数返回的大小。
reserve(n) 修改vector的容量,确保存储空间至少能容纳指定的元素数量n。如果n小于当前容量,则函数调用没有任何效果。如果n大于当前容量,则会重新分配足够的存储空间以适应新的容量。
shrink_to_fit() 使得 vector 的容量与长度一致,多退但不会少。
访问元素
at(pos) 返回容器中下标为 pos 的引用。如果数组越界抛出 std::out_of_range 类型的异常。O(1)
front() 返回首元素的引用。O(1)
back() 返回末尾元素的引用。O(1)
data() 返回指向数组第一个元素的指针。
增删元素
push_back(element) 在尾部加一个数据O(1)
emplace_back(element) 在尾部构造一个数据O(1) (C++11)
insert(it, x) 向任意迭代器it插入一个元素x ,O(N)
pop_back() 删除最后一个数据O(1)
erase(first,last) 删除[first,last)的所有元素,O(N)
clear() 清除所有元素。仅清空了大小,容量还是存在的。
迭代器
begin() / cbegin() 返回指向首元素的迭代器(地址),其中 *begin = front
end() / cend() 返回指向数组尾端占位符的迭代器,注意是没有元素的。
rbegin() / crbegin() 返回指向逆向数组的首元素的逆向迭代器,可以理解为正向容器的末元素。*rbegin = back
rend() / crend() 返回指向逆向数组末元素后一位置的迭代器,对应容器首的前一个位置,没有元素。

含有字符 c 的为只读迭代器,你不能通过只读迭代器去修改 vector 中的元素的值。如果一个 vector本身就是只读的,那么它的一般迭代器和只读迭代器完全等价。只读迭代器自 C++11 开始支持。

存取元素

  1. at()访问

    //at()函数的原型:
    reference at(size_type pos);
    const_reference at(size_type pos) const;
    

    at()函数返回指定位置pos处的元素的引用。如果pos超出了有效范围(即大于等于size()),则抛出std::out_of_range异常。

    std::vector<int> nums = {1, 2, 3, 4, 5};
    
    for (size_t i = 0; i < nums.size(); ++i) {
    	std::cout << "Element at position " << i << ": " << nums.at(i) << std::endl;
    }
    
    //Element at position 0: 1
    //Element at position 1: 2
    //Element at position 2: 3
    //Element at position 3: 4
    //Element at position 4: 5
    
  2. 下标访问

    //operator[] 函数的原型:
    reference operator[](size_type pos);
    const_reference operator[](size_type pos) const;
    

    operator[]函数返回指定位置pos处的元素的引用。与at()函数不同的是,若pos超出有效范围,则行为是未定义的。也就是说at越界时会抛出异常,而[]越界不会抛出异常。

    std::vector<int> nums = {1, 2, 3, 4, 5};
    
    for (size_t i = 0; i < nums.size(); ++i) {
        std::cout << "Element at position " << i << ": " << nums[i] << std::endl;
    }
    
    //Element at position 0: 1
    //Element at position 1: 2
    //Element at position 2: 3
    //Element at position 3: 4
    //Element at position 4: 5
    
  3. 迭代器访问

    std::vector<int> nums = {1, 2, 3, 4, 5};
    std::vector<int>::iterator it;   
    for (it = nums.begin(); it != nums.end(); it++) {
        std::cout << "Element at position " << i << ": " << *it << std::endl;
    }
    
    //Element at position 0: 1
    //Element at position 1: 2
    //Element at position 2: 3
    //Element at position 3: 4
    //Element at position 4: 5
    
  4. data()访问

    //data()函数的原型:
    T* data();
    const T* data() const;
    

    data()函数返回指向容器存储数据的指针。这个指针允许对容器中的元素进行修改。

    std::vector<int> nums = {1, 2, 3, 4, 5};
    
    int* ptr = nums.data();
    
    for (size_t i = 0; i < nums.size(); ++i) {
        std::cout << "Element at position " << i << ": " << *(ptr + i) << std::endl;
    }
    
    //Element at position 0: 1
    //Element at position 1: 2
    //Element at position 2: 3
    
  5. C++11遍历

    只能遍历完整数组,如果要指定的内容进行遍历,需要另选方法。
    auto 能够自动识别并获取类型。

    vector<int> v;
    v.push_back(12);
    v.push_back(241);
    for(auto val : v) 
        cout << val << " "; // 12 241
    

增删元素

  1. push_back() / emplace_back() :将元素添加到vector的末尾。

    //原型
    void push_back(const T& value);
    void emplace_back(const T& value);
    
    std::vector<int> vec1;
    vec1.push_back(10); // 复制或移动整数10到vec的末尾
    
    std::vector<std::pair<int, double>> vec2;
    vec2.emplace_back(1, 2.2); // 直接在vec的末尾构造pair<int, double>(1, 2.2)
    

    push_backemplace_back的性能差异主要体现在元素的构造和复制/移动开销上。

    • 对于简单数据类型(如int、double等),两者的性能差异可能不明显,因为简单数据类型的复制/移动开销相对较小。
    • 但对于复杂数据类型(如自定义的类、结构体,尤其是包含动态内存分配的类型),emplace_back的性能优势则可能更加显著。
    #include <vector>
    #include <string>
    #include <iostream>
    
    class MyClass {
    public:
        std::string name;
        int value;
    
        MyClass(std::string n, int v) : name(std::move(n)), value(v) {
            std::cout << "MyClass constructed" << std::endl;
        }
    
        MyClass(const MyClass& other) : name(other.name), value(other.value) {
            std::cout << "MyClass copied" << std::endl;
        }
    
        MyClass(MyClass&& other) noexcept : name(std::move(other.name)), value(other.value) {
            std::cout << "MyClass moved" << std::endl;
        }
    };
    
    int main() {
        std::vector<MyClass> vec;
    
        // 使用push_back
        vec.push_back(MyClass("Example", 1)); // 构造 + 复制/移动
    
        // 使用emplace_back
        vec.emplace_back("Example", 2); // 直接构造
    
        return 0;
    }
    
  2. insert():在指定位置插入一个或多个元素。

    //原型
    iterator insert(iterator position, const T& value);                             // 在指定位置插入单个元素
    void insert(iterator position, size_type n, const T& value);                     // 在指定位置插入多个相同元素
    template <class InputIterator>
    void insert(iterator position, InputIterator first, InputIterator last);         // 在指定位置插入一个范围内的元素
    
    std::vector<int> nums = {1, 2, 3};
    auto it = nums.begin() + 1;  // 在第二个位置插入元素
    nums.insert(it, 4);          // 插入单个元素
    nums.insert(it, 2, 5);       // 插入多个相同元素
    
  3. pop_back():删除vector末尾的元素。

    //原型
    void pop_back();
    
    std::vector<int> nums = {1, 2, 3};
    nums.pop_back();  // 删除末尾元素,vector变为 {1, 2}
    
  4. erase():删除指定位置或范围内的元素。

    //原型
    iterator erase(iterator position);                          // 删除单个元素
    iterator erase(iterator first, iterator last);              // 删除一个范围内的元素
    
    std::vector<int> nums = {1, 2, 3, 4, 5};
    auto it = nums.begin() + 2;    // 删除第三个元素
    nums.erase(it);
    nums.erase(nums.begin(), nums.begin() + 2);  // 删除前两个元素,vector变为  {3, 4, 5}
    
  5. clear():删除容器中所有的元素。仅清空了大小,容量还是存在的。

底层实现细节

  • vector是动态数组,所以和数组一样拥有一段连续的内存空间,并且起始地址不变。
  • 因为vector地址空间是连续的,所以能高效的进行随机访问,时间复杂度为o(1)。
  • 在vector中插入和删除元素,需要对现有元素进行复制、移动,时间复杂度为o(n)。
  • 如果vector中存储的对象很大,或者构造函数复杂,那么插入等开销会很大。因为拷贝现有对象时需要调用拷贝构造函数。
  • vector扩容原理 + 新增元素:Vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就会分配一块更大的内存,将原来的数据复制过来,释放之前的内存,在插入新增的元素。注意不是在原来空间后直接增加空间
  • 对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。
  • 不同的编译器实现的扩容方式不一样,VS2015中以1.5倍扩容,GCC以2倍扩容。
Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐