Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【C++指南】vector(二):手把手教你底层原理与模拟实现

【C++指南】vector(二):手把手教你底层原理与模拟实现

原创
作者头像
倔强的石头_
发布于 2025-05-15 15:22:18
发布于 2025-05-15 15:22:18
17900
代码可运行
举报
文章被收录于专栏:C/C++指南C/C++指南
运行总次数:0
代码可运行

@toc

一、引言

在 C++ 标准库中,vector 是最常用的动态数组容器,它提供了高效的元素存储和访问能力。

其底层实现涉及内存管理、迭代器维护、元素操作等复杂逻辑。

本文将基于自主实现的 xc::vector 类,深入探讨其设计原理与关键功能实现

vector系列关联文章:【C++指南】vector(一):从入门到详解

二、成员变量

通过对stl库中vector的实现进行分析:

我们发现其成员变量与我们想象的不一致,并不是通过一个指针指向数据和两个size_t size和capacity

在这里插入图片描述
在这里插入图片描述

通过默认构造找到了其基于三个成员变量startfinishendofstorage来实现vector

并通过size和capacity成员函数的实现可以明白:==STL库是通过指针做差来计算size和capacity==

接着往下深挖,可以找到:

==三个成员变量的数据类型,最终是基于模板类型的指针==

在这里插入图片描述
在这里插入图片描述

于是,我们通过模拟STL库的方式来实现vector

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
private:

    iterator \_start = nullptr;    // 指向数据存储起始位置

    iterator \_finish = nullptr;   // 指向有效数据尾部的下一个位置

    iterator \_endofstorage = nullptr; // 指向存储容量尾部

通过三个指针实现动态数组的核心控制:

  • \_start:内存块的起始地址
  • \_finish:当前有效元素的末尾的下一个位置
  • \_endofstorage:内存块的总容量

三、默认成员函数

2.1 默认构造函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
vector()//默认构造

{}

**思路**:默认构造函数不进行任何初始化操作,将成员指针初始化为 nullptr,表示当前 vector 为空。

2.2 析构函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
~vector()

{

    delete[]\_start;

    \_start = \_finish = \_endofstorage = nullptr;

}

**思路**:析构函数负责释放 vector 所占用的内存。首先使用 delete[] 释放存储元素的数组,然后将三个成员指针都置为 nullptr,避免悬空指针。

2.3 拷贝构造函数

传统写法
代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
//vector(const vector<T>& v)//老实人写法

//{

//    size\_t capacity = v.capacity();//开空间和初始化

//    \_start = new T[capacity];

//    \_endofstorage = \_start + capacity;

//    \_finish = \_start + v.size();



//    for (size\_t i = 0; i < size(); i++)//拷贝数据

//    {

//        \_start[i] = v.\_start[i];

//    }

//}

**思路**:传统的拷贝构造函数首先根据源 vector 的容量分配新的内存空间,然后将源 vector 的元素逐个复制到新的内存空间中。最后更新 \_finish\_endofstorage 指针。

==注意==

拷贝数据不可用memcpy ,为了防止数据类型是自定义类型而引发的浅拷贝问题,而采用逐个位置赋值,这样就可以调用数据类型相应的构造函数了

在C++中所有容器相关的拷贝中,都要用深拷贝来代替浅拷贝

现代写法
代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
vector(const vector<T>& v)//偷懒式写法

{

    reserve(v.capacity());



    for (auto i = v.cbegin(); i != v.cend(); ++i)

    {

        push\_back(\*i);

    }

}

**思路**:现代写法利用 reserve 函数预先分配足够的内存空间,然后通过 push\_back 函数将源 vector 的元素逐个添加到新的 vector 中。这种写法更加简洁,并且利用了已有的 push\_back 函数,减少了代码的重复。

2.4 赋值重载函数

传统写法
代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
//vector<T>& operator=(const vector<int>& v)//老实人写法

//{

//    size\_t capacity = v.capacity();//开空间和初始化

//    \_start = new T[capacity];

//    \_endofstorage = \_start + capacity;

//    \_finish = \_start + v.size();



