当前位置: 首页 > news >正文

STL——vector的底层实现C++

文章目录

  • 1.前言
  • 2.vector的实现
    • 2.1vector的基本结构
    • 2.2 capacity() 和 size()
    • 2.3 迭代器和下标访问
    • 2.4 构造,拷贝构造,析构函数,赋值重载
    • 2.5增删查改(重点)

1.前言

首先什么是vector?

在C++中,vector 是一种非常常用的容器,属于标准模板库(STL)的一部分。它是一个封装了动态大小数组的序列容器,提供了方便的接口来存储、访问和操作数据。

vector 是一种动态数组,它的大小可以随着元素的添加或删除而自动调整。与普通数组不同,普通数组的大小在定义时是固定的,而 vector 的大小可以在运行时动态改变。

2.vector的实现

首先我们来实现vector的基础结构(成员变量,迭代器等),再介绍一些常用的构造和拷贝构造,最后实现一些增删查改。

2.1vector的基本结构

//首先为了区分库里的vector我们用命名空间将我们要写的vector类包起来
namespace dhb
{template<class T>class vector{public://重新命名一下方便书写迭代器等typedef T* iterator;typedef const T* const_iterator;//各种成员函数private://成员变量iterator _start=nullptr;//这里我们提前给定缺省值iterator _finish=nullptr;iterator _endofstorage=nullptr;
};

vector里为了能存储各类数据用到了模板template<class T>
由由于模板不能声明定义分离
我们的成员函数就写在头文件中。

2.2 capacity() 和 size()

size_t capacity() const;

  • 返回对象所开空间大小(以元素数量表示)

size_t size() const;

  • 返回数组中的元素数量

size_t:返回无符号整型
const:不改变成员变量

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

在这里插入图片描述

2.3 迭代器和下标访问

迭代器

这里介绍普通迭代器和const迭代器,反向迭代器先不做解释。
容器里常用的范围for就是通过调用迭代器实现
iterator begin()
const_iterator begin() const

  • 返回一个指向该向量(vector)中第一个元素的迭代器

iterator end()
const_iterator end() const

  • 返回一个指向vector容器中“末端之后”元素的迭代器。
    这里的“末端之后”元素是一个理论上的元素,它位于vector中最后一个元素的后面。它并不指向任何实际存在的元素,因此不能被解引用

由此可以将begin()和end()看成一个左闭右开的区间 [begin(),end())

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;
}

下标访问
不管是在做题还是在项目中下标访问都是非常实用的。
T& operator[](size_t i)
const T& operator[](size_t i)const

  • 返回向量容器中位置为n的元素的引用
  • 原理和C语言数组类似不过多解释
  • 值得注意的是const版本,第一个const表示返回值不能改变(不能++或- -),第二个是成员变量不能改变
T& operator[](size_t i)
{assert(i < size());return _start[i];
}
const T& operator[](size_t i)const
{assert(i < size());return _start[i];
}

2.4 构造,拷贝构造,析构函数,赋值重载

由于有些构造要用到迭代器所以就放在这里讲解了。
首先最简单的无参构造

vector():_start(nullptr),_finish(nullptr),_endofstorage(nullptr)
{}

有参构造

vector(size_t n, T val = T())
{resize(n, val);//这里我提前复用了后面函数resize()//大家可以结合后面的resize()理解
}

以上两种构造较简单不做重点讲解。

拷贝构造
vector(const vector<T>& v)

  • 用一个对象去初始化一个新的对象
    使用场景:vector<int> v2(v1);(假定v1在上文已初始化)
vector(const vector<T>& v)
{//假如用v1构造v2//v就是v1//不要忘记拷贝数据reserve(capacity());for (auto& e : v){//把数据逐一插入push_back(e);}
}

用花括号构造
想要像下方一样用花括号构造,要有initializer_list构造函数

dhb::vector<int> v3 = { 1,2,3,4,5,5 };
dhb::vector<int> v4 = { 1,2,3,4,5,51,1,1,1,1,1,1,1 };

其实现和拷贝构造类似

vector(initializer_list<T> li)
{//空间略有不同,我们这里只知道花括号中的元素数量//把空间开成size()大小reserve(li.size());for (auto& e : li){push_back(e);}
}

迭代器区间构造

template<class InputIterator>
vector(InputIterator first,InputIterator last)
{//只拷贝一部分迭代器就不考虑reserve了while(first!=last){push_back(*first);++first;}
}

注意:在用迭代器区间构造时要用模板(为了适配所有的容器)可以用vector构造。
析构函数
将数组释放再置空。

~vector()
{if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}
}

赋值重载

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);
}// 可以写v1 = v2了
vector<T>& operator=(vector<T> v)
{swap(v);return *this;
}

2.5增删查改(重点)

这里我们重点讲解扩容函数reserve()中的一些坑,insert()和erase()中的迭代器失效。
T是上面基本结构中介绍的模板参数
void push_back(const T& x)
void pop_back()
在_finish尾插处尾插一个数据
在写尾插函数时还有个前提条件:扩容函数reserve()

  • 我先写个大家容易写成的错误版本:
