找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

320

积分

0

好友

40

主题
发表于 2025-12-26 10:08:37 | 查看: 33| 回复: 0

本文详细介绍了C++标准模板库(STL)中的vector容器。作为最常用的序列容器之一,vector提供了类似动态数组的功能,支持高效的元素访问与动态扩容。我们将系统学习其常用接口、核心特性,并深入探讨迭代器失效问题,最后通过一个完整的模拟实现来剖析其底层原理。

一、标准库中的vector类模板

vector是一个表示可变大小数组的序列容器。它像string一样,底层通过动态开辟的数组实现。

核心特点

  • 高效访问:可以使用下标(operator[])随机访问元素,效率与数组相当。
  • 动态扩容:其大小可以动态变化。当插入新元素导致空间不足时,容器会自动分配更大的内存空间,并将原有元素“迁移”过去。为了平衡性能,扩容通常按指数级进行,使得在尾部插入元素的均摊时间复杂度为O(1)。
  • 尾部操作高效:在末尾进行插入(push_back)和删除(pop_back)操作效率很高。在中间或头部进行插入删除则效率较低。
  • 连续存储:元素在内存中是连续存储的,这带来了良好的缓存局部性,也是其高效访问的根源。

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 1

:使用vector类必须包含头文件#include <vector>,并使用std命名空间。你可以将 string 类近似理解为 vector<char> 的一个特化版本,两者在接口设计和使用上有很多相似之处,这对于理解算法与数据结构中的容器概念很有帮助。

二、vector类的常用接口

构造函数

vector提供了多种构造函数来满足不同初始化需求。

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 2

int main()
{
    vector<int> v; // (1) 默认无参构造

    vector<int> v1(4); // (2) 创建4个元素,默认值为0
    // vector<int> v2 = 4; // 错误!explicit构造函数禁止隐式转换
    vector<int> v3(4, 10); // (2) 创建4个元素,每个值指定为10

    vector<int> v4(v1); // (4) 拷贝构造

    vector<int> v5(v1.begin(), v1.end()); // (3) 使用迭代器范围初始化

    // (5) C++11支持的列表初始化
    vector<int> v6{ 1,2,3,4 };
    vector<int> v7 = { 1,2,3,4 }; // 与v6等价
}

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 3

容量操作函数

容量相关函数用于管理vector的内存和大小。

函数名 功能说明
size 获取容器中当前元素的个数。
capacity 获取容器的当前容量(总共能容纳多少元素而不扩容)。
resize 调整容器中元素的数量(size)。
reserve 调整容器的容量(capacity),预留空间。
empty 判断容器是否为空。
clear 清空所有元素(size变为0,capacity通常不变)。
int main()
{
    vector<int> v(4, 10);
    cout << v.size() << " " << v.capacity() << endl; // 输出:4 4

    v.reserve(10); // 预留容量为10
    cout << v.size() << " " << v.capacity() << endl; // 输出:4 10

    v.resize(20); // 将大小调整为20,新增元素默认初始化为0
    cout << v.size() << " " << v.capacity() << endl; // 输出:20 20

    cout << v.empty() << endl; // 输出:0 (false)
    return 0;
}

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 4

访问及遍历操作函数

vector提供了多种灵活的方式来访问和遍历元素。

函数名 功能说明
operator[] 像数组一样通过下标访问,不进行边界检查。
at 访问指定位置元素,会进行边界检查,越界则抛出out_of_range异常。
front 访问第一个元素。
back 访问最后一个元素。
begin / end 获取指向第一个元素的迭代器和末尾元素之后位置的迭代器。
rbegin / rend 获取反向迭代器。
int main()
{
    vector<int> v{1,2,3,4}; // 列表初始化

    // 1. 下标遍历
    for (size_t i = 0; i < v.size(); i++) {
        cout << v[i] << " ";
    }
    cout << endl;

    // 2. 正向迭代器遍历
    vector<int>::iterator it = v.begin(); // 也可使用 auto it = v.begin();
    while (it != v.end()) {
        cout << *it << " ";
        ++it;
    }
    cout << endl;

    // 3. 反向迭代器遍历
    for (auto rit = v.rbegin(); rit != v.rend(); ++rit) {
        cout << *rit << " ";
    }
    cout << endl;

    // 4. 范围for循环 (C++11)
    for (auto e : v) {
        cout << e << " ";
    }
    return 0;
}

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 5

