【C++练级之路】【Lv.7】【STL】vector类的模拟实现

这篇具有很好参考价值的文章主要介绍了【C++练级之路】【Lv.7】【STL】vector类的模拟实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


【C++练级之路】【Lv.7】【STL】vector类的模拟实现,进击的C++,c++,开发语言,stl,容器,数据结构

快乐的流畅:个人主页
个人专栏:《C语言》《数据结构世界》《进击的C++》
远方有一堆篝火,在为久候之人燃烧!

引言

关于STL容器的学习,我们来到了运用最广泛、最常见的vector。有了之前关于string的学习,我们对容器设计有了一个大概的了解,而今天在熟悉的基础上去探求vector相比于string有哪些异同,同时迎来更多的新挑战……

一、成员变量

vector类中包含了

  • _start(指向有效空间的头)
  • _finish(指向有效空间的尾)
  • _end_of_storage(指向可用空间的尾)

细节:

  1. 三个成员变量均迭代器(此刻即指针)
  2. 使用缺省值,不必写多份初始化列表
template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
private:
	iterator _start = nullptr;
	iterator _finish = nullptr;
	iterator _end_of_storage = nullptr;
};

二、默认成员函数

2.1 constructor

无参构造

vector()
{}

带参构造

细节:

  1. 分别重载 size_t 和 int 类型,防止参数匹配时,匹配到迭代器区间构造,从而导致间接寻址错误
  2. 初始化的val的缺省值,是匿名构造的对象
vector(size_t n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		_start[i] = val;
	}
	_finish = _start + n;
}

vector(int n, const T& val = T())
{
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
		_start[i] = val;
	}
	_finish = _start + n;
}

迭代器区间构造

细节:

  1. 使用类模板,可以传任意类型的迭代器
  2. 迭代器访问,条件最好使用不等于(!=)
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

2.2 destructor

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

2.3 copy constructor

近代写法

细节:

  1. 先开辟一维空间
  2. 再用赋值重载,进行深拷贝(不能用memcpy,它是浅拷贝)
vector(const vector<T>& x)
{
	_start = new T[x.capacity()];
	for (size_t i = 0; i < x.size(); ++i)
	{
		_start[i] = x._start[i];
	}
	_finish = _start + x.size();
	_end_of_storage = _start + x.capacity();
}

现代写法

细节:

  1. 迭代器区间构造,构造出临时对象
  2. 再使用vector中的swap,交换*this和tmp的值,完成拷贝构造
vector(const vector<T>& x)
{
	vector<T> tmp(x.begin(), x.end());
	swap(tmp);
}

2.4 operator=

近代写法

细节:大体与拷贝构造相同

vector<T>& operator=(const vector<T>& x)
{
	if (this != &x)
	{
		_start = new T[x.capacity()];
		for (size_t i = 0; i < x.size(); ++i)
		{
			_start[i] = x._start[i];
		}
		_finish = _start + x.size();
		_end_of_storage = _start + x.capacity();
	}
	return *this;
}

现代写法

细节:

  1. 传参变成传值,这样就会拷贝构造出一个临时对象
  2. 再使用vector中的swap,交换*this和tmp的值,完成赋值重载
vector<T>& operator=(vector<T> x)
{
	swap(x);
	return *this;
}

三、迭代器

3.1 begin

迭代器的实现和编译器有关,不同的编译器有不同的实现方式。这里用指针来实现迭代器

同时,重载了普通迭代器和const迭代器。

iterator begin()
{
	return _start;
}

const_iterator begin() const
{
	return _start;
}

3.2 end

迭代器遵循左闭右开的原则,begin指向首元素,end指向末元素的下一位。

iterator end()
{
	return _finish;
}

const_iterator end() const
{
	return _finish;
}

悄悄告诉你范围for的底层实现,就是运用了迭代器。

四、元素访问

4.1 operator[ ]

为了方便的访问元素,我们重载了[ ]运算符。同时,也分为普通版本和const版本,对应不同vector类的权限。

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.1 size

获取当前有效数据个数

细节:const修饰,保证普通和const类型vector类都能访问

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

5.2 capacity

获取当前最大有效容量

细节:同上

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

看了上面size和capacity的实现,是不是就瞬间明白_start、_finish和_end_of_storage的含义了?

悄悄告诉你:其实当你不懂成员变量的含义时,可以先看看size和capacity的实现


5.3 reserve

改变当前最大容量

细节:

  1. 只扩容,不缩容
  2. 赋值重载,进行深拷贝
  3. 更新成员变量时(如果按照顺序更新),先保存size的大小,防止_finish失效。因为如果为_finish = tmp + size(),等价于_finish = tmp + _finish - _start,而_start已经更新了,所以size()计算的大小失效,最终_finish并没有更新。
void reserve(size_t n)
{
	if (n > capacity())
	{
		T* tmp = new T[n];
		if (_start)
		{
			for (size_t i = 0; i < size(); ++i)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		size_t sz = size();
		_start = tmp;
		_finish = tmp + sz;
		_end_of_storage = tmp + n;
	}
}

5.4 resize

改变当前有效数据个数

细节:

  1. 如果n<size,则减少有效个数,如果n>size,则填充指定值,直至达到n个
  2. 运用赋值重载,实现深拷贝
void resize(size_t n, T val = T())
{
	if (n > size())
	{
		reserve(n);
		for (size_t i = size(); i < n; ++i)
		{
			_start[i] = val;
		}
	}
	_finish = _start + n;
}

5.5 empty

判断是否为空

细节:const修饰,保证普通和const类型vector类都能访问

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

六、修改

6.1 push_back

尾插

细节:需要扩容时,判断容量是否为空

void push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	}

	*_finish = val;
	++_finish;
}