//    for (size\_t i = 0; i < size(); i++)//拷贝数据

//    {

//        \_start[i] = v.\_start[i];

//    }

//    return \*this;

//}

**思路**:传统的赋值重载函数首先释放当前 vector 所占用的内存,然后根据源 vector 的容量分配新的内存空间,将源 vector 的元素逐个复制到新的内存空间中。最后返回当前对象的引用。

现代写法
代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
vector<T>& operator=(vector<int>v)//偷懒式写法

{

    swap(v);

    return \*this;

}

**思路**:现代写法采用了“拷贝 - 交换”技术。首先通过值传递的方式接收一个临时对象 v,然后调用 swap 函数将当前对象和临时对象的成员指针进行交换。这样就完成了赋值操作,并且保证了异常安全性。最后返回当前对象的引用。

四、元素访问相关

3.1 [] 重载(非 const 版本)

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
T& operator[](size\_t i)

{

    return \_start[i];

}

**思路**:非 const 版本的 [] 重载函数返回指定位置元素的引用,允许对元素进行修改。

3.2 [] 重载(const 版本)

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
const T& operator[](size\_t i)const

{

    return \_start[i];

}

**思路**:const 版本的 [] 重载函数返回指定位置元素的 const 引用,用于在 const 对象上访问元素,不允许对元素进行修改。

五、迭代器相关

4.1 迭代器类型声明

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
typedef T\* iterator;

typedef const T\* const\_iterator;

**思路**:定义了两种迭代器类型,iterator 用于非 const 对象的迭代,const\_iterator 用于 const 对象的迭代。

4.2 begin 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
iterator begin()

{

    return \_start;

}

**思路**:begin 函数返回指向 vector 第一个元素的迭代器。

4.3 end 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
iterator end()

{

    return \_finish;

}

**思路**:end 函数返回指向 vector 最后一个元素的下一个位置的迭代器。

4.4 cbegin 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
const\_iterator cbegin() const

{

    return \_start;

}

**思路**:cbegin 函数返回指向 vector 第一个元素的 const 迭代器,用于在 const 对象上迭代。

4.5 cend 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
const\_iterator cend() const

{

    return \_finish;

}

**思路**:cend 函数返回指向 vector 最后一个元素的下一个位置的 const 迭代器,用于在 const 对象上迭代。

六、容量相关函数

5.1 size 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
size\_t size()const

{

    return \_finish - \_start;

}

**思路**:size 函数返回 vector 中当前元素的数量,通过 \_finish\_start 指针的差值计算得到。

5.2 capacity 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
size\_t capacity()const

{

    return \_endofstorage - \_start;

}

**思路**:capacity 函数返回 vector 当前分配的内存空间能够容纳的元素数量,通过 \_endofstorage\_start 指针的差值计算得到。

5.3 empty 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
bool empty()

{

    return \_start == \_finish;

}

**思路**:empty 函数判断 vector 是否为空,通过比较 \_start\_finish 指针是否相等来实现。

5.4 resize 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
void resize(size\_t n,const T& val=T())

{

    if (n <= size())

    {

        \_finish = \_start + n;

    }

    else

    {

        reserve(n);//该函数只有n大于capacity才会扩容

        for (size\_t i = size(); i < n; ++i)

        {

            push\_back(val);

        }

    }

}

**思路**:resize 函数用于调整 vector 的大小。如果 n 小于等于当前大小,则将 \_finish 指针移动到 \_start + n 的位置;如果 n 大于当前大小,则先调用 reserve 函数确保有足够的容量,然后使用 push\_back 函数将元素添加到 vector 中,直到大小达到 n

==可参考下面两张图来理解resizeu对于不同的n所采取的不同处理方式==

当n小于等于size时

在这里插入图片描述
在这里插入图片描述

当n大于size时

包括两种情况,分别是size<n<capacity,以及n>capacity

两种处理结果稍有不同

在这里插入图片描述
在这里插入图片描述

5.5 reserve 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
void reserve(size\_t n)