增删查改

这些是vector最核心的修改操作。

函数名 功能说明
push_back 在容器尾部插入一个元素。
pop_back 删除容器尾部的一个元素。
insert 在指定迭代器位置之前插入一个或多个元素。
erase 删除指定迭代器位置的一个元素,或一个迭代器区间的元素。
swap 交换两个vector容器的内容。
find 注意:这是<algorithm>中的通用算法,不是vector的成员函数。
#include <algorithm> // 使用std::find, std::sort

int main()
{
    vector<int> v(4, 10);
    vector<int> v2(2, 3);

    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.pop_back(); // 删除末尾的3

    v.insert(v.begin(), 1); // 在开头插入1
    v.insert(v.begin(), 5, 2); // 在开头插入5个2

    v.erase(v.begin()); // 删除第一个元素
    v.erase(v.begin(), v.begin() + 5); // 删除区间[first, last)的元素

    v.swap(v2); // 成员函数swap
    swap(v, v2); // 非成员函数swap,效果相同

    auto it = find(v.begin(), v.end(), 2); // 查找值为2的元素,返回迭代器
    sort(v.begin(), v.end()); // 对vector进行排序
    return 0;
}

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 6

迭代器失效问题

迭代器本质上是一个指针或对指针的封装,它指向容器中的某个元素。迭代器失效意味着迭代器所指向的内存空间变得无效(例如被释放),继续使用它将导致未定义行为(通常是程序崩溃)。

导致失效的常见操作

  1. 引起底层空间重新分配的操作:如reserveresizepush_back(当容量不足时)、insert(当容量不足时)等。这些操作可能导致内存地址改变,使所有迭代器、指针、引用失效。
  2. 指定位置的删除操作erase操作会删除元素,使得被删除元素及其之后位置的迭代器失效。

关键点:解决迭代器失效的方法是,在执行可能引起失效的操作后,对迭代器进行重新赋值(例如重新调用begin()find()等获取新的迭代器)。

int main()
{
    vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    auto pos = find(v.begin(), v.end(), 2);
    if (pos != v.end()) {
        // v.insert(pos, 20); // 插入操作可能导致扩容,使pos失效
        v.erase(pos);        // 删除操作使pos及其后迭代器失效
    }

    // 失效后,下面的访问是危险的!
    // cout << *pos << endl; // 未定义行为
    // *pos = 10;            // 未定义行为

    // 正确做法:在insert/erase后,如果需要继续使用,应更新pos
    // pos = v.insert(pos, 20); // insert会返回指向新插入元素的迭代器
    // pos = v.erase(pos);      // erase会返回指向被删除元素下一个位置的迭代器
    return 0;
}

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 7

三、vector深度剖析及模拟实现

理解vector的底层实现是掌握其特性和规避陷阱的关键。下面我们实现一个简化版的vector类模板。

核心框架与迭代器

namespace myvector
{
    template<class T>
    class vector
    {
    public:
        // 迭代器就是原生指针
        typedef T* iterator;
        typedef const T* const_iterator;

        iterator begin() { return _start; }
        iterator end() { return _finish; }
        const_iterator begin() const { return _start; }
        const_iterator end() const { return _finish; }

    private:
        iterator _start = nullptr;      // 指向数组首元素
        iterator _finish = nullptr;     // 指向最后一个有效元素的下一个位置 (size)
        iterator _endofstorage = nullptr; // 指向存储空间尾部的下一个位置 (capacity)
    };
}

构造函数与析构函数

