de风——【从零开始学 C++】(十)vector的模拟实现

目录

前言

[一、vector 的核心结构](#一、vector 的核心结构)

[1.1 简介作用](#1.1 简介作用)

[1.2 【代码实现】核心结构定义](#1.2 【代码实现】核心结构定义)

[1.3 新手坑点提醒](#1.3 新手坑点提醒)

二、默认成员函数实现

[2.1 无参构造函数](#2.1 无参构造函数)

简介作用

【代码实现】无参构造函数

[2.2 带 n 个 val 的构造函数](#2.2 带 n 个 val 的构造函数)

简介作用

[【代码实现】带 n 个 val 的构造函数](#【代码实现】带 n 个 val 的构造函数)

新手坑点提醒

[2.3 迭代器区间构造](#2.3 迭代器区间构造)

简介作用

【代码实现】迭代器区间构造

[2.4 拷贝构造函数【面试高频考点】](#2.4 拷贝构造函数【面试高频考点】)

简介作用

【代码实现】拷贝构造函数(深拷贝)

[经典 Bug 和坑点](#经典 Bug 和坑点)

[2.5 析构函数](#2.5 析构函数)

简介作用

【代码实现】析构函数

[2.6 赋值运算符重载【面试高频考点】](#2.6 赋值运算符重载【面试高频考点】)

简介作用

【代码实现】传统写法

【代码实现】现代写法(推荐!)

两种写法对比总结

三、容量相关接口实现

[3.1 size()、capacity()、empty()](#3.1 size()、capacity()、empty())

简介作用

【代码实现】基础容量查询

[3.2 reserve ()【面试高频考点】](#3.2 reserve ()【面试高频考点】)

简介作用

[【代码实现】reserve 扩容函数](#【代码实现】reserve 扩容函数)

[经典 Bug 和坑点](#经典 Bug 和坑点)

[3.3 resize()](#3.3 resize())

简介作用

[【代码实现】resize 函数](#【代码实现】resize 函数)

四、迭代器模拟实现

[4.1 简介作用](#4.1 简介作用)

[4.2 【代码实现】迭代器定义与接口](#4.2 【代码实现】迭代器定义与接口)

五、增删查改接口实现

[5.1 push_back () 尾插](#5.1 push_back () 尾插)

简介作用

[【代码实现】push_back 尾插](#【代码实现】push_back 尾插)

[5.2 pop_back () 尾删](#5.2 pop_back () 尾删)

简介作用

[【代码实现】pop_back 尾删](#【代码实现】pop_back 尾删)

[5.3 insert () 任意位置插入【面试高频考点】](#5.3 insert () 任意位置插入【面试高频考点】)

简介作用

[【代码实现】insert 插入函数](#【代码实现】insert 插入函数)

[5.4 erase () 删除元素【面试高频考点】](#5.4 erase () 删除元素【面试高频考点】)

简介作用

[【代码实现】erase 删除函数](#【代码实现】erase 删除函数)

迭代器失效详解

[5.5 swap () 交换函数](#5.5 swap () 交换函数)

简介作用

[【代码实现】swap 交换函数](#【代码实现】swap 交换函数)

[5.6 operator \[\] 下标访问](#5.6 operator [] 下标访问)

简介作用

[【代码实现】operator \[\] 重载](#【代码实现】operator [] 重载)

六、常见问题与坑点总结

[6.1 深浅拷贝问题](#6.1 深浅拷贝问题)

[6.2 迭代器失效问题](#6.2 迭代器失效问题)

[6.3 自赋值问题](#6.3 自赋值问题)

[6.4 reserve 扩容的坑](#6.4 reserve 扩容的坑)

结语



前言

大家好,我是你们的小小风呀!临近期末,小风不得不开始恶补学校课程了,哎~~,当初欠下的终究是要还的呀!所以小风的博客只能不定时更新啦!望大家理解,小风也不想挂课呀!言归正传 今天我们来到【从零开始学 C++】专题的第十篇,这篇可以说是 STL 容器学习的重中之重 ------vector 的模拟实现

为什么要模拟实现 vector?因为:

  • 这是面试必考题,90% 的 C++ 面试都会问到

  • 能真正理解底层内存管理机制

  • 能搞懂深浅拷贝、迭代器失效这些 "坑"

  • 为学习其他容器打下坚实基础

本文将手把手带你实现一个完整的 vector,所有代码都可以直接运行,所有面试重点我都会用【面试高频考点】标注出来。新手小白也能轻松看懂!


一、vector 的核心结构

1.1 简介作用

vector 本质上就是一个动态数组,和普通数组的区别就是:它可以自动扩容!

为了管理这个动态数组,vector 内部用了三个指针来维护:

|-----------------|------------------|-----------|
| 指针名 | 作用 | 大白话理解 |
| _start | 指向数组的起始位置 | 数组的 "头" |
| _finish | 指向最后一个有效元素的下一个位置 | 数组的 "尾巴" |
| _endofstorage | 指向整个容量的末尾位置 | 数组的 "天花板" |

这三个指针的关系:

  • size() = _finish - _start (有效元素个数)

  • capacity() = _endofstorage - _start (总容量大小)

  • 永远满足:_start ≤ _finish ≤ _endofstorage

1.2 【代码实现】核心结构定义

cpp 复制代码
#pragma once
#include <iostream>
#include <assert.h>
using namespace std;

namespace my_vector
{
    template<class T>
    class vector
    {
    public:
        // 后面会实现各种成员函数...
        
    private:
        T* _start;        // 指向数组起始位置
        T* _finish;       // 指向最后一个有效元素的下一个位置
        T* _endofstorage; // 指向整个容量的末尾
    };
}

1.3 新手坑点提醒

常见错误 :新手容易把_finish理解成 "指向最后一个元素",这是错的! ✅ 正确理解_finish指向的是最后一个元素的下一个位置,这样设计是为了方便计算 size 和判断是否为空。


二、默认成员函数实现

2.1 无参构造函数

简介作用

创建一个空的 vector,三个指针都初始化为 nullptr,表示还没有分配任何内存。

【代码实现】无参构造函数
cpp 复制代码
// 无参构造函数
vector()
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{}

2.2 带 n 个 val 的构造函数

简介作用

创建一个 vector,里面有 n 个值为 val 的元素。比如vector<int> v(5, 10)就创建了 5 个 10。

【代码实现】带 n 个 val 的构造函数
cpp 复制代码
// 带n个val的构造函数
vector(size_t n, const T& val = T())
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    // 先开n个空间
    reserve(n);
    // 然后插入n个val
    for (size_t i = 0; i < n; i++)
    {
        push_back(val);
    }
}

// 为了避免int类型的匹配问题,再重载一个版本
vector(int n, const T& val = T())
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    reserve(n);
    for (int i = 0; i < n; i++)
    {
        push_back(val);
    }
}
新手坑点提醒

经典 Bug :如果只写vector(size_t n, ...),当你写vector<int> v(5, 10)时,5 是 int 类型,会优先匹配迭代器区间构造,导致奇怪的错误! ✅ 解决方案:重载一个 int 版本的构造函数。


2.3 迭代器区间构造

简介作用

用另一个容器的迭代器区间来初始化 vector,非常灵活,可以接收各种容器的数据。

【代码实现】迭代器区间构造
cpp 复制代码
// 迭代器区间构造(模板函数)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    // 遍历迭代器区间,一个个插入
    while (first != last)
    {
        push_back(*first);
        first++;
    }
}

2.4 拷贝构造函数【面试高频考点】

简介作用

用一个已有的 vector 来创建新的 vector。这里必须深拷贝,否则会出大问题!

什么是浅拷贝? 就是两个对象共用同一块内存,一个释放了另一个就变成野指针了。 什么是深拷贝? 重新开辟一块内存,把数据完整复制过去,两个对象互不干扰。

【代码实现】拷贝构造函数(深拷贝)
cpp 复制代码
// 拷贝构造函数(深拷贝)
vector(const vector<T>& v)
    : _start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    // 1. 先开辟和v一样大的空间
    reserve(v.capacity());
    
    // 2. 把数据一个个拷贝过来
    for (auto& e : v)
    {
        push_back(e);
    }
}
经典 Bug 和坑点

浅拷贝的灾难:如果不写拷贝构造,编译器默认生成的是浅拷贝,会导致:

  1. 两个对象析构时释放同一块内存(double free)

  2. 一个对象修改数据会影响另一个对象

  3. 程序直接崩溃!

记住:只要涉及资源管理,必须自己写深拷贝!


2.5 析构函数

简介作用

释放 vector 占用的内存,把三个指针置空。

【代码实现】析构函数
cpp 复制代码
// 析构函数
~vector()
{
    if (_start)
    {
        delete[] _start;
        _start = nullptr;
        _finish = nullptr;
        _endofstorage = nullptr;
    }
}

2.6 赋值运算符重载【面试高频考点】

简介作用

实现v1 = v2这样的赋值操作。这里有传统写法现代写法两种,面试经常对比!

【代码实现】传统写法
cpp 复制代码
// 赋值运算符重载 - 传统写法
vector<T>& operator=(const vector<T>& v)
{
    // 1. 防止自赋值(v = v)
    if (this != &v)
    {
        // 2. 释放原来的空间
        delete[] _start;
        
        // 3. 开辟新空间
        _start = new T[v.capacity()];
        _finish = _start;
        _endofstorage = _start + v.capacity();
        
        // 4. 拷贝数据
        for (size_t i = 0; i < v.size(); i++)
        {
            *(_finish++) = v[i];
        }
    }
    return *this;
}
【代码实现】现代写法(推荐!)
cpp 复制代码
// 赋值运算符重载 - 现代写法(简洁又安全)
vector<T>& operator=(vector<T> v) // 注意:这里是传值,不是传引用!
{
    // 直接交换!就这么简单!
    swap(v);
    return *this;
}
两种写法对比总结

|-------|--------------|------------------|
| 对比项 | 传统写法 | 现代写法 |
| 代码量 | 多,容易写错 | 极少,3 行搞定 |
| 自赋值检查 | 需要手动检查 | 天然避免(传值已经拷贝了一份) |
| 异常安全 | 可能异常(new 失败) | 异常安全(拷贝失败不影响原对象) |
| 推荐度 | ⭐⭐ | ⭐⭐⭐⭐⭐ |

💡 现代写法原理:利用传值参数自动调用拷贝构造生成临时对象,然后交换临时对象和 this 对象的资源,临时对象出作用域自动析构 ------ 完美!


三、容量相关接口实现

3.1 size()、capacity()、empty()

简介作用

这三个是最基础的查询接口:

  • size():返回有效元素个数

  • capacity():返回总容量大小

  • empty():判断是否为空

【代码实现】基础容量查询
cpp 复制代码
// 返回有效元素个数
size_t size() const
{
    return _finish - _start;
}

// 返回总容量
size_t capacity() const
{
    return _endofstorage - _start;
}

// 判断是否为空
bool empty() const
{
    return _start == _finish;
}

3.2 reserve ()【面试高频考点】

简介作用

扩容核心函数!当空间不够时,开辟更大的空间,把旧数据移过去。

扩容策略:一般是按 2 倍扩容(VS 下是 1.5 倍,g++ 下是 2 倍)。

【代码实现】reserve 扩容函数
cpp 复制代码
// 扩容函数(只扩不缩)
void reserve(size_t n)
{
    // 只有当n大于当前容量时才扩容
    if (n > capacity())
    {
        // 1. 记录原来的有效元素个数(很重要!)
        size_t oldSize = size();
        
        // 2. 开辟新空间
        T* tmp = new T[n];
        
        // 3. 拷贝旧数据到新空间
        if (_start) // 原来有数据才拷贝
        {
            for (size_t i = 0; i < oldSize; i++)
            {
                tmp[i] = _start[i];
            }
            // 4. 释放旧空间
            delete[] _start;
        }
        
        // 5. 更新三个指针
        _start = tmp;
        _finish = _start + oldSize;  // 这里不能用size()!_start已经变了
        _endofstorage = _start + n;
    }
}
经典 Bug 和坑点

致命错误_finish = _start + size() 这样写会崩溃! 因为计算size()的时候用的是新的_start减去旧的_finish,结果完全错误!

正确做法 :扩容前先把oldSize保存下来!


3.3 resize()

简介作用

调整有效元素的个数:

  • 如果 n > size ():插入元素,用默认值填充

  • 如果 n <size ():删除尾部元素

  • 如果 n > capacity ():先扩容再插入

【代码实现】resize 函数
cpp 复制代码
// 调整有效元素个数
void resize(size_t n, const T& val = T())
{
    if (n > size())
    {
        // 情况1:插入元素,需要先检查扩容
        reserve(n);
        while (_finish < _start + n)
        {
            *(_finish++) = val;
        }
    }
    else
    {
        // 情况2:删除元素,直接移动_finish指针
        _finish = _start + n;
    }
}

四、迭代器模拟实现

4.1 简介作用

vector 的迭代器本质上就是原生指针!因为 vector 的内存是连续的,指针天然支持 ++、--、* 等操作。

4.2 【代码实现】迭代器定义与接口

cpp 复制代码
public:
    // 正向迭代器(就是原生指针)
    typedef T* iterator;
    typedef const T* const_iterator;
    
    // 普通迭代器接口
    iterator begin()
    {
        return _start;
    }
    
    iterator end()
    {
        return _finish;
    }
    
    // const迭代器接口(只读)
    const_iterator cbegin() const
    {
        return _start;
    }
    
    const_iterator cend() const
    {
        return _finish;
    }

💡 小知识:有了迭代器,范围 for 就自动支持了!因为范围 for 底层就是用迭代器实现的。


五、增删查改接口实现

5.1 push_back () 尾插

简介作用

在 vector 尾部插入一个元素,是最常用的接口。

【代码实现】push_back 尾插
cpp 复制代码
// 尾插
void push_back(const T& x)
{
    // 1. 检查是否需要扩容
    if (_finish == _endofstorage)
    {
        size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
        reserve(newCapacity);
    }
    
    // 2. 插入数据
    *_finish = x;
    _finish++;
}

5.2 pop_back () 尾删

简介作用

删除 vector 的最后一个元素。

【代码实现】pop_back 尾删
cpp 复制代码
// 尾删
void pop_back()
{
    // 断言:空vector不能尾删
    assert(!empty());
    _finish--;
}

5.3 insert () 任意位置插入【面试高频考点】

简介作用

在 pos 位置插入一个元素。这里最容易出现迭代器失效问题!

【代码实现】insert 插入函数
cpp 复制代码
// 在pos位置插入值为x的元素
iterator insert(iterator pos, const T& x)
{
    // 检查pos合法性
    assert(pos >= _start);
    assert(pos <= _finish);
    
    // 1. 检查扩容(这里会导致迭代器失效!)
    if (_finish == _endofstorage)
    {
        // 重点:扩容前记录pos相对于_start的偏移量
        size_t len = pos - _start;
        
        size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
        reserve(newCapacity);
        
        // 扩容后更新pos!因为_start已经变了
        pos = _start + len;
    }
    
    // 2. 挪动数据(从后往前挪)
    iterator end = _finish - 1;
    while (end >= pos)
    {
        *(end + 1) = *end;
        end--;
    }
    
    // 3. 插入数据
    *pos = x;
    _finish++;
    
    return pos; // 返回新的迭代器,解决失效问题
}

为什么会失效? 因为扩容时_start指向了新的内存,原来的 pos 还是指向旧内存,变成野指针了!

解决方案

  1. 扩容前记录pos - _start的偏移量

  2. 扩容后用_start + 偏移量重新计算 pos

  3. insert 函数返回新的迭代器给用户使用


5.4 erase () 删除元素【面试高频考点】

简介作用

删除 pos 位置的元素。同样存在迭代器失效问题!

【代码实现】erase 删除函数
cpp 复制代码
// 删除pos位置的元素
iterator erase(iterator pos)
{
    // 检查pos合法性
    assert(pos >= _start);
    assert(pos < _finish);
    
    // 1. 挪动数据覆盖(从前往后挪)
    iterator it = pos + 1;
    while (it != _finish)
    {
        *(it - 1) = *it;
        it++;
    }
    
    _finish--;
    return pos; // 返回删除位置的下一个元素的迭代器
}
迭代器失效详解

经典错误

cpp 复制代码
// 错误写法!erase后it失效了,再++会崩溃
for (auto it = v.begin(); it != v.end(); it++)
{
    if (*it % 2 == 0)
        v.erase(it);
}

正确写法

cpp 复制代码
// 正确写法:用erase的返回值更新迭代器
for (auto it = v.begin(); it != v.end(); )
{
    if (*it % 2 == 0)
        it = v.erase(it); // 接收返回值
    else
        it++;
}

5.5 swap () 交换函数

简介作用

交换两个 vector 的内容,效率极高 ------ 只需要交换三个指针就行!

【代码实现】swap 交换函数
cpp 复制代码
// 交换两个vector
void swap(vector<T>& v)
{
    std::swap(_start, v._start);
    std::swap(_finish, v._finish);
    std::swap(_endofstorage, v._endofstorage);
}

5.6 operator \[\] 下标访问

简介作用

像数组一样用[]访问元素,需要提供普通版本和 const 版本。

【代码实现】operator \[\] 重载
cpp 复制代码
// 普通版本(可读可写)
T& operator[](size_t pos)
{
    assert(pos < size());
    return _start[pos];
}

// const版本(只读)
const T& operator[](size_t pos) const
{
    assert(pos < size());
    return _start[pos];
}

六、常见问题与坑点总结

6.1 深浅拷贝问题

问题表现:两个 vector 指向同一块内存,析构时 double free 崩溃。

根本原因:默认拷贝构造是浅拷贝,只复制指针不复制内容。

解决方案:自己实现深拷贝的拷贝构造和赋值重载。


6.2 迭代器失效问题

两种失效场景

  1. insert/erase 导致的失效:插入删除后,原来的迭代器指向的位置已经无效

  2. 扩容导致的失效:扩容后内存地址变了,所有旧迭代器都变成野指针

解决方案

  • 永远使用 insert/erase 的返回值来更新迭代器

  • 扩容后不要使用之前保存的迭代器


6.3 自赋值问题

问题表现v = v 自己赋值给自己,传统写法中会先释放内存再拷贝,结果拷贝的是已经释放的内存。

解决方案

  • 传统写法:加上if (this != &v)判断

  • 现代写法:天然避免,推荐使用!


6.4 reserve 扩容的坑

经典错误 :忘记保存 oldSize,直接用_finish = _start + size()导致指针计算错误。

记住:凡是涉及_start 变化的地方,都要先保存偏移量!


结语

恭喜你!到这里,一个完整的 vector 就实现完毕了!

回顾一下今天的【面试高频考点】:

  1. ✅ 深拷贝的实现(拷贝构造、赋值重载)

  2. ✅ 赋值重载的传统写法 vs 现代写法

  3. ✅ reserve 扩容逻辑

  4. ✅ insert/erase 的迭代器失效处理

这四个点,面试 10 次有 9 次会问到!建议大家亲手把代码敲一遍,理解才会更深刻。

下一篇我们将继续学习 list 的模拟实现,敬请期待!如果这篇文章对你有帮助,别忘了点赞收藏关注三连哦~


相关推荐
右耳朵猫AI1 小时前
Rust技术周刊 2026年第20周
开发语言·后端·rust
爱和冰阔落1 小时前
【Linux系统编程】环境变量深度解析——从 fork 继承到 export 内建命令,两张表打通进程上下文
linux·c++·环境变量·系统调用
kaico20181 小时前
Python 在 Jenkins Pipeline 中的使用总结
开发语言·python·jenkins
流浪0011 小时前
C++篇:深入理解 C++ 智能指针:从裸指针到 RAII 的蜕变
开发语言·c++
丘山望岳1 小时前
二叉搜索双壁——map和set
开发语言·数据结构·c++
QiLinkOS1 小时前
合肥气链科技有限公司创办与未来技术应用
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
瑞雪兆丰年兮1 小时前
[从0开始学Java|第十六、十七天]项目阶段(拼图小游戏)
java·开发语言
AI人工智能+电脑小能手1 小时前
【大白话说Java面试题 第85题】【Mysql篇】第15题:MySQL 的事务中,幻读是怎么解决的?
java·开发语言·数据库·mysql·面试
yaoxin5211231 小时前
423. Java 日期时间 API - DayOfWeek 和 Month 枚举
开发语言·python