vector(模拟实现)

vector模拟实现源代码

一、核心数据结构:内存布局与指针管理

vector 的底层实现依赖于三个关键指针,它们共同维护了动态数组的内存布局:

cpp 复制代码
iterator _start = nullptr;       // 指向数据起始位置
iterator _finish = nullptr;      // 指向最后一个有效元素的下一个位置
iterator _endofstorage = nullptr; // 指向已分配内存的末尾

其中 iterator 定义为 T*,即原生指针。这三个指针构成以下逻辑区间:

  • [ _start, _finish ):存放有效数据元素,是用户可访问的数据范围。
  • [ _finish, _endofstorage ):预留空间,用于后续插入操作,避免频繁扩容。
关键计算公式:
  • size() = _finish - _start:当前实际存储元素个数。
  • capacity() = _endofstorage - _start:当前可用总容量(未使用部分也计入)。

设计亮点:通过指针差值计算大小和容量,无需额外成员变量,高效且节省空间。


二、迭代器支持:兼容 STL 风格遍历

vector 提供完整的迭代器接口,支持标准算法和范围循环:

cpp 复制代码
iterator begin() { return _start; }
iterator end() { return _finish; }

const_iterator begin() const { return _start; }
const_iterator end() const { return _finish; }
  • begin() / end() 返回非 const 迭代器,允许修改元素。
  • const 版本返回 const_iterator(即 const T*),保证只读访问。
示例:支持范围 for 循环
cpp 复制代码
vector<int> v = {1, 2, 3};
for (auto x : v) {
    cout << x << " "; // 输出: 1 2 3
}

📌 说明auto 推导出的是 int&(非 const),若对 const vector 遍历,则自动变为 const int&


三、构造函数详解:多种初始化方式

1. 默认构造函数
cpp 复制代码
vector() = default;
  • 所有指针初始化为 nullptr
  • 初始 size = 0, capacity = 0
2. 拷贝构造函数(深拷贝)
cpp 复制代码
vector(const vector<T>& v) {
    reserve(v.capacity());
    for (const auto& elem : v) {
        push_back(elem);
    }
}
  • 先调用 reserve(v.capacity()) 分配足够空间。
  • 使用 push_back 逐个调用拷贝构造函数,确保对 stringunique_ptr 等复杂类型进行深拷贝
  • ❌ 不使用 memcpy,防止浅拷贝导致悬空指针或重复释放。

🔥 重点强调 :对于含堆内存的对象(如 std::string),必须逐元素复制,否则会引发严重错误。

3. 迭代器区间构造模板
cpp 复制代码
template <class InputIterator>
vector(InputIterator first, InputIterator last) {
    while (first != last) {
        push_back(*first++);
    }
}
  • 支持任意输入迭代器,包括原始指针、其他容器的迭代器。
  • 示例:
cpp 复制代码
int arr[] = {1, 2, 3};
vector<int> v(arr, arr + 3); // 构造 {1,2,3}

✅ 可以从 listset 等容器中提取数据构造 vector

4. 填充构造函数
cpp 复制代码
vector(int n, T val = T()) {
    resize(n, val);
}
  • 调用 resize(n, val) 扩容并填充默认值或指定值。
  • valstring,则每个元素都调用其构造函数。
5. initializer_list 构造函数
cpp 复制代码
vector(initializer_list<T> il) {
    reserve(il.size());
    for (const auto& elem : il) {
        push_back(elem);
    }
}
  • 支持 {1, 2, 3} 初始化语法。
  • 示例:
cpp 复制代码
vector<int> v = {10, 20, 30}; // 正确!

⚠️ 注意:initializer_list 构造函数优先级高于其他构造函数,因此不能写成 vector<int> v{10}; 以外的形式。

6. 赋值运算符:copy-and-swap 惯用法
cpp 复制代码
vector<T>& operator=(vector<T> v) {
    swap(v); // 交换资源,旧数据随临时对象析构自动释放
    return *this;
}
  • 参数按值传递 → 触发拷贝构造 → 创建副本。
  • swap(*this, v) 交换两个对象的三个指针。
  • 原对象的旧资源被销毁,新资源保留。

优点

  • 自动处理自赋值(v = v)。
  • 异常安全:若中间步骤抛异常,原对象不变。
  • 代码简洁,易于维护。

四、内存管理:动态扩容机制