//先写个扩容函数
void reaerve(size_t n)
{if(n>capacity()){//申请空间T* tmp=new T[n];//如果_str不为空拷贝数据if(_str){memcpy(tmp,_str,size()*sizeof(T));//注意这里的第三个参数是字节//释放旧空间delete [] _str;}//重新给成员函数值_str=tmp;_finish=_str+size();_endofstorage=_str+n;}
}

这段代码主要有两个错误:
第一个是_finish=_str+size();由于size()返回的是_finish - _start但是_finish是老的vector上的指针,而_start已经完成了_str=tmp的操作,_start和_finish不在同一数组上由此我们的size()就失效了。我们可以通过提前记录size()的值的方法来解决。
第二个错误是memcpy的使用,memcpy 是按字节拷贝,不适用于拷贝 std::string 这样的复杂对象,因为它们内部有指针和动态内存管理逻辑,所以我们要手动逐一拷贝数据。

由此我们可以修改我们的代码:

void reserve(size_t n)
{if (n > capacity()){T* tmp = new T[n];size_t oldsize = size();//拷贝数据//只有顺序表内不为空的时候才需要拷贝数据if (_start){/*memcpy(tmp, _start, oldsize*sizeof(T));*///这里第三个参数易错是长度乘每个参数大小//memcpy是一个一个字节拷贝,会导致string类型的问题//逐个拷贝for (size_t i = 0; i < oldsize; i++){tmp[i] = _start[i];}delete[]_start;}_start = tmp;_finish = _start + oldsize;_endofstorage = _start + n;}
}

写完了扩容函数我们的尾插尾删就容易多了简单带过一下:

void push_back(const vector<T>& x)
{if(_finish==_endofstorage){//空间满了就扩容reserve(capacity()==042*capacity())}//插入数据*_finish=x;_finish++;
}//尾删
//判空
bool empty() const
{return _finish == _start;
}
void pop_back()
{assert(!empty());//再写个判空--_finish;
}

iterator insert(iterator pos, T x)
iterator erase(iterator pos)

	//任意位置插入iterator insert(iterator pos, T x){assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = _finish - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容以后我们的pos就被判定为失效状态(虽然实际上不一定失效)//重新将pos定位一下pos = _start + len;}//挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//插入数据*pos = x;_finish++;return pos;//?}iterator erase(iterator pos){assert(pos >= _start);assert(pos <= _finish);//挪动数据iterator end = pos + 1;while (end != _finish){*(end - 1) = *end;end++;}_finish--;return pos;}

接下来由以上两个接口来介绍一下迭代器失效:
在 C++ 中,迭代器失效(Iterator Invalidation)是一个常见的问题,尤其是在对容器进行修改操作时。当迭代器失效时,继续使用该迭代器可能会导致未定义行为(如访问非法内存、程序崩溃等)。
结合上文的reserve函数:
在这里插入图片描述
这里的迭代器失效虽然只是在空间扩容后才发生的,但是我们在写代码的过程中只要insert数据了我们酒吧迭代器认定为失效状态。
erase也会造成迭代器失效问题但是和insert有些不同,我们结合实例理解:

vector<int> v{1,2,3,4,4,5}
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0)v.erase(it);else++it;
}

在这里插入图片描述

那么我们该如何解决迭代器失效问题呢?
在这里插入图片描述
在这里插入图片描述
这是库里的函数,我们发现都会返回一个迭代器position(也就是我上面写pos),insert的pos就是插入数据的位置,erase的pos位置是那个被删除后的数据后面的一个数据(因为后面数据前移了)。有了返回值我们就可以将其赋值给迭代器进行更新。
如下:

iterator insert(iterator pos, T x)
{assert(pos >= _start);assert(pos <= _finish);if (_finish == _endofstorage){size_t len = _finish - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());//扩容以后我们的pos就被判定为失效状态(虽然实际上不一定失效)//重新将pos定位一下pos = _start + len;}//挪动数据iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//插入数据*pos = x;_finish++;return pos;//?
}
iterator erase(iterator pos)
{assert(pos >= _start);assert(pos <= _finish);//挪动数据iterator end = pos + 1;while (end != _finish){*(end - 1) = *end;end++;}_finish--;return pos;
}
http://www.dtcms.com/a/275706.html

相关文章:

  • 安全初级作业1
  • 深入理解 QSettings:Qt 中的应用程序配置管理
  • PID控制算法理论学习基础——单级PID控制
  • 手机识别数据集,2628张原始图片,支持yolo,coco json,pasical voc xml等格式的标注
  • Web安全-Linux基础-02-系统基础命令
  • 这个Pandas函数可以自动爬取Web图表
  • Android下一个简单的定时器,每隔一秒输出一个数字
  • 【JVM|类加载】第三天
  • monorepo 发布库 --- 打包文件
  • 多线程的区别和联系
  • 使用sqlmap的SQL Injection注入
  • CSS分层渲染与微前端2.0:解锁前端性能优化的新维度
  • Linux之Zabbix分布式监控篇(一)
  • 电商广告市场惊现“合规黑洞”,企业如何避免亿元罚单
  • phpstudy搭建pikachu靶场
  • 单链表的题目,咕咕咕
  • 区块链平台之以太坊深入解读:技术、经济与生态的全面解析
  • 从OpenMV到执行器:当PID算法开始“调教”舵机
  • 计算机视觉与深度学习 | 基于Matlab的多特征融合可视化指纹识别系统(附完整代码)
  • 迅为RK3588开发板Android13系统super.img的解包和重新组包
  • 【C++】封装红黑树模拟实现set和map
  • GESP2025年6月认证C++三级( 第三部分编程题(2)分糖果)
  • MIG_IP核的时钟系统
  • 《硬件产品经理》第七章:产品开发流程之验证
  • 【6.1.3 漫画分布式锁】
  • 【web站点安全开发】任务1:html基础表单和表格
  • C# 接口(派生成员作为实现)
  • Leaflet面试题及答案(41-60)
  • OneCode 3.0架构深度剖析:工程化模块管理与自治UI系统的设计与实现
  • 20250712-3-Kubernetes 应用程序生命周期管理-服务编排(YAML)及编写技巧_笔记