【STL】 模拟实现简易 vector

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

目录

1. 读源码

2. 框架搭建

3. vector 的迭代器

4. vector 的拷贝构造与赋值

拷贝构造

赋值

5. vector 的常见重要接口实现

operator[ ] 的实现

insert 接口的实现

erase 接口实现

pop_back 接口的实现

resize 接口实现

源码分享

写在最后:


1. 读源码

想要自己实现一个 vector,读源码来理解他的实现是必不可少的一个步骤,

但是,当我们拿到 vector 的源码之后,一堆代码,我们应该从何看起呢?

我们当然是从一个类的核心读起,也就是从他的成员变量开始读:

【STL】 模拟实现简易 vector,C++学习,STL

这里我们找到了他的成员变量,他的类型是 iterator,这又是个啥,

我们来溯源一下:

【STL】 模拟实现简易 vector,C++学习,STL

我们可以看到,实际上 iterator 就是一个T* 的指针类型,

而 iterator 是迭代器,这里我们也可以大致猜到,vector 的迭代器其实就是原生指针。

回归正题,那他的成员函数有什么作用呢?

这个时候我们就可通过去看他的:构造函数 + 插入接口,来进一步的了解:

 【STL】 模拟实现简易 vector,C++学习,STL

 先来看构造函数,其他的就是一些重载,而且具体的实现也封装起来了,

但是我们看他的默认构造也看不出什么名堂来,只是都初始化成0了,

那我们还是得去看看他的插入接口:

【STL】 模拟实现简易 vector,C++学习,STL

源码的 push_back 说,如果 finish != end_of_storage 就调 construct 然后 finish++

这里我们可以先猜一下源码的意思,他给了 start,finish,end_of_storage,

那其实我们可以菜 start 是数组开始的位置,finish 是结束的位置,

end_of_storage 是数组容量的最后一个位置,那这个 if 语句的判断就是如果数组没满,

就插入一个数据,让 finish++,这里我们暂时不知道 construct 究竟是什么,

但是看源码千万不要陷进一些细节,我们先把大的框架给看好先,那这个时候,

我们就可以大概猜到,else 里面的就是需要扩容的逻辑,他调用了 insert_aux,

那我们就再去看一看这个函数:

【STL】 模拟实现简易 vector,C++学习,STL这个函数很大,我就一点一点分析啊,

一开始是又进行一次判断,这里的 insert 不一定是只被 push_back 使用的,

所以可能其他地方调用的时候需要这一个判断:

【STL】 模拟实现简易 vector,C++学习,STL

然后我们来看 else 里面的逻辑,首先这里是扩容的策略,

如果第一次就扩容成 1,如果是其他情况就双倍扩容,

然后这里调用的是 allocate 也就是STL自己的空间配置器来要内存,

应为STL比较嫌弃 malloc 开内存的速度啊,就自己内部实现了一个内存池。

【STL】 模拟实现简易 vector,C++学习,STL

 然后这一段逻辑就是拷贝数据到新的空间,

然后又调用了 construct 把数据插入进去,这里我还是先不看他的底层实现啊:

【STL】 模拟实现简易 vector,C++学习,STL

然后最后这里就是把旧的空间释放掉,然后更新成员变量:

【STL】 模拟实现简易 vector,C++学习,STL

然后我这里再补充一个小的点:

【STL】 模拟实现简易 vector,C++学习,STL这里使用的就是 try catch 来捕获异常的操作,为了防止内存泄漏 catch 这里有销毁内存的操作,

这个时候不得不吐槽一下老 C++ 程序员的爱好,使用宏,总是喜欢搞一堆宏,让人很难受啊。

回归正题啊,这里我们是想搞懂成员变量的含义啊,但是他的 push_back 封装的比较复杂,

所以我们再去看一个扩容的逻辑(reserve)验证我们刚刚的猜想:

【STL】 模拟实现简易 vector,C++学习,STL

其他的我们不在意啊,就来看着几个成员变量的操作,

start = tmp,这里就差不多能证实 start 指向的是数组的开头位置了,

finish = tmp + old_size,这里的 old_size 不就是以前的数据大小吗,那 finish 也没错,