核心扩容逻辑(仅当容量不足时触发)
cpp 复制代码
void reserve(size_t n) {
    if (n <= capacity()) return;

    T* new_start = new T[n]; // 动态分配新内存
    size_t old_size = size();

    // 逐元素移动(调用拷贝构造)
    for (size_t i = 0; i < old_size; ++i) {
        new_start[i] = std::move(_start[i]); // 移动语义优化
    }

    delete[] _start; // 释放旧内存

    _start = new_start;
    _finish = _start + old_size;
    _endofstorage = _start + n;
}
扩容策略:初始容量为 4,之后每次翻倍
cpp 复制代码
if (_endofstorage == _finish) {
    size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
    reserve(new_capacity);
}

均摊时间复杂度:O(1)

尽管单次扩容是 O(n),但长期来看,插入操作的平均开销仍为常数。

为什么不用 memcpy
cpp 复制代码
// ❌ 错误做法(浅拷贝风险)
memcpy(new_start, _start, sizeof(T) * size());

// ✅ 正确做法(深拷贝)
for (size_t i = 0; i < size(); ++i)
    new_start[i] = _start[i];
  • memcpy 仅适用于 POD 类型 (如 int, double)。
  • std::stringvector 等类类型,会导致多个对象共享同一块堆内存,析构时重复释放,崩溃!

五、元素操作:插入与删除的安全性保障

插入元素到指定位置 insert(pos, x)
cpp 复制代码
iterator insert(iterator pos, const T& x) {
    if (_finish == _endofstorage) {
        size_t len = pos - _start; // 记录偏移量
        reserve(capacity() == 0 ? 4 : capacity() * 2);
        pos = _start + len; // 重新定位迭代器(因扩容后地址变化)
    }

    // 从末尾开始后移元素,腾出位置
    for (iterator it = _finish; it != pos; --it) {
        *it = std::move(*(it - 1));
    }

    *pos = x;
    ++_finish;

    return pos;
}

🔑 关键点

  • 扩容前保存 pos 相对于 _start 的偏移量。
  • 扩容后重新计算 pos,避免迭代器失效。
  • 使用 std::move 提升性能(尤其对大对象)。
删除元素 erase(pos)
cpp 复制代码
iterator erase(iterator pos) {
    if (pos >= _finish) return _finish;

    // 从 pos+1 开始向前移动元素,覆盖被删元素
    for (iterator it = pos + 1; it != _finish; ++it) {
        *(it - 1) = std::move(*it);
    }

    --_finish;

    return pos; // 返回下一个位置的迭代器
}

符合 STL 规范erase 返回被删除元素的下一个位置。

  • 支持经典模式:for (auto it = v.begin(); it != v.end(); ) { if (*it % 2 == 0) it = v.erase(it); else ++it; }

六、辅助工具函数:实用性强,细节到位

函数 实现 作用
operator[] assert(pos < size()); return _start[pos]; 下标访问,带越界检查
empty() return _start == _finish; 判断是否为空
clear() _finish = _start; 清空内容,不释放内存
resize(n, val) if (n > size()) reserve(n); _finish = _start + n; fill(_finish - n, _finish, val); 扩展或截断
swap(vector&) std::swap(_start, other._start); std::swap(_finish, other._finish); std::swap(_endofstorage, other._endofstorage); 快速交换资源

💡 clear() 不释放内存,是为了提高后续插入效率(避免反复申请)。


七、测试代码解析(test.cpp)------验证功能完整性

test1:基本 push_back + 打印
cpp 复制代码
void test1() {
    vector<int> v;
    for (int i = 1; i <= 5; ++i) {
        v.push_back(i);
    }
    print(v); // 1 2 3 4 5
}
test2:pop_back 逐步删除
cpp 复制代码
void test2() {
    vector<int> v = {1, 2, 3, 4, 5};
    while (!v.empty()) {
        cout << v.back() << " ";
        v.pop_back();
    }
    // 输出: 5 4 3 2 1
}
test3:insert 头部与中间插入
cpp 复制代码
void test3() {
    vector<int> v = {10, 20, 30};
    v.insert(v.begin(), 5);           // 头部插入
    v.insert(v.begin() + 2, 15);      // 中间插入
    print(v); // 5 10 15 20 30
}
test4:erase 单个删除
cpp 复制代码
void test4() {
    vector<int> v = {1, 2, 3, 4, 5};
    auto it = v.erase(v.begin() + 2); // 删除 3
    cout << *it; // 4
}
test5:删除所有偶数(经典用法)
cpp 复制代码
void test5() {
    vector<int> v = {1, 2, 3, 4, 5, 6};
    for (auto it = v.begin(); it != v.end(); ) {
        if (*it % 2 == 0) {
            it = v.erase(it); // erase 返回下一个迭代器
        } else {
            ++it;
        }
    }
    print(v); // 1 3 5
}

精髓erase 返回下一个有效位置,使得 for 循环不会跳过元素。

