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

【C++标准模版库】模拟实现vector+迭代器失效问题

模拟实现vector

  • 一.vector成员变量
  • 二.构造函数
    • 1.无参(默认)构造
    • 2.有参构造
    • 3.拷贝构造
      • 1.传统写法
      • 2.现代写法
  • 三.vector对象的容量操作
    • 1.size
    • 2.capacity
    • 3.clear
    • 4.empty
    • 5.reserve
    • 6.resize
  • 四.vector对象的访问及遍历操作
    • 1.operator[]
    • 2.实现迭代器:begin+end
  • 五.vector对象的增删查改操作
    • 1.operator
      • 1.传统写法
      • 2.现代写法
    • 2.push_back
    • 3.pop_back
    • 4.insert
      • 迭代器失效:类似野指针
      • 迭代器失效:位置意义改变
    • 5.erase
  • 六.源代码
    • 1.vector.h

一.vector成员变量

成员变量:

  1. iterator _start:指向vector的起始位置。
  2. iterator _finish:指向vector的最后一个有效数据的下一个位置。
  3. iterator _end_of_storage:指向vector可容纳最大数据个数的下一个位置。

大体结构如下:

在这里插入图片描述

namespace xzy
{//有模版不能分离到.h与.cpp文件,否则报链接错误template<class T>class vector{public:typedef T* iterator;private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}

二.构造函数

1.无参(默认)构造

类内函数拷贝构造,系统就不再提供默认构造,无法vector<int> v; 需要自己提供默认构造,C++11中vector() = default; 强制生成默认构造。

//C++11 强制生成默认构造
//vector() = default;vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr)
{}

2.有参构造

  1. 迭代器区间构造:例如用list对象的迭代器区间构造vector对象。作用:例如由于list对象排序性能较低,利用vector的迭代器区间初始化list对象中相同的数据进行排序,可以提高性能。
//类模版的成员函数可以是函数模版
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}
int main()
{list<int> l1(10, 1); //数据类型要求匹配,例如:都是intvector<int> v1(l1.begin(), l1.end());
}
  1. n个值为val初始化:
vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}

在这里插入图片描述

解决办法:

在这里插入图片描述

3.拷贝构造

1.传统写法

vector(const vector<T>& v)
{_start = new T[v.capacity()];//memmove(_start, v._start, v.size() * sizeof(T));for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();
}

更好的写法:遍历vector进行尾插。

vector(const vector<T>& v)
{reserve(v.size());for (auto& e : v){push_back(e);}
}

2.现代写法

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}vector(const vector<T>& v)
{vector<T> tmp(v.begin(), v.end());swap(tmp);
}

三.vector对象的容量操作

1.size

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

2.capacity

size_t capacity() const
{return _end_of_storage - _start;
}

3.clear

void clear()
{_finish = _start;
}

4.empty

bool empty() const
{return _start == _finish;
}

5.reserve

扩容时:先开辟新空间,再将旧空间拷贝到空间,释放旧空间,最后修改数据。

但是这里存在一个坑,如下:

在这里插入图片描述

初始_start 和 _finish 为缺省值 nullptr。
_finish = _start + size() = _start + _finish - _start = _finish = nullptr——>程序崩溃。

正确代码如下:

void reserve(size_t n)
{//第一种解决方法//if (n > capacity())//{//	T* tmp = new T[n];//	memmove(tmp, _start, sizeof(T) * size());//	delete[] _start;//	_finish = tmp + size();//	_start = tmp;//	_end_of_storage = _start + n;//}//第二种解决方法//if (n > capacity())//{//	size_t old_size = size();//	T* tmp = new T[n];//	memmove(tmp, _start, sizeof(T) * size());//	delete[] _start;//	_start = tmp;//	_finish = _start + old_size;//	_end_of_storage = _start + n;//}//第三种解决方法if (n > capacity()){//先保存有效数据个数size_t old_size = size();//开空间T* tmp = new T[n];//拷贝数据for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}//释放旧空间delete[] _start;//更新数据_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}
}

但是第一种与第二种方法在某些vector容器数据为自定义类型时存在浅拷贝问题如下图:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同理:拷贝构造使用的memmove也存在此问题。

6.resize

修改有效数据的个数时:若传入的参数小于有效数据的个数:删除数据即修改_finish即可;若传入的参数大于有效数据的个数:插入数据前考虑是否扩容。

void resize(size_t n, const T& val = T())
{if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}
}

在这里插入图片描述

四.vector对象的访问及遍历操作

1.operator[]

T& operator[](size_t i)
{assert(i >= 0 && i < size());return _start[i];
}const T& operator[](size_t i) const
{assert(i >= 0 && i < size());return _start[i];
}