public:
    // 默认构造
    vector()
        :_start(nullptr)
        ,_finish(nullptr)
        ,_endofstorage(nullptr) 
    {}

    // 迭代器范围构造
    template <class InputIterator>
    vector(InputIterator first, InputIterator last)
        : _start(nullptr)
        , _finish(nullptr)
        , _endofstorage(nullptr)
    {
        while (first != last) {
            push_back(*first);
            ++first;
        }
    }

    // 拷贝构造(现代写法)
    vector(const vector<T>& v)
        :_start(nullptr)
        ,_finish(nullptr)
        ,_endofstorage(nullptr)
    {
        vector<T> tmp(v.begin(), v.end()); // 用v的数据构造tmp
        swap(tmp); // 交换*this和tmp的资源
    }

    // 赋值运算符重载(现代写法)
    vector<T>& operator=(vector<T> v) { // 传参时调用拷贝构造
        swap(v); // 交换*this和副本v的资源
        return *this; // 出作用域后v(原*this资源)被析构
    }

    // 析构函数
    ~vector() {
        delete[] _start;
        _start = _finish = _endofstorage = nullptr;
    }

容量与大小操作

public:
    size_t size() const {
        return _finish - _start;
    }
    size_t capacity() const {
        return _endofstorage - _start;
    }

    void reserve(size_t n) {
        if (n > capacity()) {
            size_t old_sz = size();
            T* tmp = new T[n]; // 开辟新空间

            // 拷贝数据(注意:如果T是自定义类型且深拷贝,memcpy可能有问题)
            // 这里使用循环构造更安全
            for(size_t i = 0; i < old_sz; ++i) {
                tmp[i] = _start[i];
            }

            delete[] _start; // 释放旧空间
            _start = tmp;
            _finish = _start + old_sz;
            _endofstorage = _start + n;
        }
    }

    void resize(size_t n, const T& val = T()) {
        if (n <= size()) {
            _finish = _start + n; // 缩小
        } else {
            if (n > capacity()) {
                reserve(n);
            }
            iterator it = _finish;
            _finish = _start + n;
            while (it < _finish) {
                *it = val;
                ++it;
            }
        }
    }

元素访问与修改

public:
    T& operator[](size_t pos) {
        assert(pos < size());
        return _start[pos];
    }
    const T& operator[](size_t pos) const {
        assert(pos < size());
        return _start[pos];
    }

    void push_back(const T& x) {
        // 常规写法:
        // if (_finish == _endofstorage) {
        //     size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
        //     reserve(new_capacity);
        // }
        // *_finish = x;
        // ++_finish;

        // 复用insert
        insert(end(), x);
    }

    void pop_back() {
        // 常规写法:
        // assert(_finish > _start);
        // --_finish;

        // 复用erase
        erase(end() - 1);
    }

插入与删除(核心与失效处理)

public:
    iterator insert(iterator pos, const T& val) {
        assert(pos >= _start && pos <= _finish); // 可以等于_finish(即尾插)

        // 检查扩容
        if (_finish == _endofstorage) {
            size_t len = pos - _start; // 记录pos的相对位置!!!
            size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
            reserve(new_capacity);
            pos = _start + len; // 关键!扩容后更新pos,防止迭代器失效
        }

        // 从后向前移动元素
        iterator end = _finish - 1;
        while (end >= pos) {
            *(end + 1) = *end;
            --end;
        }

        *pos = val;
        ++_finish;
        return pos; // 返回指向新插入元素的迭代器
    }

    iterator erase(iterator pos) {
        assert(pos >= _start && pos < _finish);

        iterator it = pos + 1;
        while (it != _finish) {
            *(it - 1) = *it;
            ++it;
        }
        --_finish;
        return pos; // 返回指向被删除元素下一个位置的迭代器
    }

工具函数

public:
    void swap(vector<T>& v) {
        std::swap(_start, v._start);
        std::swap(_finish, v._finish);
        std::swap(_endofstorage, v._endofstorage);
    }

    bool empty() const {
        return _start == _finish;
    }

C++ vector容器详解:从入门使用到模拟实现动态数组 - 图片 - 8

模拟实现总结
这个简化实现涵盖了vector最核心的机制:动态数组管理、迭代器、容量增长、以及在insert/erase中处理迭代器失效。理解这些底层细节不仅能帮你更好地使用STL容器,也是提升系统编程中内存管理能力的重要一步。注意,生产级别的实现会更加复杂,需处理异常安全、优化拷贝、支持分配器等。




上一篇:Kubernetes DaemonSet 详解:实现节点守护、日志收集与存储插件部署的操作指南
下一篇:使用立创EDA设计ESP32-C3开发板:从原理图到PCB全流程详解
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-11 18:02 , Processed in 0.276463 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表