vector的特性与优点

C++ 中的 vector 是一种序列容器,它允许你在运行时动态地插入和删除元素。
vector 是基于数组的数据结构,但它可以自动管理内存(析构函数的好处),这意味着你不需要手动分配和释放内存。
vector 是 C++ 标准模板库(STL)的一部分,提供了灵活的接口和高效的操作。

动态大小:vector 的大小可以根据需要自动增长和缩小。
物理连续(注:有些容器只是逻辑连续,物理上并不连续):vector 中的元素在内存中是连续存储的,这使得访问元素非常快速。
支持迭代器:vector 可以被迭代,你可以使用循环(如 for 循环)来访问它的元素(也可以使用范围for循环进行遍历)
泛式实现:基于模版的好处,vector 可以存储任何类型的元素,包括内置类型、对象、指针等。

使用场景

当你需要一个可以动态增长和缩小的数组时。
当你需要频繁地在序列的末尾添加或移除元素时(O(1)复杂度)。
当你需要一个可以高效随机访问元素的容器时(物理连续,重载operator[],所以支持随机访问)。

vector常用API

常用API


#include<iostream>
#include<vector>

int main()
{
	//构造方法:
	std::vector<int> myVector;        // 创建一个存储整数的空 vector

	std::vector<int> myVector2(5);     // 创建一个包含 5 个整数的 vector,每个值都为默认值(0)
	std::vector<int> myVector3(5, 10); // 创建一个包含 5 个整数的 vector,每个值都为 10

	std::vector<int> vec; // 默认初始化一个空的 vector
	std::vector<int> vec2 = { 1, 2, 3, 4 }; // 初始化一个包含元素的 vector

	//push_back函数
	myVector.push_back(7); // 将整数 7 添加到 vector 的末尾
	myVector.push_back(8); // 将整数 8 添加到 vector 的末尾
	myVector.push_back(5); // 将整数 5 添加到 vector 的末尾
	myVector.push_back(3); // 将整数 3 添加到 vector 的末尾

	//operator[] 与 at 函数 ,体系了迭代性
	int x = myVector[0];     // 获取第一个元素
	int y = myVector.at(1); // 获取第二个元素
	//注意operator[] 和 at的显著区别就是 ,at会抛出异常
  //这是面向对象处理异常的方法,异常篇章会详细讲解 
    
	//size函数
	int size = myVector.size(); // 获取 vector 中的元素数量

	//vector支持迭代器访问
	for (auto it = myVector.begin(); it != myVector.end(); ++it) {
		std::cout << *it << " ";
	}

	//范围for循环(范围for循环本质上基于迭代器实现)
	for (int element : myVector) {
		std::cout << element << " ";
	}

	//clear函数
	myVector.clear(); // 清空 vector
	return 0;
}

完整的使用实例

#include <iostream>
#include <vector>

int main() {
    // 创建一个空的整数向量
    std::vector<int> myVector;

    // 添加元素到向量中
    myVector.push_back(3);
    myVector.push_back(7);
    myVector.push_back(11);
    myVector.push_back(5);

    // 访问向量中的元素并输出
    std::cout << "Elements in the vector: ";
    for (int element : myVector) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    // 访问向量中的第一个元素并输出
    std::cout << "First element: " << myVector[0] << std::endl;

    // 访问向量中的第二个元素并输出
    std::cout << "Second element: " << myVector.at(1) << std::endl;

    // 获取向量的大小并输出
    std::cout << "Size of the vector: " << myVector.size() << std::endl;

    // 删除向量中的第三个元素
    myVector.erase(myVector.begin() + 2);

    // 输出删除元素后的向量
    std::cout << "Elements in the vector after erasing: ";
    for (int element : myVector) {
        std::cout << element << " ";
    }
    std::cout << std::endl;

    // 清空向量并输出
    myVector.clear();
    std::cout << "Size of the vector after clearing: " << myVector.size() << std::endl;

    return 0;
}

上面列举的是vector最常用的api
更多api手册在cplusplus官方网站可以找到
链接: cplusplus

vector部分实现原理分析

底层存储:vector不仅仅逻辑上是连续,物理上也是连续的
所以本质上,在构造函数中,会new出一段空间作为初始空间
在析构函数中delete 释放这些空间

增容原理 :vector是可变大的弹性数组,所以当空间满了又要插入新元素时,就要进行增容。
实现 : 开辟new 一个原大小数倍的新数组,使用这个新数组,把原数组的值赋值给新数组,然后detele新数组

拷贝构造函数和operator=赋值函数。vector的拷贝构造函数和operator=赋值函数必定是 深拷贝 ,不然就会导致两个vector实例对象指向同一个位置,导致析构函数重复释放同一个位置