2.实现迭代器: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;
}

五.vector对象的增删查改操作

1.operator

1.传统写法

vector<T>& operator=(const vector<T>& v)
{if (this != &v){delete[] _start;_start = new T[v.capacity()];//memcpy(_start, v._start, v.size() * sizeof(T)); //当vector<string>时存在问题for(size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}return *this;
}

更好的写法:遍历vector进行尾插。

vector<T>& operator=(const vector<T>& v)
{if (this != &v){clear();reserve(v.size());for (auto& e : v){push_back(e);}}return *this;
}

2.现代写法

void swap(vector<T>& v)
{std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);
}//类内可以用类名替代类型:vector& operator=(vector tmp)
vector<T>& operator=(vector<T> tmp)
{swap(tmp);return *this;
}

2.push_back

尾插时:先检查容量,再进行尾插。

void push_back(const T& x)
{//容量满了——>扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//尾插*_finish = x;++_finish;
}

3.pop_back

尾删时:先检查是否有有效数据,再进行尾删。

void pop_back()
{assert(!empty());--_finish;
}

4.insert

插入时:先检查容量,再整体右移一位,最后插入。

迭代器失效:类似野指针

实现插入操作有一个坑造成迭代器失效问题,如下图:

在这里插入图片描述

修改后的代码能解决上面的问题,但是又存在另一个迭代器失效的问题,如下图:

在这里插入图片描述

迭代器失效:位置意义改变

在这里插入图片描述

由于不知道insert函数内是否存在扩容,不能确保pos是否为野指针,解决方法:可以在insert函数中返回pos用于接受。代码如下:

iterator insert(iterator pos, const T& x)
{assert(pos >= _start);assert(pos <= _finish);//容量满了——>扩容if (_finish == _end_of_storage){//记录pos的相对位置防止迭代器失效size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}//整体后移一位iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//插入数据*pos = x;++_finish;return pos;
}

5.erase

同理erase也会遇到的迭代器失效:位置意义改变,在vs2022中的vector(SLT)不接收erase的返回值强行访问,程序崩溃。接收erase的返回值访问,程序正常运行。

iterator erase(iterator pos)
{assert(pos >= _start);assert(pos < _finish);iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;return pos;
}

实现删除vector中的偶数:

int main()
{std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);auto it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);}else{it++;}}return 0;
}

六.源代码

1.vector.h

