C++从入门到起飞之——vector模拟实现 全方位剖析!

news2024/11/17 8:22:50

 

🌈个人主页:秋风起,再归来~
🔥系列专栏:C++从入门到起飞          
🔖克心守己,律己则安

目录

1、vector的成员变量

2、迭代器

3、size与capacity

4、[]运算符重载

5、reserve

6、push_back

7、empty 

8、pop_back

9、resize 

10、swap 

11、insert 

12、erase 

 13、构造函数系列

14、clear与赋值运算符重载 

 15、析构函数

16、vector类模版的打印函数

17、完结散花


1、vector的成员变量

>vector其实就是我们所熟悉的顺序表,我们在之前模拟实现顺序表的时候用到了一个指针a指向了我们要动态管理的内容,用size记录有效数据的个数,用capacity记录容量的大小。而在C++stl中顺序表vector的实现所用的成员变量却有所不同!

我们在查看了stl中vector的部分源代码后发现类模版vector中的成员变量是三个迭代器。

再通过查看源代码中的typedef可以发现vector中的迭代器iterator就是类模版参数T的原始指针!

>那这三个成员变量到底是什么意思呢?其实我们通过变量的命名就可以大致猜出他们的意义:start指向顺序表的开始,finish指向有效数据的结尾,end_of_storage指向有效空间的结尾。

但这仅仅只是我们的猜测而已,我们还要通过看源代码来证实我们的猜测! 

将源代码和文档中vector迭代器的功能介绍相结合我们不难证实我们的猜测是正确的!

2、迭代器

// Vector的迭代器是一个原生指针
typedef T* iterator;
typedef const T* conat_iterator;
iterator begin()
{
	return start;
}
iterator end()
{
	return finish;
}
conat_iterator begin() const
{
	return start;
}
conat_iterator end() const
{
	return finish;
}

3、size与capacity

size_t size()const
{
	return (finish - start);
}
size_t capacity()const
{
	return (end_of_storage - start);
}

4、[]运算符重载

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

5、reserve

>我们常犯的错误写法:

void reserve(size_t n)
{
	if (n > capacity())
	{
		iterator tmp = new T[n];
		//memcpy(tmp, start, sizeof(T) * size()); 浅拷贝
		for (size_t i = 0; i < size(); i++)
		{
			tmp[i] = start[i];
		}
		delete[] start;
		start = tmp;
		finish = start + size();
		end_of_storage = start + capacity();
	}
}

错误的原因就在于,我们更新finish和end_of_storage时size是用旧空间的finish减新空间的start(我们先更新了start),而我们期望的是 旧空间的finish减旧空间的start,我们可以采用提前保存一份相对位置的方法来解决这个问题!

>正确的写法:

//reserve
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_len = finish - start;
		iterator tmp = new T[n];
		//memcpy(tmp, start, sizeof(T) * size()); 浅拷贝
		for (size_t i = 0; i < size(); i++)
		{
			tmp[i] = start[i];
		}
		delete[] start;

		start = tmp;
		finish = start + old_len;
		end_of_storage = start + n;
	}
}

>特别要注意的一点是,我们之前在模拟实现string类时,使用memcpy来拷贝数据雀氏没有问题,不过,在vector类模版中我们还用memcpy来一个一个字节的来拷贝数据就会有大问题!当vector中的元素是内置类型时倒也没有问题,但是,当元素是自定义类型且里面的数据有申请资源时(如元素为string对象)就会有大坑。原因就在于, memcpy是浅拷贝,我们扩容时,新空间和旧空间的资源是同一块,我们释放旧空间后,新空间所对应的资源也就不存在了!

6、push_back

//push_back
void push_back(const T& val)
{
	if (size() == capacity())
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*finish = val;
	++finish;
}

7、empty 

//empty
bool empty()
{
	return start == finish;
}

8、pop_back

//pop_back
void pop_back()
{
	assert(!empty());
	finish--;
}

9、resize 

>注意当n小于size时,resize相当于删除数据,当n>size时,在size后面用val填充。这里val的缺省值是用T()匿名构造的对象!

