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

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


前言

本文带你进入vector的模拟实现,对于vector,是我们深入学习STL的必要途径。


一、vector的成员函数

根据库的实现方式,成员函数如下:

iterator _start = nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;

c++11开始可以对成员变量使用缺省值,在这里我们可以使用缺省值。

二、增删查改工作

说明size()和capapcity()

【C++STL】模拟实现vector容器,C++,c++,java,开发语言

size的大小为_finish - _start
capacity的大小为_end_of_storage - _start

2.1reserve()

该函数的作用是:扩容。

void reserve (size_type n);
  • 思路:
  • 1.如果要扩容的大小n小于capacity,则不会缩容。
  • 2.否则执行扩容。
    • 先申请一块n大小的空间
    • 拷贝原空间的数据到新空间,并释放原空间
    • 将新空间的数据交给_start管理。
//扩容
void reserve(size_t n)
{
	//防止有些情况是直接调用
	if (n > capacity())
	{
		size_t oldsize = size();//必须要记录旧的size,旧的size是用旧的_finish - _start获得的
		iterator tmp = new T[n];
		//memcpy(tmp, _start, sizeof(T) * size());
		//这里如果使用memcpy函数的话,如果vector对象是自定义类型,比如string,会造成浅拷贝问题。
		for (size_t i = 0; i < oldsize;i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		//_finish = _start + size();
		//如果这样用的话,size()是用旧的_finish - 新的_start,不是连续的空间,没有意义
		_finish = _start + oldsize;//_finish必须更新,旧的_finish迭代器已经失效了
		_end_of_storage = _start + n;
	}
	//tmp不用处理,出了作用域自动调用析构
}

注意:在扩容时,如果是自定义类型,比如string,使用memcpy函数进行拷贝数据,会出现浅拷贝问题。
【C++STL】模拟实现vector容器,C++,c++,java,开发语言
v1会拷贝给v2,v2的指针仍然指向v1的string对象所指向的空间。这就造成了两次调用析构函数释放同一块空间的问题。
正确的解法是:
在拷贝时调用string的赋值运算符重载,完成深拷贝。

【C++STL】模拟实现vector容器,C++,c++,java,开发语言

还有一个点是:

注意:在这里会出现迭代器失效的问题。

【C++STL】模拟实现vector容器,C++,c++,java,开发语言

此时空间已满,则需要重新申请空间。

【C++STL】模拟实现vector容器,C++,c++,java,开发语言

重新申请空间后,_start和_end_of_stortage都会指向新的空间,唯独_finish还指向旧的已经被释放的空间,此时如果这样更新_finish位置:

_finish = _start + size();

这里的size()是用旧的_finish - 新的_start得到,是没有意义的。

解决方案:
保存旧的size,随后_start加上旧的size即为_finish的位置。

2.2 resize()

该函数的功能是:重新改变vector容器的大小,让其到达n。新的空间默认初始化为val
思路:

  • 1.如果n小于size,则让_finish指向_start + n位置。
  • 2.如果n大于等于size,先调用reserve函数进行扩容,再将新的空间填充为val。
void resize(size_t n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		//扩容到n,如果n<capacity(),则啥事不做,否则,扩容到n
		reserve(n);
		//将多的空间全部设置成缺省值
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

注意这里的缺省值为T(),是一个T类型的匿名对象。
不管T是什么,都可以使用模板实例化出具体的匿名对象,即使是内置类型,比如int,也可以实例化出int(),这里的值为0

2.3 insert()

【C++STL】模拟实现vector容器,C++,c++,java,开发语言
在这里我们只实现最常用的接口。

该函数的作用是:

在pos位置插入模板对象val。

思路:

  • 插入元素前先检查容量
  • 如果容量满了,则调用reserve函数执行扩容操作
  • 然后从pos位置开始将其之后的元素全部都往后挪。
  • 再在pos位置插入val。
void insert(iterator pos, const T& val)
{
	assert(pos <= _finish);
	//插入前先检查扩容
	if (_finish == _end_of_storage)
	{
		size_t oldpos = pos - _start;//记录相对位置
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);

		pos = _start + oldpos;
	}

	//记录旧的size,防止出现迭代器失效问题。

	//将pos之后的位置往后挪,再插入
	iterator end = _finish - 1; // reserve之后,_finish也跟着更新了。
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}

	*pos = val;
	_finish += 1;
}

注意:insert在扩容时仍然会出现迭代器失效的问题。
解决方案:提前记录pos的相对位置,即oldpos = pos - _start;
然后再扩容完成后,更新pos 的位置,pos = _start + oldpos;
否则在扩容后,旧的pos迭代器就已经失效了。

2.4 erase()

该函数的功能是:删除pos位置的值。

//在删除最后一个位置或者只剩下一个待删除元素时,会出现迭代器失效问题。
iterator erase(iterator pos)
{
	assert(pos < _finish);
	iterator end = pos;
	while (end != _finish)
	{
		*end = *(end + 1);
		++end;
	}
	--_finish;

	return pos;
}

有一个需要注意的点:当我们删除元素后,pos迭代器就会失效,因为空间已经释放,这时候不能使用pos迭代器。
【C++STL】模拟实现vector容器,C++,c++,java,开发语言

解决方法:返回删除位置的下一个元素的位置,即pos位置。
然而,在只剩下一个待删除元素或者删除最后一个位置的元素时,不能再使用pos迭代器。

2.5 push_back()和pop_back()

