C/C++ vector模拟实现

模拟实现:

框架

namespace yx
{template<class T>class vector{public:typedef T* iterator;private:iterator _start;iterator _finish;iterator _end_of_storage;};
}

这里我们声明定义不分离

reverse()

新开一个空间,拷贝数据,然后释放旧空间

代码:

void reserve(size_t n)//满了要扩容
{if (n > capacity()){T* tmp = new T[n];//new 开空间 + 初始化memcpy(tmp,_start,sizeof(T) * size());//一个一个字节的拷贝下来delete[] _start;_start = tmp;}_finish = _start + size();_end_of_storage = _start + n;
}

capacity()

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

size()

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

operator[]

俩版本,一个可读可写,一个只读

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

push_back()

分析

void push_back(const T& x)
{if (_finish == _end_of_storage)//扩容{size_t newcapacity = capacity() == 0 ? 4 : capcaity * 2;reverse(newcapacity);}*_finish = x;++_finish;}

这时我们来运行一下

意外的出错了,为什么呢?

我们来调试一下

我们发现_finish竟然等于0,

其实问题出现在size(),size = _finish - _start ,  但这不是正好吗?

我们来看一个图,start还是不是原来的start,当然不是了,这是start已经被更新了 ,start = tmp,相当于旧的finish 减新的start,size已经不是我们要的哪个size了。

修改方法

第一种

我们先修改一下start的位置

在reserve中

void reserve(size_t n)//满了要扩容
{if (n > capacity()){T* tmp = new T[n];//new 开空间 + 初始化if (_start){memcpy(tmp, _start, sizeof(T) * size());//一个一个字节的拷贝下来delete[] _start;}_finish = tmp + size();_start = tmp;_end_of_storage = _start + n;}}

测试成功,但这强依赖顺序了,不太好。

第二种

oldsize方法提前存储

void reserve(size_t n)//满了要扩容
{if (n > capacity()){size_t oldsize = size();//oldsize提前存储T* tmp = new T[n];//new 开空间 + 初始化if (_start){memcpy(tmp, _start, sizeof(T) * size());//一个一个字节的拷贝下来delete[] _start;}_start = tmp;_finish = tmp + oldsize;_end_of_storage = _start + n;}}

~vector()

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

迭代器iterator

下面写的只是一种方式

typedef T* iterator;iterator begin()
{return _start;
}iterator end()
{return _finish;
}

我们来用范围for遍历测试一下

测试通过

三种遍历方式

for (size_t i = 0; i < v1.size(); i++)
{cout << v1[i] << " ";
}cout << endl;for (auto e : v1)
{cout << e << " ";
}cout << endl;vector<int>::iterator it = v1.begin();
while (it != v1.end())
{cout << *it << " ";++it;
}cout << endl;

const迭代器

typedef const T* const_iterator;const_iterator begin() const
{return _start;
}const_iterator end() const
{return _finish;
}

pop_back()

pop_back()
{assert(size() > 0);--_finish;
}

insert()

头插

代码:

//头插,在x前插入
void insert(iterator pos, const T& x)
{//检查是否需要扩容if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}

我们测试一下

为什么是个随机值呢?

insert迭代器失效

此时_finish = _end_of_storage ,出现了扩容,

导致出现了迭代器失效,pos变为野指针

扩容后,start,finish _end_of_storage都去了新空间,旧空间释放了,而pos还在旧空间里,pos为野指针,所以导致了随机值。

修改方法,算出pos与start的距离

修改后代码:

void insert(iterator pos, const T& x)
{//检查是否需要扩容if (_finish == _end_of_storage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);//扩容完后pos为野指针pos = _start + len;//pos找到新空间位置}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;
}

测试通过

it会不会失效呢?失效后的迭代器还能访问吗?

实参传给形参,形参的改变不会影响实参。但如果出现了扩容呢?insert函数内的pos可以解决野指针问题,但it解决不了。我们就不敢访问这个迭代器了。因为出现了野指针。

我们访问一下迭代器,出现野指针了吧。

迭代器失效后的建议是不要访问。

如果我们给pos加个引用呢?

测试一下

我们发现begin传不过去了,为什么?

v1.begin(),begin() 返回一个迭代器,而且是传值返回,c++规定,传值返回返回的不是_start,返回的是其拷贝,生成临时对象,临时对象具有常性,所以非不要不访问。

erase()

头删

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

测试

erase的迭代器失效

报错了,为什么?

erase迭代器失效

我们界定erase以后,这个it失效了。为什么?

第一种情况:缩容

如果删除后的数据小于容量capacity的一半,就开始缩容

旧的空间释放掉,导致it变为野指针

第二种情况:越界

如果我们删除5呢。

这里出现了越界(非法访问)。迭代器it也失效了

所以erase it 以后,it就失效。

在vs中不会缩容,那它如何判断的呢?vs下的iterator是一个很复杂的类型,不是一个原生指针实现的

我们可以这样理解,erase  it以后,就把it的类型改了,我们再访问的时候就会报错。 

如何修改呢?

给其一个返回值

我们来删除所有偶数

void test6()
{std::vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);//删除所有偶数std::vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){v1.erase(it);}else{++it;}}for (auto e : v1){cout << e << " ";}cout << endl;}

程序对不对呢?

当然不对,erase it 一次以后,it就失效了,程序报错。调试一下

删除完2后,it失效,程序报错,如何修改呢?

拷贝构造

我们没有写拷贝构造,编译器会默认生成一个拷贝构造。是浅拷贝,完成的是值的拷贝

void test7()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);vector<int> v2(v1);for (auto e : v2){cout << e << " ";}cout << endl;
}

我们测试一下

会导致析构两次,报错。

我们没有写默认构造,拷贝构造也是构造,构造函数的定义是,只要你写了任意构造,编译器默认就不生成

这里我们强制编译器生成默认构造

//强制编译器生成默认的构造
vector() = default;//拷贝构造v2(v1),拷贝构造也是构造
vector(const vector<T>& v)
{for (auto e : v){push_back(e);}
}

测试通过

 swap()

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

operator=

v1 = v3,直接传值传参v就是v3的拷贝构造,v1就是this,tihs和v交换,this不要的数据给v,v出了作用域会析构,写法对于所有深拷贝都适合

//赋值 v1 = v3,v1之前的空间要释放,v1要和v3有一样大的空间一样的值
vector<T> operator=(vector<T> v)//直接传值传参v就是v3的拷贝,
{this->swap(v);return *this;
}

 测试通过

 迭代器区间初始化

迭代器区间初始化

类模板的成员函数
函数模板,支持任意容器的迭代器初始化

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);++first;}
}

迭代器区间初始化可以更好的控制初始化范围。

那么再这里写函数模板到底是为了什么?

任意类型的迭代器都能用。

char和转化为int,类型提升,用的ascii码

n个value值构造函数

vector(size_t n, const T& val = T())
{reserve(n);for (size_t i = 0; i < n; i++){push_back(val);}
}

我们看到val的值为T(),这里是匿名对象,

那val能不能给0呢?答案是不可以的,为什么?

当T的为int时,当然可以;但当T为string呢?T为vector呢?是不是就不行了。

当然,当T()匿名对象的T为int的时候是不是就不对了呢?

这个在C语言中是不对的,但在C++中是正确的。

void test9()
{int i = 0;int j(1);int k = int();int x = int(2);}

C++对内置类型进行了升级。C++的内置类型也有了构造,为了兼容模板。

测试

v2初始化为了xxx

当我们再写一个v3初始化为1时,出现编译错误,为什么?

由于此时有两个构造,它会选择更适合自己的模板,所以选择了第一个,*first出现了错误。

如何修改呢?

第一种

在10后面加个u,表示无符号整型

第二种

重载了一个构造函数,size_t修改为int

对于初始化{}

单参数和多参数对象隐式类型转换

	class A{public:A(int a1):_a1(a1), _a2(0){}A(int a1,int a2):_a1(a1), _a2(a2){}private:int _a1;int _a2;};

单参数和多参数对象隐式类型转换

*****************

单参数用括号,多参数必须用花括号{ }


A aa1(1, 1);
A aa2 = { 2,2 };
A aa2{ 2,2 };
const A& aa8 = { 1, 2};A aa3(1);
A aa4 = 1;
const A& aa8 = { 1, 2};

aa8引用的是{1,2}中间产生的临时对象,具有常性

c++11规定单参数也可以用花括号{}来初始化


A aa5(1);
A aa6 = { 1 };
A aa7{ 1 };

 结果是一样的,但单参数不建议这样写。

我们来看一下下面代码是不是隐式类型转换

	vector<int> v1 = { 1,2,3,4,5,6 };for (auto e : v1){cout << e << " ";}cout << endl;

这里当然不是,上面描述的有单参数的和两个参数,本质上都去调自己的构造去了。

这里不是隐式类型转换,上面参数固定,这里参数不固定,可以是3个可以是5个等。

为什么会这样呢?

因为c++11里支持initializer_list,新增的一个类型,方便初始化 ,

	vector<int> v1 = { 1,2,3,4,5,6 };

这里的1 ,2  ,3 ,4 ,5 ,6 的类型为initializer_list

它的底层其实是俩指针,一个指向开始,一个指向最后一个数据的下一位,所以这里我们可以用范围for遍历。

vector<int> v2 = {1,2,3,4,5,6} 这里的{1,2,3,4,5,6}类型为initializer_list,生成临时对象,拷贝构造给v2,优化为构造。

我们在这里还需要写一个initializer_list的构造

vector(initializer_list<T> il)
{reserve(il.size());for (auto e : il){push_back(e);}}

测试

我们看一下下面这个怎某构造

vector<A> v3 = {  };

知识点

我先把结论告诉你:vector<>里面的数据类型是一个自定义类型时,要考虑深拷贝。

我们看一下代码

	void test11(){vector<string> v1;v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");v1.push_back("11111111111111");for (auto e : v1){cout << e << " ";}cout << endl;}

测试一下

没问题

如果我们再push一次呢

代码出错了,为什么???

我们调试一下

空间不够,需要扩容,然后把数据给给tmp,_start释放。而数据拷贝给新空间的时候是浅拷贝,string没有深拷贝

memcpy是一个字节一个字节的拷贝,对任意类型拷贝都是浅拷贝。

tmp指向的还是原来的哪个地址,而_start空间释放了,它的深拷贝没有发生再vector这一层,而是发生在自定义类型存的数据。

如何解决呢?

对_str进行深拷贝,但我们不能访问_str里的数据

我们直接使用赋值来完成工作

void reserve(size_t n)//满了要扩容
{if (n > capacity()){size_t oldsize = size();//oldsize提前存储T* tmp = new T[n];//new 开空间 + 初始化//if (_start)//{//	memcpy(tmp, _start, sizeof(T) * size());//一个一个字节的拷贝下来//	delete[] _start;//}if (_start){for (size_t i = 0; i < oldsize; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = tmp + oldsize;_end_of_storage = _start + n;}}

本集完

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/30218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTML星空特效

目录 写在前面 完整代码 代码分析 运行效果 系列文章 写在后面 写在前面 100行代码实现HTML星空特效。 完整代码 全部代码如下。 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"&g…

【Redis】基于Redission实现分布式锁(代码实现)

目录 基于Redission实现分布式锁解决商品秒杀超卖的场景&#xff1a; 1.引入依赖&#xff1a; 2.加上redis的配置&#xff1a; 3.添加配置类&#xff1a; 4.编写代码实现&#xff1a; 5.模拟服务器分布式集群的情况&#xff1a; 1.右键点击Copy Configuration 2.点击Modi…

怎么投资中证全指证券公司指数?

中证全指证券公司指数的代码是399975&#xff0c;有50只成分股&#xff0c;几乎包含了市场上所有主要的证券公司&#xff0c;算是指数基金中投资证券行业的不二选择。 根据天天基金的数据显示&#xff0c;市面上有31只跟踪该指数的基金&#xff0c;规模最大的是南方中证全指证…

【Java面试】二十二、JVM篇(下):JVM参数调优与排查

文章目录 1、JVM的参数在哪里设置2、常见的JVM调优参数有哪些3、常见的JVM调优工具有哪些4、Java内存泄漏的排查思路5、CPU飙高的排查思路 1、JVM的参数在哪里设置 war包部署&#xff0c;在tomcat中设置&#xff0c;修改TOMCAT_HOME/bin/catalina.sh 文件 jar包启动&#xff0…

jpg格式图片无法打开可以修复吗?有哪些方法?

JPG的图片打不开怎么办呢&#xff1f;当JPG图片打不开的时候&#xff0c;我们需要先了解下具体的原因&#xff0c;是因为格式不支持&#xff0c;还是因为图片文件损坏。不同的原因&#xff0c;解决的方法也会不同&#xff0c;比如图片损坏&#xff0c;我们就需要对图片修复处理…

web爬虫笔记:js逆向案例九(某多多 anti_content参数)补环境流程

web爬虫笔记:js逆向案例九(某多多 anti_content参数)补环境流程 一、目标网站:aHR0cHM6Ly9tb2JpbGUueWFuZ2tlZHVvLmNvbS8= 二、接口分析 1、快速定位加密位置(通过搜索/cells/hub/v3快速定位到加密js文件) 2、通过分析可知&#

双系统下,如何隐藏另一个系统分区?

前言 最近有小伙伴在公众号下留言&#xff1a; 小伙伴说&#xff1a;“双系统时&#xff0c;非当前系统的系统盘能不能屏蔽&#xff1f;&#xff01;比如Win7的系统盘在Win10系统时&#xff0c;盘符成了D盘&#xff0c;安装应用软件时&#xff0c;有些文件就到了D盘&#xff0…

DuDuTalk:智能电子录音工牌在销售场景的应用价值

在快速变化的市场环境中&#xff0c;销售团队面临着日益激烈的竞争和不断变化的客户需求。为了提升销售效率、优化客户体验并加强团队协作&#xff0c;越来越多的企业开始采用智能电子录音工牌作为销售场景中的关键工具。本文将从多个方面探讨智能电子录音工牌在销售场景中的应…

一图读懂腾讯云EdgeOne Open Edge平台

为了鼓励更多开发者参与、共同构建和改进边缘应用&#xff0c;腾讯云EdgeOne面向广大开发者&#xff0c;打造了技术开放共创平台——OpenEdge&#xff0c;该平台是国内首个全免费的边缘应用平台&#xff0c;不仅进一步开放了腾讯云遍布世界各地的边缘节点能力&#xff0c;还可以…

Ollama(docker)+ Open Webui(docker)+Comfyui

Windows 系统可以安装docker desktop 相对比较好用一点&#xff0c;其他的应该也可以 比如rancher desktop podman desktop 安装需要windows WSL 安装ollama docker docker run -d --gpusall -v D:\ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama 这里…

微服务必备容器化技术

文章目录 docker介绍与安装及上手应用什么是容器化技术&#xff1f;为什么需要学习docker&#xff1f;如何理解dockerdocker下载与安装docker的基础组成docker体验 dockerfile介绍并创建go-zero环境容器docker的基础组成从容器构建属于go环境的容器基于dockerfile构建go容器镜像…

最新技术:跨境电商源码,应对多国市场需求,让您轻松开展全球业务!

随着全球化进程的不断推进&#xff0c;跨境电商已成为企业拓展国际市场的重要途径。为了满足不同国家和地区消费者不断增长的需求&#xff0c;跨境电商源码应运而生&#xff0c;为企业提供了便捷高效的全球化业务发展方案。 一、全球化运营的关键 跨境电商源码的核心功能在于…

基本循环神经网络(RNN)

RNN背景&#xff1a;RNN与FNN 在前馈神经网络中&#xff0c;信息的传递是单向的&#xff0c;这种限制虽然使得网络变得更容易学习&#xff0c;但在一定程度上也减弱了神经网络模型的能力。 在生物神经网络中&#xff0c;神经元之间的连接关系要复杂的多。前馈神经网络可以看着…

PySide(PyQt)的特殊按钮(互锁、自锁、独占模式)

界面图: Qt Designer中创建窗口,放置一个QGroupBox,命名为btnStation,这就是自定义的按钮站,按钮站里放置6个按钮。自锁按钮相当于电器中的自锁功能的按钮,每按一次状态反转并保持不变。独占按钮也是自锁功能的按钮,不同的是当独占按钮为ON时,其余所有按钮均被置为OFF…

SmartEDA革新电路设计:告别繁琐,轻松步入智能时代!

在数字化浪潮席卷而来的今天&#xff0c;电路设计的复杂性和繁琐性一直是工程师们面临的难题。然而&#xff0c;随着科技的进步&#xff0c;一款名为SmartEDA的电路设计工具应运而生&#xff0c;它以智能化、高效化的特点&#xff0c;彻底颠覆了传统电路设计的方式&#xff0c;…

在3dmax软件中如何快速创建毛发?---模大狮模型网

在3D建模和渲染中&#xff0c;为角色或物体添加逼真的毛发效果是提升场景真实感的重要步骤之一。然而&#xff0c;手动一根一根创建毛发是非常繁琐的&#xff0c;因此掌握如何在软件中快速生成和调整毛发效果至关重要。模大狮将详细介绍如何利用3ds Max 2018创建毛发&#xff0…

Salia PLCC cPH2 远程命令执行漏洞(CVE-2023-46359)

漏洞描述 Salia PLCC cPH2 v1.87.0 及更早版本中存在一个操作系统命令注入漏洞&#xff0c;该漏洞可能允许未经身份验证的远程攻击者通过传递给连接检查功能的特制参数在系统上执行任意命令。 产品界面 fofa语法 "Salia PLCC" POC GET /connectioncheck.php?ip1…

发论文idea来了!强化学习+Transformer,29个创新点汇总

基于Transformer的强化学习&#xff08;TRL&#xff09;是一种利用Transformer模型架构来改进和增强强化学习算法性能的方法。 这种方法通过结合Transformer模型强大的表示能力和强化学习的决策优化框架&#xff0c;显著提升了智能体的学习能力和适应能力&#xff0c;为我们解…

dockerfile文件的中的命令

# 基础镜像 FROM registry.cn-beijing.aliyuncs.com/205erp/myopenjdk:8.6 # 设置工作目录 WORKDIR /opt # 拷贝jar包到工作目录 COPY target/*.jar app.jar RUN ls # 设置暴漏的端口 EXPOSE 8080 # 启动jar包 CMD java ${JAVA_TOOL_OPTIONS} -jar app.jar

N7745A Keysight 是德 多端口光功率计 简述

N7745A光功率计专为表征多端口光器件而设计&#xff0c;适用于多路复用器、PON分路器、波长选择开关&#xff08;WSS&#xff09;和ROADM等多端口器件的测试。它可以节省通道空间&#xff0c;通过LAN或USB连接进行并行编程&#xff0c;集成多种设备到单一设置&#xff0c;提高了…