//resize(大多数情况用来初始化)
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;
		}
	}
}

10、swap 

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

11、insert 

关于insert这里我想要说一点关于迭代器失效的问题,一定要记住,我们在pos位置插入数据后,pos就失效了,我们一定不要再用pos去访问数据了。虽然我们在insert内部已近对pos进行了更新,但形参的改变不会影响实参。这里如果用引用也是行不通的,当我们用一个容器的迭代器去传参时,函数返回一个临时变量(具有常性),不能传给普通的引用。更不能加const,不然内部的pos无法更新! 如果实在要用到pos,那我们就必须在外面更新pos,用pos接收函数的返回值即可!

//insert
iterator insert(iterator pos, const T& val)
{
	assert(pos >= start);
	assert(pos < finish);
	//扩容
	if (finish == end_of_storage)
	{
		size_t old_pos = pos - start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = start + old_pos;
	}
	iterator tmp = finish;
	while (tmp!=pos)
	{
		*tmp = *(tmp - 1);
		tmp--;
	}
	*pos = val;
	finish++;
	return pos;
}

12、erase 

erase也同样面临迭代器失效的问题,我们在使用的时候一定要注意!

//erase
iterator erase(iterator pos)
{
	assert(pos >= start);
	assert(pos < finish);
	iterator tmp =pos + 1;
	while (tmp != finish)
	{
		*(tmp-1) = *tmp;
		tmp++;
	}
	finish--;
	return pos;
}

 13、构造函数系列

>默认构造

//构造函数
//vector() {};
//C++11 前置生成默认构造
vector() = default;

 >n个value构造

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

>迭代器构造 

//迭代器构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

关于迭代器构造这里头也有一些小坑:

void test6()
{
	vector<int> v1(10, 1);
	print_container(v1);
}

这里我们期望用10个1来构造v1

这里报了一个非常奇怪的报错,我明明期望调用的不是迭代器的构造,但这里却调到了!

究其原因在于,编译器通过我们传递的参数选择调用最匹配的函数, vector(size_t n, const T & val = T())的参数类型一个为size_t,另一个为int,而vector(InputIterator first, InputIterator last)通过推导俩个参数的类型都为int,整形在传参时默认是int类型,所以编译器认为最为匹配的函数是迭代器构造函数!

那我们要怎么解决这个问题呢?其实非常简单,我们只要再重载一个俩个参数类型都为int的构造函数就可以了!

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

此时编译器看到有现成的最为匹配的函数当然不会再通过函数模版去生成一个函数,毕竟编译器和我们一样喜欢偷懒! 

 >vector(const vector<T>&v)

//vector(const vector& v)
vector(const vector<T>&v)
{
	reserve(v.size());
	/*for (size_t i = 0; i < v.size(); i++)
	{
		push_back(v[i]);
	}*/
	for (auto& e : v)
	{
		push_back(e);
	}
}

14、clear与赋值运算符重载 

//clear
void clear()
{
	finish = start;
}

 >赋值运算符重载

//赋值运算符重载
/*vector<T>& operator=(const vector<T>&v)
{
	if (this != &v)
	{
		clear();
		reserve(v.size());
		for (auto& e:v)
		{
			push_back(e);
		}
	}
	return *this;
}*/
vector<T>& operator=(vector<T> tmp)
{
	swap(tmp);
	return *this;
}

 15、析构函数

//析构函数
~vector()
{
	if (start)
	{
		delete[] start;
		start = finish = end_of_storage = nullptr;
	}
		
}

16、vector类模版的打印函数

