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

STL——vector

STL历史

STL 的主要实现版本

  1. HP STL:由 Alexander Stepanov 和 Meng Lee 在惠普 Palo Alto 实验室工作时完成,是 C++ STL 的第一个实现版本。

  2. SGI STL:由 Silicon Graphics Computer Systems 公司参照 HP STL 实现,被 Linux 的 C++ 编译器 GCC 所采用。

  3. STLport:由 Boris Fomitchev 开发的跨平台版本,基于 SGI STL。

  4. P.J.Plauger STL:由 P.J.Plauger 实现,被 Visual C++ 编译器所采用。

  5. Rouge Wave STL:由 Rouge Wave 公司开发,用于 Borland C++ 编译器。

STL六大组件

  1. 容器(Containers):如 vector, list, deque, set, map,用于存放数据。

  2. 算法(Algorithms):如 sort, search, copy, erase。

  3. 迭代器(Iterators):用于连接容器和算法。

  4. 仿函数(Functors):行为类似函数,可作为算法的策略。

  5. 配接器(Adapters):用于修饰容器、仿函数或迭代器接口。

  6. 配置器(Allocators):负责空间配置与管理。

Vector

1、vector 是表示可变大小数组的容器,一般是异地扩容(需要拷贝操作)

2、vector 采用连续空间存储,可采用下标随机访问

3、vector 的尾插尾删相对高效,但在其他位置的增删操作需要挪动元素,效率较低

模拟实现

构造函数

vector 以迭代器作为成员变量,且此处迭代器的实现为 原生指针

template<class T>
class myVector
{
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;}myVector():_start(nullptr),_finish(nullptr),_end_of_storge(nullptr){}private:iterator _start;iterator _finish;iterator _end_of_storge;
};

增 -- insert

void insert (iterator position, size_type n, const T& val);

向 position 位置插入 n 个 val

首先判断 position 的合法性(注意 assert 在 release 版本代码中不起作用,后面需要使用捕获异常),其次检查空间是否足够,如果不够就按照 2 倍来开足够的空间,最后挪动元素,并插入 n 个 val

	size_t capacity(){return _end_of_storge - _start;}size_t size(){return _finish - _start;}void insert(iterator position, size_t n, const T& val){assert(position >= _start && position <= _finish);if (_finish + n > _end_of_storge){size_t new_capacity = capacity() == 0 ? 6 : capacity * 2;reserve(new_capacity);}}

如果空间不够,扩容至 new_capacity,实现扩容函数 reserve

reserve

reserve 中在开好空间后,需要实现深拷贝,那么使用 T 类型的赋值重载 解决两个已经存在的对象间的深拷贝问题;至于 T 类型的赋值重载怎么实现的,那是 T 类的事情,不关我 myVector 的事(当然此处默认 T 类有默认构造、赋值重载)

实际上,如果 T 没有实现深拷贝的赋值重载就 g 了;

iterator src = _finish;
while (src != position) {--src;new (src + n) T(*src);   // 拷贝构造到目标地址(src)->~T();             // 析构原位置对象(可选)
}