6.2 pop_back

尾删

细节:断言vector不为空,才进行删除

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

6.3 insert

指定位置插入

细节:

  1. 断言判断pos的合法性
  2. 扩容前,先保存pos的相对位置,扩容后,刷新pos,防止迭代器失效
  3. 返回指向新插入元素的迭代器,防止迭代器失效
iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos <= _finish);

	if (_finish == _end_of_storage)
	{
		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 = val;
	++_finish;
	return pos;
}

6.4 erase

指定位置删除

细节:

  1. 断言判断pos的合法性
  2. 返回指向删除元素的后一位的迭代器,防止迭代器失效
iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

	iterator start = pos + 1;
	while (start < _finish)
	{
		*(start - 1) = *start;
		++start;
	}

	--_finish;
	return pos;
}

上述有两种迭代器失效:

  1. 野指针
  2. 指向含义改变

关于迭代器失效,我们统一认为,进行过插入或删除操作的迭代器pos,已经失效,不能再使用。只有接收其返回值,刷新pos,才能重新使用。


6.5 swap

交换两个vector类的值

细节:使用std库中的swap函数,交换各个成员变量的值

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

总结

我们在有了学习string的基础后,学习vector的成本降低了不少,函数名和用法大体相同。但是,我们依旧遇到了新的问题与挑战,如多层深拷贝,迭代器失效等。我与C++的故事,仍在无声地诉说……


【C++练级之路】【Lv.7】【STL】vector类的模拟实现,进击的C++,c++,开发语言,stl,容器,数据结构文章来源地址https://www.toymoban.com/news/detail-821868.html

真诚点赞,手有余香

到了这里,关于【C++练级之路】【Lv.7】【STL】vector类的模拟实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++练级之路】【Lv.12】继承(你真的了解菱形虚拟继承吗?)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 继承(inheritance),是面向对象的三大特性之一。 它是面向对象编程中, 使代码可以复用 的最重要的手段,它允许程序员在 保持原有类特性的基础上进行扩展

    2024年03月14日
    浏览(48)
  • 【C++练级之路】【Lv.15】AVL树(双子旋转,领略绝对平衡之美)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 之前讲解了二叉搜索树,最优情况下它具有非常好的搜索性能,但是在极端场景下,它可能退化为单支树,可以形象地称为歪脖子树~ 这样的话,它搜索的时间

    2024年03月25日
    浏览(39)
  • 【C++练级之路】【Lv.14】二叉搜索树(进化的二叉树——BST)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 二叉树在之前的数据结构章节讲解过,当时使用C来实现。而如今学习的二叉搜索树,便是二叉树的进阶,也更适合使用C++来实现。 二叉搜索树(BST,Binary Se

    2024年03月23日
    浏览(33)
  • 【C++练级之路】【Lv.16】红黑树(冰与火的碰撞,红与黑的史诗)

    快乐的流畅:个人主页 个人专栏:《C语言》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 之前学习的AVL树,是一种平衡二叉搜索树,它追求绝对平衡,从而导致插入和删除性能较差。而今天学习的红黑树,是另一种平衡二叉搜索树,它追求相对平衡

    2024年04月09日
    浏览(62)
  • 【C++练级之路】【Lv.20】位图和布隆过滤器(揭开大数据背后的神秘面纱)

    快乐的流畅:个人主页 个人专栏:《算法神殿》《数据结构世界》《进击的C++》 远方有一堆篝火,在为久候之人燃烧! 哈希映射 的思想,在实际中有许多运用,之前介绍的 哈希表 是一种经典的应用场景,而今天我们将了解其他的哈希数据结构—— 位图和布隆过滤器 ,它

    2024年04月12日
    浏览(49)
  • 【C++练级之路】【Lv.4】类和对象(下)(初始化列表,友元,static成员,编译器的优化)

    欢迎各位小伙伴关注我的专栏,和我一起系统学习C++,共同探讨和进步哦! 学习专栏 : 《进击的C++》 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象

    2024年02月04日
    浏览(57)
  • C++初阶-vector类的模拟实现

    C++ STL中的vector就类似于C语言当中的数组,但是vector又拥有很多数组没有的接口,使用起来更加方便。 相比于STL中的string,vector可以定义不同的数据类型。 迭代器的本质可以暂时看作是指针,模拟实现vector,需要定义三个指针:指向起始位置_start,指向最后一个有效元素的下

    2024年02月04日
    浏览(152)
  • 【C++ STL】vector模拟实现

    2023年05月17日
    浏览(51)
  • C++ STL vector 模拟实现

    ✅1主页:我的代码爱吃辣 📃2知识讲解:C++之STL 🔥3创作者:我的代码爱吃辣 ☂️4开发环境:Visual Studio 2022 💬5前言:上次我们已经数字会用了vector,这次我们对其底层更深一步挖掘,其中重点是,Vector中一些深浅拷贝问题。 目录 一.Vector模拟实现的整体框架 二. Vector的构

    2024年02月13日
    浏览(35)
  • C++ [STL之vector模拟实现]

    本文已收录至《C++语言》专栏! 作者:ARMCSKGT vector是STL容器容器之一,其底层实现类似于数据结构顺序表,相当于string来说得益于泛型模板的加持使得vector可以变为任何类型,且是可以动态扩容,堪称大号数组!在vector的实现中,有许多值得我们学习的细节,接下来将为大家

    2024年02月11日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包