template<class T>
void print_vector(const vector<T>& v)
{
	// 规定,没有实例化的类模板里面取东西,编译器不能区分这里const_iterator
	// 是类型还是静态成员变量
	//typename vector<T>::const_iterator it = v.begin();
	auto it = v.begin();
	while (it != v.end())
	{
	    cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

值得注意的是:以上代码中vector<T>::const_iterator it = v.begin();这种写法是有问题的!

C++规定,没有实例化的类模板里面取东西,编译器不能区分这里const_iterator是类型还是静态成员变量!

正确的写法:typename vector<T>::const_iterator it = v.begin()auto it = v.begin()

17、完结散花

好了,这期的分享到这里就结束了~

如果这篇博客对你有帮助的话,可以用你们的小手指点一个免费的赞并收藏起来哟~

如果期待博主下期内容的话,可以点点关注,避免找不到我了呢~

我们下期不见不散~~

​​​​​​

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

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

相关文章

LSTM结合时序异常检测直接写!小论文闭着眼睛发!

还在愁小论文&#xff1f;不如考虑考虑这个方向&#xff1a;LSTM时间序列异常检测。 这是个比较活跃且热门的研究方向&#xff0c;因为LSTM具有非常优秀的时序数据深度处理能力&#xff0c;能够灵活适应不同复杂度的数据&#xff0c;给我们提供高精度的预测结果&#xff0c;在…

时间继电器和定时器

一、概述 1.时间继电器是可以在设定的定时周期内或周期后闭合或断开触点的元器件。 2.时间继电器上可设定的定时周期数量有限&#xff0c;多为一个或两个。定时时长从0.02s至300h(根据产品型号范围不同)。 3.定时器可以理解为一台钟表&#xff0c;它在某个时间点上闭合(断开…

PostgreSQL如何设置主键自增(序列、SERIAL)

文章目录 PostgreSQL如何设置主键自增背景什么是序列Postgresql的自增机制基本使用使用SERIAL或BIGSERIAL数据类型手动创建序列和设置默认值实战demo&#xff1a;PostgreSQL 手动序列管理设置序列的当前值 工作常用总结创建表时候自定义序列&#xff1a;id SERIAL PRIMARY KEY …

调用具体接口的所有实现类

Java获取接口的所有实现类方法-CSDN博客https://blog.csdn.net/feeltouch/article/details/135399078

最实用接地气的 .NET 微服务框架

目录 前言 项目介绍 快速入门 1、服务注册 2、启动UI 3、服务发现与调用 4、启动服务网关 项目地址 最后 前言 微服务架构已经成为搭建高效、可扩展系统的关键技术之一&#xff0c;然而&#xff0c;现有许多微服务框架往往过于复杂&#xff0c;使得我们普通开发者难以…

基于生成对抗模型GAN蒸馏的方法FAKD及其在EdgesSRGAN中的应用

文章目录 FAKD系列论文paper1: FAKD&#xff1a;用于高效图像超分辨率的特征亲和知识蒸馏&#xff08;2020&#xff09;ABSTRACT1. INTRODUCTION2. PROPOSED METHOD2.1. Feature Affinity-based Distillation (FAKD) 2.2. Overall Loss Function3. EXPERIMENTAL RESULTS3.1. Ex…

TypeSript9 命名空间namesapce

我们在工作中无法避免全局变量造成的污染&#xff0c;TypeScript提供了namespace 避免这个问题出现 内部模块&#xff0c;主要用于组织代码&#xff0c;避免命名冲突。命名空间内的类默认私有通过 export 暴露通过 namespace 关键字定义 TypeScript与ECMAScript 2015一样&…

React学习day02-React事件绑定、组件、useState、React组件样式处理方式

3、React事件绑定&#xff08;以点击事件为例&#xff09; &#xff08;1&#xff09;语法&#xff08;整体遵循驼峰命名法&#xff09;&#xff1a;on事件名称{事件处理程序} 比如&#xff1a;点击事件onClick&#xff08;类似于vue中的click&#xff09; &#xff08;2&…

成为Python高手,我能给出的最好建议

今天笔者将向大家分享5个良好的Python编程习惯&#xff0c;大牛认证&#xff0c;通过不断实践&#xff0c;助你写出更Pythonic的代码&#xff0c;让你向Python大师之路更进一步。 今天笔者将向大家分享5个良好的Python编程习惯&#xff0c;大牛认证&#xff0c;通过不断实践&a…

Java面试题精选:消息队列(二)

一、Kafka的特性 1.消息持久化&#xff1a;消息存储在磁盘&#xff0c;所以消息不会丢失 2.高吞吐量&#xff1a;可以轻松实现单机百万级别的并发 3.扩展性&#xff1a;扩展性强&#xff0c;还是动态扩展 4.多客户端支持&#xff1a;支持多种语言&#xff08;Java、C、C、GO、…

WPF中如何根据数据类型使用不同的数据模板

我们在将一个数据集合绑定到列表控件时&#xff0c;有时候想根据不同的数据类型&#xff0c;显示为不同的效果。 例如将一个文件夹集合绑定到ListBox时&#xff0c;系统文件夹和普通文件夹分别显示为不同的效果&#xff0c;就可以使用模板选择器功能。 WPF提供了一个模板选择…

查找4(散列表)

1&#xff09;基本概念 、 2)散列函数的构造 3&#xff09;解决冲突 I&#xff09;开放地址发 II&#xff09;链地址法 4&#xff09;散列表的查找

vs2022 C++ 使用MySQL Connector/C++访问mysql数据库

1、下载MySQL Connector/C&#xff0c;我这里下载的是debug版本&#xff0c;下载链接MySQL :: Download MySQL Connector/C (Archived Versions) 2、解压并且放到MySQL文件夹中&#xff0c;便于使用 3、打开vs2022&#xff0c;右键项目&#xff0c;点击属性 4、在 “C/C” ->…

el-input中show-password密码提示功能去掉

el-input中show-password密码提示功能去掉 一、效果图二、封装个组件三、如何使用 一、效果图 二、封装个组件 <template><divclass"el-password el-input":class"[size ? el-input-- size : , { is-disabled: disabled }]"><inputclass…

Java线上监控诊断产品Arthas(续集)

Java线上监控诊断产品Arthas&#xff08;续集&#xff09; 前言1.auth指令2.monitor指令解读 3.classloader指令场景 4.dump指令场景 5.getstatic指令场景 6.heapdump指令场景 7.profiler指令场景 8.sc指令场景 9.trace指令场景 前言 在去年&#xff0c;我发表了一片文章&…

心血管内科常用评估量表汇总,附操作步骤与评定标准

心血管内科常用量表来评估患者病情、预测风险&#xff0c;量表在制定治疗方案和预测疾病进展等方面发挥着重要作用。常笑医学整理了6个心血管内科常用的评估量表&#xff0c;支持下载和在线使用&#xff0c;供临床医护人员参考。 01 GRACE缺血风险评估 &#xff08;完整量表请点…

Qt 学习第7天:Qt核心特性

元对象系统Meta-object system 来自AI生成&#xff1a; Qt中的元对象系统&#xff08;Meta-Object System&#xff09;是Qt框架的一个核心特性&#xff0c;它为Qt提供了一种在运行时处理对象和类型信息的能力。元对象系统主要基于以下几个关键概念&#xff1a; 1. QObject&a…

【91-136】行为型模式

目录 一.模板方法模式 1.1 概述 1.2 结构 1.3 案例 1.4 优缺点 1.5 使用场景 二.策略模式 2.1 概述 2.2 结构 2.3 案例 2.4 优缺点 2.5 使用场景 2.6 JDK 源码解析 三.命令模式 3.1 概述 3.2 结构 3.3 案例 3.4 优缺点 3.5 使用场景 四.责任链模式 4.1 概…

NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis 翻译

NeRF&#xff1a;将场景表示为用于视图合成的神经辐射场 引言。我们提出了一种方法&#xff0c;该方法通过使用稀疏的输入视图集优化底层连续体场景函数来实现用于合成复杂场景的新视图的最新结果。我们的算法使用全连通&#xff08;非卷积&#xff09;深度网络来表示场景&…

设计模式(一):七大原则

*设计模式的目的* 编写软件过程中,程序员面临着来自耦合性,内聚性以及可维护性,可扩展性,重用性,灵活性等多方面的挑战,设计模式是为了让程序(软件),具有更好 1) 代码重用性 (即:相同功能的代码,不用多次编写) 2) 可读性 (即:编程规范性, 便于其他程序员的阅读和理…