end_of_storage = start + n,reserve 函数传来的 n 就是要扩容到的容量大小,

那我们就大致了解了他的成员变量的含义了。

刚刚说好的,来看看 construct 的实现是怎么样的:

【STL】 模拟实现简易 vector,C++学习,STL

发现没有,construct 其实就是一个定位 new,如果我们需要给一个自定义类型开空间,

那我们就不能直接调用 malloc 了,得调用该自定义类型的构造函数,

而这个 destroy 为什么也把它放出来呢,因为清理资源的时候,他调用的destroy,

其实就是在调用自定义类型的析构函数来清理资源。 

2. 框架搭建

那我们话不多说,直接开始写我们自己的 vector。

先来快速打个架子,让代码跑起来:

#pragma once

#include <iostream>
#include <vector>

using namespace std;

namespace xl {
	template<class T>
	class vector {
    public:
		typedef T* iterator;

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;

	public:
		vector()
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}
		
	public:
		iterator begin() {
			return _start;
		}

		iterator end() {
			return _finish;
		}
			 
	public:
		void reserve(size_t n) {
			if (n > capacity()) {
				size_t old_size = size();
				T* tmp = new T[n];
				if (_start) {
					for (size_t i = 0; i < size(); i++) {
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + old_size;
				_end_of_storage = _start + n;
			}
		}

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

	public:
		int size() const {
			return _finish - _start;
		}

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

我们实现了一个构造,一个 push_back,一个最基本的迭代器,

现在我们可以把代码跑起来了:

void test() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

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

这里我们是直接用范围for,因为范围for 没问题,迭代器肯定没问题。

来看结果:

【STL】 模拟实现简易 vector,C++学习,STL

这里我们再把重要的析构函数给加上:

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

3. vector 的迭代器

我们来看这样一个场景:

void Print(const vector<int>& v) {
	for (auto e : v) cout << e << " ";
	cout << endl;
}

void test2() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

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

	Print(v);
}

实际上,编译器报错了:

【STL】 模拟实现简易 vector,C++学习,STL

这是为什么呢?

我们搭建框架的时候,只实现了普通迭代器,

这里我们传参的时候加了 const 导致出现了权限放大的情况,

所以我们需要重载一份 const 迭代器:

public:
    typedef T* iterator;
    typedef const T* const_iterator;

public:
	iterator begin() {
		return _start;
	}

	iterator end() {
		return _finish;
	}

	const_iterator begin() const {
		return _start;
	}

	const_iterator end() const {
		return _finish;
	}

我们赶紧来测试一手:

void Print(const xl::vector<int>& v) {
	for (auto e : v) cout << e << " ";
	cout << endl;
}

void test2() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

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

	Print(v);
}

输出:

【STL】 模拟实现简易 vector,C++学习,STL

4. vector 的拷贝构造与赋值

拷贝构造

这里就实现一下传统写法:

// 传统写法	
vector(const vector<T>& v)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	_start = new T[v.capacity()];
	for (size_t i = 0; i < v.size(); i++) {
		_start[i] = v._start[i];
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

当然啦,实现方法有很多,怎么舒服怎么来就好~

赋值

这里我就直接用现代写法啦,因为实现起来真的和方便:

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<T>& operator=(vector<T> v) {
	swap(v);
	return *this;
}

5. vector 的常见重要接口实现

operator[ ] 的实现

T operator[](size_t pos) {
	assert(pos < size());
	return _start[pos];
}

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

来测试一下:

void test1() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	for (int i = 0; i < 5; i++) {
		cout << v[i] << " ";
	}
	cout << endl;
}

输出:

【STL】 模拟实现简易 vector,C++学习,STL

insert 接口的实现

void insert(iterator pos, const T& x) {
	assert(pos >= _start && pos <= _finish);

	if (_finish == _end_of_storage) {
		size_t len = pos - _start; // 防止迭代器失效的问题(扩容之后pos仍指向旧空间)
		size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(new_capacity);
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos) {
		*(end + 1) = *end;
		end--;
	}
	*pos = x;
	_finish++;
}

实现了 insert 之后,其实我们已经不需要自己实现 push_back 了,

直接复用 insert 就行了:

void push_back(const T& x) {
	//if (_finish == _end_of_storage) {
	//	size_t new_capacity = capacity() == 0 ? 4 : capacity() * 2;
	//	reserve(new_capacity);
	//}
	//*_finish = x;
	//_finish++;

	insert(end(), x);
}

我们来测试一下:

void test2() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

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