//#pragma once#ifndef __VECTOR_H__
#define __VECTOR_H__#include<iostream>
#include<assert.h>
using namespace std;namespace xzy
{//有模版不能分离到.h与.cpp文件,否则报链接错误template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;//C++11 强制生成默认构造//vector() = default;vector():_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){}vector(const vector<T>& v){_start = new T[v.capacity()];//memmove(_start, v._start, v.size() * sizeof(T)); //当vector<string>时存在问题for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i];}_finish = _start + v.size();_end_of_storage = _start + v.capacity();}/*vector(const vector<T>& v){reserve(v.size());for (auto& e : v){push_back(e);}}*///类模版的成员函数可以是函数模版template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector(size_t n, const T& val = T()){reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}}void clear(){_finish = _start;}//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		delete[] _start;//		_start = new T[v.capacity()];//		//memcpy(_start, v._start, v.size() * sizeof(T));//当vector<string>时存在问题//      for(size_t i = 0; i < v.size(); i++)//        {//			  _start[i] = v._start[i];//        }//		_finish = _start + v.size();//		_end_of_storage = _start + v.capacity();//	}//	return *this;//}//vector<T>& operator=(const vector<T>& v)//{//	if (this != &v)//	{//		clear();//		reserve(v.size());//		for (auto& e : v)//		{//			push_back(e);//		}//	}//	return *this;//}void swap(vector<int>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}类内可以用类名替代类型:vector& operator=(vector tmp)vector<T>& operator=(vector<T> tmp){swap(tmp);return *this;}~vector(){if (_start != nullptr){delete[] _start;}_start = _finish = _end_of_storage;}iterator begin(){return _start;}iterator end(){return _finish;}const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}//以下有一个坑//void reserve(size_t n)//{//	if (n > capacity())//	{//		//开空间//		T* tmp = new T[n];//		//拷贝数据//		memmove(tmp, _start, sizeof(T) * size());//		//释放旧空间//		delete[] _start;//		//更新数据//		_start = tmp;//		_finish = _start + size(); 注意:size()已经不再是有效数据个数了,可以通过调试观察//		_end_of_storage = _start + n;//	}//}void reserve(size_t n){//第一种解决方法:依旧存在错误//if (n > capacity())//{//	T* tmp = new T[n];//	memmove(tmp, _start, sizeof(T) * size());//	delete[] _start;//	_finish = tmp + size();//	_start = tmp;//	_end_of_storage = _start + n;//}//第二种解决方法:依旧存在错误/*if (n > capacity()){size_t old_size = size();T* tmp = new T[n];memmove(tmp, _start, sizeof(T) * size());delete[] _start;_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}*///第三种解决方法if (n > capacity()){//先保存有效数据个数size_t old_size = size();//开空间T* tmp = new T[n];//拷贝数据for (size_t i = 0; i < old_size; i++){tmp[i] = _start[i];}//释放旧空间delete[] _start;//更新数据_start = tmp;_finish = _start + old_size;_end_of_storage = _start + n;}}void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish < _start + n){*_finish = val;++_finish;}}}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}void push_back(const T& x){//容量满了——>扩容if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : 2 * capacity());}//尾插*_finish = x;++_finish;}bool empty(){return _start == _finish;}void pop_back(){assert(!empty());--_finish;}//void insert(iterator pos, const T& x)//{//	//容量满了——>扩容//	if (_finish == _end_of_storage)//	{//		reserve(capacity() == 0 ? 4 : 2 * capacity());//	}//	//整体后移一位//	iterator end = _finish;//	while (end > pos)//	{//		*end = *(end - 1);//		--end;//	}//	//插入数据//	*pos = x; //pos为野指针,迭代器失效//	++_finish;//}iterator insert(iterator pos, const T& x){//容量满了——>扩容if (_finish == _end_of_storage){//记录pos的相对位置防止迭代器失效size_t len = pos - _start;reserve(capacity() == 0 ? 4 : 2 * capacity());pos = _start + len;}//整体后移一位iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}//插入数据*pos = x;++_finish;return pos;}iterator erase(iterator pos){iterator it = pos + 1;while (it != end()){*(it - 1) = *it;++it;}--_finish;return pos;}T& operator[](size_t i){assert(i >= 0 && i < size());return _start[i];}const T& operator[](size_t i) const{assert(i >= 0 && i < size());return _start[i];}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}#endif

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Flume系列之:把flume配置写入到zookeeper节点
  • net 工控机 字节转换 字符,ToString 格式化
  • 前端HTML+CSS复习
  • AIGC平台创业启示录:从Airbnb的成功经验中汲取灵感
  • 反制攻击者-蚁剑低版本
  • 腾讯OCR签名算法
  • EDI是什么:EDI系统功能介绍
  • Depth Anything——强大的单目深度估计模型
  • 北京崇文门中医院贾英才主任解读头晕:症状与根源
  • [Unity] ShaderGraph实现DeBuff污染 溶解叠加效果
  • 数据结构初阶(c语言)-排序算法
  • idea插件反编译class文件
  • 前端HTML+CSS查漏补缺——仿制百度搜索首页的一些思考
  • C#中多线程编程中的同步、异步、串行、并行及并发及死锁
  • <数据集>航拍车辆识别数据集<目标检测>
  • 【Linux系统编程】快速查找errno错误码信息
  • 30天自制操作系统-2
  • Git同步原始仓库到Fork仓库中
  • JavaScript 一些 DOM 的知识点
  • passportjs 源码分析
  • PAT A1120
  • vue-cli3搭建项目
  • 不用申请服务号就可以开发微信支付/支付宝/QQ钱包支付!附:直接可用的代码+demo...
  • 分布式事物理论与实践
  • 构建二叉树进行数值数组的去重及优化
  • 微信小程序实战练习(仿五洲到家微信版)
  • 我看到的前端
  • 掌握面试——弹出框的实现(一道题中包含布局/js设计模式)
  • kubernetes资源对象--ingress
  • 国内开源镜像站点
  • 交换综合实验一
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • ​html.parser --- 简单的 HTML 和 XHTML 解析器​
  • ​LeetCode解法汇总2182. 构造限制重复的字符串
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (1)Nginx简介和安装教程
  • (10)Linux冯诺依曼结构操作系统的再次理解
  • (3)(3.2) MAVLink2数据包签名(安全)
  • (7)STL算法之交换赋值
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (C#)一个最简单的链表类
  • (二)JAVA使用POI操作excel
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (论文阅读11/100)Fast R-CNN
  • (使用vite搭建vue3项目(vite + vue3 + vue router + pinia + element plus))
  • (转)ObjectiveC 深浅拷贝学习
  • (转)Sublime Text3配置Lua运行环境
  • (转)拼包函数及网络封包的异常处理(含代码)
  • ***利用Ms05002溢出找“肉鸡
  • .md即markdown文件的基本常用编写语法
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .Net Core 中间件与过滤器
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .NET 项目中发送电子邮件异步处理和错误机制的解决方案