顾名思义,这两个函数的功能是在最后一个位置插入元素和删除最后一个位置的操作。
直接复用insert和erase函数即可,这里不再赘述。

三、[]重载和迭代器

typedef T* iterator;
typedef const T* const_iterator;

3.1begin迭代器

返回_start地址即可。

iterator begin()
{
	return _start;
}

const_iterator begin() const
{
	return _start;
}

3.2 end迭代器

返回_finish地址即可。

iterator end()
{
	return _finish;
}

const_iterator end() const
{
	return _finish;
}

3.3 []运算符重载

思路:只需给定pos下标即可。
在此之前需要判断pos位置不能超过整个顺序表的size大小。

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

//不可修改的
const T& operator[]( size_t pos) const
{
	assert(pos < size());
	return *(_start + pos);
}

四、默认成员函数

4.1构造函数

构造函数可分为3种:

  • 无参构造
  • 构造n个val对象
  • 根据迭代区间构造

对于无参的构造,我们只需要初始化成员函数即可。

  • 1.无参构造
//无参构造函数
vector()
//:_start(nullptr)
//,_finish(nullptr)
//,_end_of_storage(nullptr)
{}
  • 2.构造n个val对象
//构造n个对象
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	//1.开空间
	reserve(n);
	//2.将空间填充为val对象
	for (size_t i = 0; i < capacity(); i++)
	{
		_start[i] = val;
	}
	//也可以复用resize
	//resize(n, val);
}

注意:这里在构造时必须初始化,如果不初始化,在调用reserve函数开空间时会出现访问野指针的问题。
也可以调用resize函数进行拷贝构造n个对象。

  • 3.使用迭代区间进行构造

同理,如果不初始化,会出现扩容后访问野指针的情况。

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

4.2 拷贝构造函数

  • 1.拷贝构造传统写法
//拷贝构造,必须实现深拷贝
//传统写法
//v1(v)
vector(const vector<T>& v)
	//这里必须初始化,否则扩容时会访问不属于自己的空间
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
	//如果私有成员给了缺省值,就不用再再次初始化了
{
	reserve(v.capacity());
	//memcpy(_start, v._start, sizeof(T) * v.size());
	//这里使用memcpy的错误跟扩容的错误一样,不再赘述
	for (size_t i = 0; i < v.size(); i++) // 如果是string类,会调用string的赋值重载,赋值重载调用string的拷贝构造,完成深拷贝
	{
		_start[i] = v._start[i];
	}

	_finish = _start + v.size();
}

拷贝构造需要注意的几个问题:

  • 1.拷贝构造同构造函数一样,如果不初始化,在开空间时会出现访问野指针的情况。
  • 2.拷贝构造必须进行深拷贝,否则如果vector的对象是自定义类型,比如string,会出现两次释放同一块空间的问题。

4.3 析构函数

释放_start指向的空间,然后全部置为空即可。

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

总结

vector的模拟实现就到此结束了,如果对你有帮助的话,不妨来一个关注吧!文章来源地址https://www.toymoban.com/news/detail-570533.html

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

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

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

相关文章

  • 【C++】:STL源码剖析之vector类容器的底层模拟实现

    构造一个空vector size和capacity为0 将_start _finish _endofstorage 都置为空指针即可 传统写法 : 1). 新开辟一块和 v 同样容量的空间,更新 _start, _finish, _endofstorage 2). 将 v 中的数据拷贝到新开辟的空间中 注意 : 不要使用memcpy函数拷贝数据,如果数据是内置类型或浅拷贝的自定义类型

    2024年02月04日
    浏览(37)
  • 【STL】vector的模拟实现

    目录 前言 结构解析 构造析构 构造 默认构造 初始化成 n 个 val  以迭代器区间构造 拷贝构造 析构 运算符重载 赋值重载 下标访问 迭代器 const迭代器 容量操作 查看大小和容量 容量修改 数据修改 尾插尾删 指定位置插入和删除 insert erase 清空 判空 交换 源码 从vector开始就要开

    2024年02月06日
    浏览(56)
  • 【STL】模拟实现vector(详解)

    2023年05月14日
    浏览(27)
  • 【STL】 模拟实现简易 vector

    目录 1. 读源码 2. 框架搭建 3. vector 的迭代器 4. vector 的拷贝构造与赋值 拷贝构造 赋值 5. vector 的常见重要接口实现 operator[ ] 的实现 insert 接口的实现 erase 接口实现 pop_back 接口的实现 resize 接口实现 源码分享 写在最后: 想要自己实现一个 vector,读源码来理解他的实现是必不

    2024年02月16日
    浏览(26)
  • [STL] vector 模拟实现详解

    目录 一,准备工作 二,push_back   1, 关于引用 2. 参数const 的修饰  补充 三,迭代器实现 四,Pop_back 五,insert 1. 补充——迭代器失效 六, erase 七,构造函数  1. 迭代器构造  2. 其他构造 3. 拷贝构造  1) 传统写法 2)现代写法(提高函数复用性)  八,赋值符号重载 九,

    2024年02月16日
    浏览(26)
  • 【STL】:vector的模拟实现

    朋友们、伙计们,我们又见面了,本期来给大家解读一下有关vector的模拟实现,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成! C 语 言 专 栏: C语言:从入门到精通 数据结构专栏: 数据结构 个  人  主  页 : stackY、 C + + 专 栏   : C++ Linux 专

    2024年02月06日
    浏览(84)
  • C++ STL vector 模拟实现

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

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

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

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

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

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

    2024年02月11日
    浏览(31)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包