test6:resize 测试
cpp 复制代码
void test6() {
    vector<int> v = {1, 2, 3};
    v.resize(5);         // 扩大至 5,补 0
    v.resize(2, 99);     // 截断至 2,不传值则补 0
    print(v); // 1 2
}
test7:综合构造测试
cpp 复制代码
void test7() {
    vector<int> v1(3, 10);                    // 填充构造
    vector<int> v2({1, 2, 3});                // initializer_list
    vector<int> v3(v1.begin(), v1.end());     // 区间构造
    vector<int> v4 = v1;                      // 拷贝构造
    v4 = v3;                                  // 赋值运算符
}
test8:string 类型测试(深拷贝安全性)
cpp 复制代码
void test8() {
    vector<string> v1 = {"hello", "world"};
    vector<string> v2 = v1; // 深拷贝!每个 string 都独立拥有自己的堆内存
    v1[0] = "hi";
    cout << v2[0]; // "hello" ------ 未受影响
}

结论:所有操作均保证深拷贝,对复杂类型安全。


八、设计亮点总结(强化版)

亮点 说明
不使用 memcpy 保证对 std::string 等类类型的深拷贝,杜绝内存错误
copy-and-swap 惯用法 赋值运算符简洁、异常安全、自动处理自赋值
插入时重定位迭代器 扩容后重新计算 pos 偏移量,避免迭代器失效
erase 返回迭代器 符合 STL 规范,支持安全删除循环
均摊 O(1) 插入 2 倍扩容策略,降低频繁分配成本
支持任意迭代器构造 可从 arraylistset 等构造 vector
RAII 内存管理 所有资源由对象自身管理,无泄漏风险

九、完整简化版 vector 实现框架(核心骨架)

cpp 复制代码
template <typename T>
class vector {
private:
    T* _start = nullptr;
    T* _finish = nullptr;
    T* _endofstorage = nullptr;

public:
    using iterator = T*;
    using const_iterator = const T*;

    // 构造函数
    vector() = default;
    vector(const vector& v);
    template <class InputIterator>
    vector(InputIterator first, InputIterator last);
    vector(int n, T val = T());
    vector(std::initializer_list<T> il);

    // 赋值运算符
    vector& operator=(vector v) {
        swap(v);
        return *this;
    }

    // 迭代器
    iterator begin() { return _start; }
    iterator end() { return _finish; }
    const_iterator begin() const { return _start; }
    const_iterator end() const { return _finish; }

    // 元素操作
    void push_back(const T& x);
    void pop_back();
    iterator insert(iterator pos, const T& x);
    iterator erase(iterator pos);

    // 工具函数
    size_t size() const { return _finish - _start; }
    size_t capacity() const { return _endofstorage - _start; }
    bool empty() const { return _start == _finish; }
    void clear() { _finish = _start; }
    void resize(size_t n, T val = T());
    void reserve(size_t n);
    void swap(vector& other);

    // 下标访问
    T& operator[](size_t pos) {
        assert(pos < size());
        return _start[pos];
    }
    const T& operator[](size_t pos) const {
        assert(pos < size());
        return _start[pos];
    }
};

十、结语:为何学习这个实现?

理解 vector 底层原理,不仅能写出更高效的代码,还能:

  • 避免踩坑(如迭代器失效、浅拷贝崩溃);
  • 在面试中展示深度;
  • 为自定义容器打下坚实基础;
  • 更好地使用 STL 算法与泛型编程。

🎯 掌握它,就是掌握了现代 C++ 的基石之一。

相关推荐
鱼听禅1 小时前
C# 学习笔记-使用 类型化客户端搭建HTTP客户端服务
学习·http·c#·工厂方法模式
高亦真1 小时前
今天是学习嵌入式的第38天
linux·学习·算法
蒸蒸yyyyzwd1 小时前
cpp 选手备战秋招学习笔记 day36
c++
用户69586106727112 小时前
从源码到加载器:__attribute__((constructor)) / __attribute__((destructor))的跨平台实现原理详解
c++
传奇开心果编程2 小时前
【Rust入门知识点学与练】第33课:异步编程入门(async/await)强化
开发语言·学习·rust
传奇开心果编程2 小时前
【Rust入门知识点学与练】第34课:所有权与借用
开发语言·学习·rust
AAA代码批发商2 小时前
DAY43 ARM 体系结构学习笔记|嵌入式入门,从硬件组件到流水线、内存与栈帧
arm开发·笔记·学习
彧azz3 小时前
初学Unity:编辑器
笔记·学习·unity·游戏引擎
余额瞒着我当琳3 小时前
C++多态深入解析:多态概念,多态实现条件,虚函数表与内存分布,常见问题及注意事项
c++·算法