 还是这段代码,来看输出:

【STL】 模拟实现简易 vector,C++学习,STL

现在我们解决了 insert 内部的迭代器失效的问题,

再来看看这样一个场景:

void test2() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(4);
	v.push_back(5);

	xl::vector<int>::iterator it = v.begin() + 2;
	v.insert(it, 3);
	*it += 10;

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

}

我们插入了一个 3 ,然后把 3 += 10 ,应该打印出 13 才对,

但是,来看输出:

【STL】 模拟实现简易 vector,C++学习,STL

为什么会还是打印 3 呢?

我们调试来看看:

【STL】 模拟实现简易 vector,C++学习,STL

 目前为止还是正常的:

【STL】 模拟实现简易 vector,C++学习,STL

走到这里我们发现 it 指针变成随机值了,这是为什么?

我们虽然在 insert 实现的内部对扩容这里进行了防止迭代器失效的操作,

但是,形参的改变不影响实参,扩容之后旧空间就被释放了,导致了迭代器失效。 

那我们该怎么解决呢?

我们看看源码是咋实现的:(当有细节问题的时候,就可以看看源码的实现细节了)

【STL】 模拟实现简易 vector,C++学习,STL

源码里面使用的操作,是搞了个返回值,

就是返回指向新插入位置的迭代器,如果源码看不太懂,可以去看看文档是怎么说的:

【STL】 模拟实现简易 vector,C++学习,STL

这是文档对这个返回值的描述。

erase 接口实现

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

我们来测试一下:

void test3() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	xl::vector<int>::iterator it = v.begin();
	v.erase(it);

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

输出:

【STL】 模拟实现简易 vector,C++学习,STL

好像没什么问题,但其实并不是这样的,我们再来看一个场景:

void test3() {
	xl::vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	xl::vector<int>::iterator it = v.begin();
	while (it != v.end()) {
		v.erase(it);
		it++;
	}

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

输出:

【STL】 模拟实现简易 vector,C++学习,STL

怎么就崩了呢?

erase 以后,迭代器是有可能会失效的,我们试试库里的:

跑刚刚的代码:

【STL】 模拟实现简易 vector,C++学习,STL

实际上,VS的库里做了强制的检查,他不让我们访问 erase 之后的迭代器,

所以我们让 it++ 程序就报错了。

那库里是怎么处理的呢?

还得看看源码是怎么样的:

【STL】 模拟实现简易 vector,C++学习,STL

我们发现,他也是通过返回值来解决这个问题的,

我们也可以很容易的看出,返回值返回的就是原来位置的迭代器,

根据这个特性,我们测试一下:

void test3() {
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	vector<int>::iterator it = v.begin();
	while (it != v.end()) {
		it = v.erase(it);
	}

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

输出:

【STL】 模拟实现简易 vector,C++学习,STL确实是都删除掉了,那我来改一改我们的代码:

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

 这样就没问题了:

【STL】 模拟实现简易 vector,C++学习,STL

pop_back 接口的实现

这个就直接复用 erase 就好了:

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

resize 接口实现

这里我们先补充一个新知识:

C++ 有了模板之后,对内置类型有了升级,他们也可以使用构造函数初始化,

来看代码:

void test4() {
	int i = 0;
	int j = 1;

	int a = int();
	int b = int(1);

	cout << i << " " << j << " " << a << " " << b << endl;
}

输出:

【STL】 模拟实现简易 vector,C++学习,STL

好,我们再来看这个接口:

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

这样我们给 val 缺省值的时候,就可以覆盖自定义类型和内置类型了。

vector 的接口当然不止这些,但是最核心的我们基本都实现了,其他的接口有兴趣再实现吧~

源码分享

Gitee链接:模拟实现简易STL: 模拟实现简易STL (gitee.com)

写在最后:

以上就是本篇文章的内容了,感谢你的阅读。

如果感到有所收获的话可以给博主点一个哦。

如果文章内容有遗漏或者错误的地方欢迎私信博主或者在评论区指出~文章来源地址https://www.toymoban.com/news/detail-589997.html

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

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

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

相关文章

  • C++ STL vector 模拟实现

    C++ STL vector 模拟实现

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

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

    【C++ STL】vector模拟实现

    2023年05月17日
    浏览(35)
  • STL 关于vector的细节,vector模拟实现【C++】

    STL 关于vector的细节,vector模拟实现【C++】

    _start指向容器的头,_finish指向容器当中 有效数据 的下一个位置,_endofstorage指向整个容器的尾 先开辟一块与该容器大小相同的空间,然后将该容器当中的数据一个个拷贝过来即可,最后更新_finish和_endofstorage的值即可。 深拷贝版本一: 注意: 不能使用memcpy函数 , 如果vec

    2024年02月15日
    浏览(18)
  • C++ —— STL容器【vector】模拟实现

    C++ —— STL容器【vector】模拟实现

    本章代码gitee仓库:vector模拟实现、vector源码 看源码发现 vector 是类模板定义的,成员是采用迭代器进行管理 当涉及到容器类时,通常有一些关键函数,如构造函数、析构函数和拷贝构造函数,它们负责初始化容器对象、销毁对象和进行对象的拷贝等 这里注意拷贝构造要实现

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

    C++ [STL之vector模拟实现]

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

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

    【C++】STL 模拟实现之 vector

    vector 是我们学习的第一个真正的 STL 容器,它接口的使用方式和 string 有一点点的不同,但大部分都是一样的,所以这里我们就只演示其中一些接口的使用,大家如果有疑惑的地方直接在 cplusplus 是上面查看对应的文档即可。 vector 提供了四种构造方式 – 无参构造、n 个 val 构

    2023年04月27日
    浏览(17)
  • 【C++STL】模拟实现vector容器

    【C++STL】模拟实现vector容器

    本文带你进入vector的模拟实现,对于vector,是我们深入学习STL的必要途径。 根据库的实现方式,成员函数如下: c++11开始可以对成员变量使用缺省值,在这里我们可以使用缺省值。 size的大小为_finish - _start capacity的大小为_end_of_storage - _start 该函数的作用是:扩容。 思路:

    2024年02月16日
    浏览(8)
  • 【c++】:STL中vector的模拟使用及模拟实现

    【c++】:STL中vector的模拟使用及模拟实现

        文章目录 前言 一.使用库中vector常用接口 二.vector的模拟实现 总结   上一篇我们讲解了STL中的string的使用和模拟实现,这次我们就来讲解STL中的vector,vector相对于string来说模拟实现会难一些,难点在于迭代器失效问题和深浅拷贝问题。 首先介绍一下vector: 1. vector是表示

    2024年01月21日
    浏览(14)
  • 【STL模版库】模拟实现vector类模版

    【STL模版库】模拟实现vector类模版

    解释: [1] 对于顺序表迭代器就是指向其内部元素的指针。 [2] 下面是其结构示意图: 解释: [1] 构造函数必须要先将3个迭代器置空,否则在reserve开空间时会出现访问野指针的问题。 [2] 第二个参数用T类型的匿名对象做缺省值,相当于去调默认构造。生成的匿名对象具有常性

    2024年02月06日
    浏览(10)
  • 【C++STL】“vector“容器的模拟实现

    【C++STL】“vector“容器的模拟实现

    🎉博客主页:小智_x0___0x_ 🎉欢迎关注:👍点赞🙌收藏✍️留言 🎉系列专栏:C++初阶 🎉代码仓库:小智的代码仓库 这里的 iterator 是 typedef T* iterator; 定义来的, T 是模板参数。 _start 是指向开始的指针变量。 _finish 是指向最后一个元素的下一位的指针变量。 _endofstorage 是

    2024年02月16日
    浏览(12)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包