{

    if (n > capacity())

    {



        size\_t size = \_finish - \_start;//提前存下size防止迭代器失效

        T\* tmp = new T[n];



        if (\_start)//如果原空间不为空,则拷贝数据

        {

            for (size\_t i = 0; i < size; ++i)

            {

                tmp[i] = \_start[i];

            }

        }

        

        delete[]\_start;

        \_start = tmp;

        \_finish = \_start + size;

        \_endofstorage = \_start + n;

    }

}

**思路**:reserve 函数用于增加 vector 的容量。如果 n 大于当前容量,则分配一个新的内存空间,将原有的元素复制到新的内存空间中,然后释放原有的内存空间。在操作过程中,需要提前记录当前的大小,以避免迭代器失效。

==reserve、insert和erase的实现,涉及到迭代器失效的问题,限于本篇文章主题和篇幅限制,没法在此展开,故放到系列第三篇文章,单独来讲解==

如果对代码有不理解的地方可以直接点击链接跳转下一篇文章:

七、修改相关操作

6.1 push\_back 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
void push\_back(const T& val)  //尾插

{

    if (\_finish == \_endofstorage)

    {

        reserve(capacity() == 0 ? 4 : 2 \* capacity());

    }

    \*(\_finish++) = val;



}

**思路**:push\_back 函数用于在 vector 的末尾添加一个元素。如果当前容量不足,则调用 reserve 函数进行扩容,然后将元素赋值给 \_finish 指针所指向的位置,并将 \_finish 指针向后移动一位。

6.2 pop\_back 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
void pop\_back() //尾删

{

    assert(!empty());

    --\_finish;

}

**思路**:pop\_back 函数用于删除 vector 的最后一个元素。在删除之前,需要确保 vector 不为空,然后将 \_finish 指针向前移动一位。

6.3 insert 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
void insert(iterator pos, const T& val) //插入  

{

    assert(pos >= begin()); //位置合法性判断

    assert(pos <= end());



    

    if (\_finish == \_endofstorage)//判断扩容

    {

        size\_t len = pos - begin();//提前存下pos相对位置,防止pos迭代器失效

        reserve(capacity() == 0 ? 4 : 2 \* capacity());

        pos = begin() + len;



    }



    iterator i = end();//挪动数据

    while (i != pos) 

    {

        \*i = \*(i - 1);

        --i;

    }



    \*pos = val;

    ++\_finish;

}

**思路**:insert 函数用于在指定位置插入一个元素。首先进行位置合法性检查,如果当前容量不足,则进行扩容操作。在扩容过程中,需要提前记录插入位置的相对位置,以避免迭代器失效。然后将插入位置之后的元素依次向后移动一位,最后将元素插入到指定位置,并将 \_finish 指针向后移动一位。

==可参考下面两张图来理解insert的过程==

1.首先判断是否需要扩容

2.然后需要判断和挪动数据(因为vector底层是基于顺序表的原理实现的)

3.最后将空出来的位置插入数据 ,修改finish的值

在这里插入图片描述
在这里插入图片描述

挪动数据和插入

在这里插入图片描述
在这里插入图片描述

6.4 erase 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
iterator erase(iterator pos)

{

    assert(pos >= begin());

    assert(pos < end());

    assert(!empty());



    iterator i = pos + 1;

    while (i != end())

    {

        \*(i - 1) = \*i;

        ++i;

    }

    --\_finish;

    return pos;



}

**思路**:erase 函数用于删除指定位置的元素。首先进行位置合法性检查,然后将删除位置之后的元素依次向前移动一位,最后将 \_finish 指针向前移动一位。函数返回指向删除位置的迭代器。

==可借助下面两张图来理解erase的过程==

`1.挪动数据

2.修改finish`

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

八、其他函数

7.1 swap 函数

代码语言:cpp
代码运行次数:0
运行
AI代码解释
复制
void swap(vector<T> v)//两个vector对象的交换

{

    std::swap(\_start, v.\_start);

    std::swap(\_finish, v.\_finish);

    std::swap(\_endofstorage, v.\_endofstorage);

}

**思路**:swap 函数用于交换两个 vector 对象的内容。通过交换三个成员指针,实现了两个 vector 对象的快速交换。