其他api,有了前3点实现vector的核心思想,要实现push_back,pop_back等api就非常简单了。

注 :如果实在不理解深拷贝的原理,可以参考我的博客了解深拷贝和浅拷贝
[C++类和对象] :类的默认成员函数

#include <iostream>
using namespace std;

template<class T>
class MyVector {
public:
    // 1. 构造函数
    // 默认构造:初始化为空
    MyVector() : _data(nullptr), _size(0), _capacity(0) {}

    // 带初始大小的构造:创建n个元素的空间
    MyVector(size_t n) 
    {
        _data = new T[n];  // 物理连续空间
        _size = n;         // 元素个数为n
        _capacity = n;     // 容量为n
    }


    // 2. 拷贝构造函数(深拷贝)
    MyVector(const MyVector<T>& other)
     {
        _capacity = other._capacity;  // 复制容量
        _size = other._size;          // 复制元素个数
        if (_capacity > 0) 
        {
            _data = new T[_capacity];  // 新开辟独立空间
            // 拷贝所有元素
            for (size_t i = 0; i < _size; ++i) 
            {
                _data[i] = other._data[i];
            }
        } else 
        {
            _data = nullptr;  // 空vector处理
        }
    }


    // 3. 赋值运算符重载(深拷贝)
    MyVector<T>& operator=(const MyVector<T>& other)
     {
        // 防止自赋值(如v1 = v1)
        if (this != &other) 
        {
            // 释放当前空间
            delete[] _data;

            // 复制容量和元素个数
            _capacity = other._capacity;
            _size = other._size;

            if (_capacity > 0) 
            {
                _data = new T[_capacity];  // 新开辟独立空间
                // 拷贝所有元素
                for (size_t i = 0; i < _size; ++i) 
                {
                    _data[i] = other._data[i];
                }
            } 
            else 
            {
                _data = nullptr;  // 空vector处理
            }
        }
        return *this;
    }


    // 4. 析构函数:释放空间
    ~MyVector() 
    {
        delete[] _data;    // 释放连续空间
        _data = nullptr;   // 避免野指针
        _size = 0;
        _capacity = 0;
    }


    // 5. 增容机制:当空间不足时扩容
    void reserve(size_t new_capacity) 
    {
        if (new_capacity > _capacity) {
          // 仅当新容量更大时扩容
            T* new_data = new T[new_capacity];  // 开辟新空间

            // 拷贝旧元素到新空间
            if (_data != nullptr) 
            {
                for (size_t i = 0; i < _size; ++i) {
                
                    new_data[i] = _data[i];
                }
                delete[] _data;  // 释放旧空间
            }

            _data = new_data;       // 指向新空间
            _capacity = new_capacity;  // 更新容量
        }
    }


    // 6. 尾插元素
    void push_back(const T& val) 
    {
        // 若空间满了则扩容(原容量0则扩为1,否则扩为2倍)
        if (_size >= _capacity) 
        {
            size_t new_capacity = (_capacity == 0) ? 1 : _capacity * 2;
            reserve(new_capacity);
        }
        _data[_size] = val;  // 插入新元素
        _size++;             // 更新元素个数
    }


    // 7. 基本访问接口
    size_t size() const { return _size; }         // 获取元素个数
    size_t capacity() const { return _capacity; } // 获取容量
    T& operator[](size_t index) { return _data[index]; } // 访问元素
    const T& operator[](size_t index) const { return _data[index]; }


private:
    T* _data;       // 指向物理连续的存储空间
    size_t _size;   // 当前元素个数
    size_t _capacity; // 容量(最大可存储元素个数)
};

vector的缺点

空间浪费 :vector的空间满则扩容的机制注定了他有浪费空间的风险。取一个极端案例。vector原始容量是100个元素,但是我现在要插入101个元素,vector会扩容到200个元素的容量。这样一来就浪费了99单位空间,几乎是浪费了50%

头插和中间插入效率低 :要在头上插入,或者中间插入,后面的元素必须整体挪一个位置。复杂度为O(N)
插入一个元素复杂度就是O(N),那么插入N个复杂度就来到了恐怖的
O(N^2),这会极大降低效率。
这就是为何C++官方没有支持vector的push_front

扩容过程的性能开销大
vector 扩容时需要经历「开辟新空间→拷贝旧元素→释放旧空间」三个步骤。对于存储大量元素或大对象(如大型结构体、类实例)的 vector,拷贝旧元素的过程会产生巨大的性能开销(时间复杂度为 O (N))。尤其是在频繁插入元素导致多次扩容时,这种开销会被放大,可能引发程序运行时的性能波动(比如突然的卡顿)

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