所以其实使用 placement new 定位 new 来拷贝构造未初始化的内存是更完美的方法

	void reserve(size_t n){if (n > capacity())	// 需要扩容的容量 大于 实际容量时才执行扩容{size_t old_size = size();T* tmp_start = new T[n];if (size() > 0)	// 有内容才需拷贝,且需要实现深拷贝{for (size_t i = 0; i < size(); i++)	// 循环次数即元素个数{*(tmp_start + i) = *(_start + i);	// 调 T 类型的赋值重载处理深拷贝}}delete[] _start;_start = tmp_start;_finish = _start + old_size;_end_of_storge = _start + n;}}

回到 insert,扩完空间后,从 position 向后挪动 n 个位置,并插入 n 个 val

	void insert(iterator position, size_t n, const T& val){assert(position >= _start && position <= _finish);if (_finish + n > _end_of_storge)	// 扩容{size_t new_capacity = capacity() == 0 ? 6 : capacity() * 2;reserve(new_capacity);}iterator begin = _finish;	// 从后向前挪动while (begin > position){begin--;*(begin + n) = *begin;}for (size_t i = 0; i < n; i++)	// 插入 n 个 val{*(position + i) = val;}_finish += n;}
bug 出现:异地扩容后迭代器的失效

在经过下面这段代码之前,position 指向原空间的某位置;如果发生了异地扩容,但是 position 没有跟着改变,那么就变成了悬空迭代器 or 野指针

        if (_finish + n > _end_of_storge)	// 扩容{size_t new_capacity = capacity() == 0 ? 6 : capacity * 2;reserve(new_capacity);}

所以我们需要在扩容前,记录下 position 的偏移量,在扩容后更新

	void insert(iterator position, size_t n, const T& val){assert(position >= _start && position <= _finish);if (_finish + n > _end_of_storge)	// 扩容{size_t pos_offset = position - _start;	// 记录 position 偏移量size_t new_capacity = capacity() == 0 ? 6 : capacity() * 2;reserve(new_capacity);position = _start + pos_offset;	// 更新 position}iterator begin = _finish;	// 从后向前挪动while (begin > position){begin--;*(begin + n) = *begin;}for (size_t i = 0; i < n; i++)	// 插入 n 个 val{*(position + i) = val;    // T 类的赋值重载}_finish += n;}

包一丝,下面这段 定位 new 的 代码应该是 挪动数据时,对 _finish 后未初始化的内存,拷贝时用的

iterator src = _finish;
while (src != position) {--src;new (src + n) T(*src);   // 拷贝构造到目标地址(src)->~T();             // 析构原位置对象(可选)
}

增 -- push_back

void push_back (const T& val);

复用 insert

	void push_back(const T& val){insert(_finish, 1, val);}

删 -- erase

iterator erase (iterator position);

删掉 position 位置的元素,返回被删掉元素后那个元素的迭代器

这里是实现删掉一个元素,标准库还提供了重载是删除迭代器区间的元素;整体从前向后挪动元素覆盖掉要删除的元素即可

	iterator erase(iterator position){assert(position >= _start && position <= _finish);iterator begin = position + 1;while (begin < _finish){*(begin - 1) = *begin;begin++;}_finish--;return position;}

删 -- pop_back

void pop_back();

复用 erase

	void pop_back(){erase(_finish - 1);}

operator[ ]

重载 [ ] 运算符

	T& operator[](size_t i){return *(_start + i);}

拷贝构造

拷贝构造也是构造,也走一遍初始化列表

这里在 拷贝 数据时,可以复用 push_back,因为 push_back 中实现了深拷贝逻辑;

但是如果不复用,可以直接使用 定位 new 来对申请好未初始化的内存 拷贝构造,最后处理 _finish 和 _end_of_storge,也是比较完美的方法;vector 里装 内置类型 和 自定义类型都可以解决

	myVector(const myVector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storge(nullptr){this->reserve(v.capacity());	// 为 this 分配 和 v 一样大的原始内存for (size_t i = 0; i < v.size(); i++){//push_back(v[i]);new (_start + i) T(v[i]);	// 使用定位 new 来拷贝构造}_finish = _start + v.size();_end_of_storge = _start + v.capacity();}

析构函数

	~myVector(){delete[] _start;_start = _finish = _end_of_storge = nullptr;}

resize

void resize (size_t n, T val = T());

resize 的功能分段考虑,如果 n <= this->size( ) ,那么将 size( ) 减小至 n;如果 n > this->size( ),那就将 size( ) 扩大到 n,被扩充的元素值设置为 val 

	void resize(size_t n, T val = T()){if (n <= size())	// 删掉 size() - n 个元素{_finish = _start + n;}else{reserve(n);	// 如果 n < capacity() reserve 什么也不会做while (size() < n){// *_finish = val;new (_finish) T(val);_finish++;}}}

考虑一个问题:如果 T 类型中申请了资源,而我们在 n <= this->size( ) 又没有释放,不就内存泄露了?所以在删掉元素时,应当显式调用析构函数清理;那么如果 T 是内置类型,调用析构函数的操作会被编译器优化成 空操作的

	void resize(size_t n, T val = T()){if (n <= size())	// 删掉 size() - n 个元素{// _finish = _start + n;while (_start + n != size()){_finish--;_finish->~T();	// 显式调用 T 析构函数,适配资源清理情况}}else{reserve(n);	// 如果 n < capacity() reserve 什么也不会做while (size() < n){// *_finish = val;new (_finish) T(val);_finish++;}}}

小结

容器中存储的可能是 内置类型 或 自定义类型 或 有申请内存资源的自定义类型,在实现 容器内元素 的拷贝逻辑时,用 placement new 是比较标准的做法,它会调用该类型的 拷贝构造 完成对所申请原始内存的初始化。

不过没有使用 try catch 来捕获可能的异常。

http://www.dtcms.com/a/299738.html

相关文章:

  • GO 从入门到精通2
  • MyBatis-Plus 通用 Service
  • J2EE模式---表现层集成模式
  • MyBatis Plus 对数据表常用注解
  • 进阶数据结构:用红黑树实现封装map和set
  • Sql server查询汇总补缺月份
  • 【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 热词评论查询功能实现
  • Android开发中内存泄漏问题治理方案
  • 四通OKI5560SC针式打印机如何复位清零和恢复出厂设置??
  • 昇思学习营-昇思+香橙派+deepseek介绍课程内容及心得
  • Chukonu 阅读笔记
  • Rerank 模型的其中两种路径:BERT 相似度与 CoT 推理
  • 如何应对心事干扰学习工作?
  • 高可用集群KEEPALIVED的详细部署
  • 【CTF-Web】dirsearch寻找download.php进行?path=flag.txt任意文件下载
  • 深入解析命名管道:原理、实现与进程间通信应用
  • 机器学习对中特估股票关键特征选取的应用与研究
  • 【橘子分布式】gRPC(番外篇-监听流)
  • Thinkph6中常用的验证方式实例
  • 【时时三省】(C语言基础)用指向函数的指针作函数参数
  • 网络:应用层
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-30,(知识点:传输线特性阻抗,影响因素)
  • 【web应用】基于Vue3和Spring Boot的课程管理前后端数据交互过程
  • 1、虚拟机安装
  • InfluxDB Flux 查询协议实战应用(二)
  • Linux726 raid0,raid1,raid5;raid 创建、保存、停止、删除
  • Python 程序设计讲义(22):循环结构——for 循环
  • 使用FRP搭建内网穿透工具,自己公网服务器独享内外网端口转发
  • C++ APM异步编程模式剖析
  • 2025微前端架构研究与实践方案