九、实现亮点与注意事项

  1. **深拷贝实现**:通过逐个元素赋值确保自定义类型正确拷贝,再次重申一句在C++中所有容器相关的拷贝中,都要用深拷贝来代替浅拷贝
  2. **异常安全**:采用“拷贝 - 交换”模式保证赋值操作的原子性。
  3. **迭代器安全**:在扩容时通过记录相对位置保持迭代器有效性(具体实现细节将在后续文章深入解析)。

结尾总结

本文通过自主实现的 xc::vector 类,展示了动态数组容器的核心实现技术。

需要特别注意的是,虽然当前实现通过记录相对位置等手段初步解决了迭代器失效问题,但不同操作(如 reserveinserterase)引发的迭代器失效机制与应对策略仍需深入探讨。建议读者持续关注后续文章《,我们将结合具体代码与测试用例,系统分析迭代器失效的根本原因及解决方案。

提示:在实际开发中,建议优先使用标准库 std::vector。若需自定义实现,务必严格遵循 C++ 对象生命周期管理规则,并通过单元测试验证关键功能。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Python基础
输入install ,选择Package Control:Install Package
仓颉cahr
2022/04/09
1.2K0
Python基础
Python入门三部曲(三)
在函数greet_user()中,变量username是一个形参—-函数完成其工作所需要的一项信息.在代码greet_user(‘kobe’)中,值’kobe’是一个实参。
龙哥
2018/10/22
1.2K0
快速掌握Python基础语法(下)
接着上一篇,这篇继续来讲 Python 的基础语法,如果你还没有看过之前的那一篇文章,可以看一下。
SuperFeng
2019/09/26
5180
关于“Python”的核心知识点整理大全5
有时候,你要将元素从列表中删除,并接着使用它的值。例如,你可能需要获取刚被射杀的 外星人的x和y坐标,以便在相应的位置显示爆炸效果;在Web应用程序中,你可能要将用户从活 跃成员列表中删除,并将其加入到非活跃成员列表中。
用户10920956
2024/01/19
2570
[python] 列表的练习总结
1 bicycles = ['trek', 'cannondale', 'redline', 'specialized'] print(bicycles) print(bicycles[0]) ##第一个列表数据 print(bicycles[0].title()) print(bicycles[-1].title()) ##从最后开始数 messge = "my first bicycle was a "+bicycles[2].title()+"." print (messge)
py3study
2020/01/08
6110
入门必备!面向对象编程之Python函数与类
Python支持大多数面向对象编程技术。在Python中所有东西都是对象,包括类、函数、数和模块。它允许多态性,不只是在类层级之内而且通过采用鸭子类型的方式。任何对象可以用于任何类型,只要它有适当的方法和特性就能工作。
数据STUDIO
2021/06/24
7860
全网最详细超长python学习笔记、14章节知识点很全面十分详细,快速入门,只用看这一篇你就学会了!
注意事项:本博客是我早起自己写的python笔记word版本,现在转成博客形式,由于ipython文件找不到了,很多代码都会以图片形式出现,不过放心不影响学习,对于比较长的程序例子我回重新贴好代码放入。
汀丶人工智能
2022/12/21
1.2K0
全网最详细超长python学习笔记、14章节知识点很全面十分详细,快速入门,只用看这一篇你就学会了!
函数
函数是带名字的代码块,用于完成具体的工作。通过使用函数,程序的编写、阅读、测试和修复都将更容易。
狼啸风云
2019/01/18
7660
关于“Python”的核心知识点整理大全4
编程语言Perl曾在互联网领域长期占据着统治地位,早期的大多数交互式网站使用的都是 Perl脚本。彼时,“解决问题的办法有多个”被Perl社区奉为座右铭。这种理念一度深受大家的喜 爱,因为这种语言固有的灵活性使得大多数问题都有很多不同的解决之道。在开发项目期间,这 种灵活性是可以接受的,但大家最终认识到,过于强调灵活性会导致大型项目难以维护:要通过 研究代码搞清楚当时解决复杂问题的人是怎么想的,既困难又麻烦,还会耗费大量的时间。
用户10920956
2024/01/19
1770
Python:核心知识点整理大全16-笔记
注意 使用默认值时,在形参列表中必须先列出没有默认值的形参,再列出有默认值的实参。 这让Python依然能够正确地解读位置实参。
用户10920956
2024/01/19
1330
Python:核心知识点整理大全16-笔记
Python编程:从入门到实践(选记)「建议收藏」
本文参考《 Python 编程:从入门到实践》一书,作者: [ 美 ] Eric Matthes
全栈程序员站长
2022/09/08
6.9K0
Python编程:从入门到实践(选记)「建议收藏」
Python函数(二)
经常会发现,向函数传递列表很有用,其中包含的可能是名字、数或更复杂的对象(如字典)。将列表传递给函数后,函数就能直接访问其内容。下面使用函数来提高处理列表的效率。假设有一个用户列表,我们要问候其中的每位用户。下面的示例将包含名字的列表传递给个名为 greet_users() 的函数,这个函数问候列表中的每个人:
Francek Chen
2025/01/22
640
Python函数初识
​ 计算机语言中的函数是类比于数学中的函数演变来的,但是又有所不同。前面的知识中我们学会了运用基础语法(列表、字典)和流程控制语句貌似也能处理一些复杂的问题,但是相对于相似的大量重复性的操作我们就没办法用之前的逻辑方法来解决了,这时候就需要一个可以概括这些重复性操作的统一代码来描述其特征来实现,所以函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
py3study
2020/01/17
7960
关于“Python”的核心知识点整理大全15
注意 大家有时候会形参、实参不分,因此如果你看到有人将函数定义中的变量称为实参或将 函数调用中的变量称为形参,不要大惊小怪。
用户10920956
2024/01/19
2130
关于“Python”的核心知识点整理大全15
关于“Python”的核心知识点整理大全18
这就是一种导入方法:只需编写一条import语句并在其中指定模块名,就可在程序中使用该 模块中的所有函数。如果你使用这种import语句导入了名为module_name.py的整个模块,就可使 用下面的语法来使用其中任何一个函数:
用户10920956
2024/01/19
1290
关于“Python”的核心知识点整理大全18
#小手一抬学Python# Python语法基础干货盘点【附源码】
笔者Python学习主要以《Python编程:从入门到实战》这本书为主,笔记的思路参考书里的脉络。其次还有笔者一年前在慕课上看的北理的嵩天教授的Python课程。嵩天教授的课很好,最大的特点是每个版块都有完整的示例代码。但可能对新手小白不太友好,有些不常用的函数容易弄混。《Python编程:从入门到实战》更适合零基础学习,里边会提到一些互通的编程思想和Python的格式规范。
程序员迪迪
2022/01/05
1.7K0
函数
要执行函数定义的特定任务,可调用该函数。需要在程序中多次执行同一项任务时,无需反复编写完成该任务的代码,而只需调用执行该任务的函数,让Python运行其中的代码。
清菡
2020/12/02
8870
函数
关于“Python”的核心知识点整理大全19
在Python 2.7中创建类时,需要做细微的修改——在括号内包含单词object:
用户10920956
2024/01/19
1330
关于“Python”的核心知识点整理大全19
python笔记(一)
字符串处理 单双引号一样 .title():将每个单词的首字母变为大写,其余小写(不管原来是什么样) .upper():将字符串中所有字母变为大写 .lower():将字符串中所有字母变为小写 .strip():删除行首和行末的空白(空格和制表符)(直接输入变量返回值才能看到,否则看不到效果) .lstrip():删除左边,即行首 .rstrip():删除友边,即行末 合并字符串直接用加号:+ 转义(不管单双引号都生效): \t:制表符 \n:换行
py3study
2020/01/14
1.6K0
关于“Python”的核心知识点整理大全12
列表和字典的嵌套层级不应太多。如果嵌套层级比前面的示例多得多,很可能有更简单 的解决问题的方案。
用户10920956
2024/01/19
2310
关于“Python”的核心知识点整理大全12
推荐阅读
相关推荐
Python基础
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验