【vector的模拟实现】

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

目录

1 类的成员变量

2 常用成员函数和迭代器

3 增删查改

3.1 reverse

3.2 push_back

3.3 resize

3.4 insert && erase

4 默认成员函数

4.1 构造函数

4.2 拷贝构造

4.3 赋值运算符重载

4.4 析构函数


前面我们详细介绍了string类的使用,vector的使用与string相差不大,大家可以自己到官网上查询:vector的使用

(注意下面的实现是参考stl SGI版本3.0)

1 类的成员变量

namespace grm
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
    private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;
	};
}

 这里面与之前我们实现的顺序表有所不同的是里面的成员变量全是指针,_start是开始数据的地址,_finish是有效数据下一位的地址,_endofstorage是数据最大容量的下一位地址。

【vector的模拟实现】,C++初阶,算法,c++,数据结构


 2 常用成员函数和迭代器

        
        size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _endofstorage - _start;
		}
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
		const T& operator[](size_t i)
		{
			return _finish[i];
		}
		T& operator[](size_t i)const
		{
			return _finish[i];
		}

这些都很简单,就不在多说了。


3 增删查改

3.1 reverse

大家看看下面这种写法有没有问题?

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}
				
				_start = tmp;
				_finish = _start + size();
				_endofstorage = _start + n;
			}
		}

大家回顾一下size()我们是用_finish - _start实现的,这样的话使用size()的时候_finish还没有更新,而_start已经被更新了,那不就g了吗,正确的处理方式有两种,一种是先更新_start,另外一种是先保存size()。

方法1:

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start;
				}
				
				//写法1:
				_finish = tmp + size();
				_start = tmp;
				_endofstorage = _start + n;
			}
		}

 写法2:

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();//先保存size()
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}
				
				//写法2:
				_start = tmp;
				_finish = tmp + sz;
				_endofstorage = _start + n;
			}
		}

 这样无论先更新还是后更新都可。

大家仔细看看上面这种写法还有没有问题?

顺便问一句,这里能够用memcpy吗?

如果T是内置类型的话那还好没啥问题,那如果T是一个自定义类型就会出大问题,因为memcpy进行的是浅拷贝,那析构的话就又会将同一块空间析构两次而出错。

正确的做法是一个一个赋值拷贝:

        void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				size_t sz = size();
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * sz);//浅拷贝,如果是自定义类型就会发生重复析构
					for (int i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				//错误写法
				/*_start = tmp;
				_finish = _start + size();//使用size()时_finish还没有更新
				_endofstorage = _start + n;*/

				//写法1:
				_finish = tmp + size();
				_start = tmp;
				_endofstorage = _start + n;

				//写法2:
				/*_start = tmp;
				_finish = tmp + sz;
				_endofstorage = _start + n;*/
			}
		}
结论:如果对象中涉及到资源管理时,千万不能使用 memcpy 进行对象之间的拷贝,因为 memcpy 是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

3.2 push_back

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

3.3 resize

        void resize(size_t n, const T& x = T())//用T类型创造出来的匿名对象
		{
			if (n > capacity())
			{
				reserve(n);
			}
			while (_finish != _endofstorage)
			{
				*_finish = x;
				_finish++;
			}
		}

3.4 insert && erase

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

			if (_finish >= _endofstorage)
			{
				//扩容了要更新pos
				size_t gap = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + gap;
			}
			iterator end = _finish ;
			while (pos < end)
			{
				*end = *(end - 1);
				--end;
			}
			*pos = val;
			++_finish;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);

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

大家想想为啥扩容了要更新pos?假如不更新pos,当扩容了后pos的值肯定会发生改变,那么我们就不能够使用原先的pos来访问数据了,否则就非法越界了。这也是迭代器失效的问题。大家想想string会迭代器失效吗?答案也是会的,不过由于string我们常用的是下标访问所以一般来说不会失效。

插入可以用返回值接受,为啥不用引用呢?

有些场景下可能不适用:

        //有些场景下不适用,像下面这里:
		v.insert(v.begin(), -1);//这里v.begin()是用一个具有常属性的临时变量保存的,不能够修改,与形参类型矛盾了。
		for (auto e : v)
		{
			cout << e << " ";
		}

 大家再思考一下下面这段程序:

    void test5()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(2);
		v.push_back(5);
		//删除所有为2的数字
		vector<int>::iterator it = find(v.begin(), v.end(), 2);
		while (it != v.end())
		{
			//vector<int>::iterator ret = find(it, v.end(), 2);
			auto ret = find(it, v.end(), 2);
			if (ret != v.end())
			{
				it =ret;
				v.erase(it);
			}
			++it;
		}
		for (auto& e : v)    cout << e << " ";
    }

这种使用方法肯定是错误的,因为it在不断的往下面走可能会失效,正确应该修改为:

            if (ret != v.end())
			{
				it =ret;
				v.erase(it);
			}
			else
			{
				++it;
			}

4 默认成员函数

4.1 构造函数

           vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{}

4.2 拷贝构造

传统写法:

        vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			memcpy(_start, v._start, sizeof(T) * v.size());
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}

这里也是一样,正确的做法是一个一个赋值拷贝:

//传统写法
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T) * v.size());//这里也是浅拷贝
			for (int i = 0; i < v.size(); i++)
				_start[i] = v._start[i];
			_finish = _start + v.size();
			_endofstorage = _start + v.capacity();
		}

我们知道现代写法就是不用我们自己造轮子,通过构造函数来帮助我们实现,但是我们又发现是无法用我们刚才实现的构造函数来完成tmp对象的创建的,所以我们又得重新再写一个构造函数:

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

这里的InputIterator取名C++是有默认规定的,迭代器分类:

input_iterator(只写)  output_iterator(只读) 没有实际对应的类型
forward_iterator(单向) <forward_list> <unordered_map> <unordered_set>
bidirectional_iterator(双向) <list> <map> <set>
randomaccess_iterator(随机) <vector> <deque> <string>

有了这个构造函数后就能够用现代写法了:

        //现代写法
		//void swap(vector& v)//可以这么写,但不推荐,可读性不高
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}
		vector(const vector<T>& v)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T> tmp(v.begin(),v.end());
			swap(tmp);
		}

4.3 赋值运算符重载

传统写法:

        //传统写法
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				T* tmp = new T[v.capacity()];
				//memcpy(tmp, v._start, sizeof(T) * v.size());浅拷贝
				for (int i = 0; i < v.size(); i++)
					tmp[i] = v._start[i];
				delete[] _start;
				_start = tmp;
				_finish = _start + v.size();
				_endofstorage = _start + v.capacity();
			}
			return *this;
		}

现代写法:

        //现代写法1:
		vector<T>& operator=(const vector<T>& v)
		{
			vector<T> tmp(v);
			swap(tmp);
			return *this;
		}

		//现代写法2:
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

这里与之前string的类似。

4.4 析构函数

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

有需要的老铁可以在博主的码云中获取源码:https://gitee.com/monday-sky/text_cpp/commit/6521a7399bdf5ebccf028e5fa130cccbd57d4dcchttps://gitee.com/monday-sky/text_cpp/commit/6521a7399bdf5ebccf028e5fa130cccbd57d4dcc文章来源地址https://www.toymoban.com/news/detail-793513.html

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

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

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

相关文章

  • C++初阶-vector类的模拟实现

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

    2024年02月04日
    浏览(152)
  • 【C++初阶】10. vector的使用及模拟实现

    vector的文档介绍 vector是表示可变大小数组的序列容器。 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。 必要时查

    2024年02月06日
    浏览(51)
  • 【C++初阶】STL详解(四)vector的模拟实现

    本专栏内容为:C++学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C++。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C++ 🚚代码仓库:小小unicorn的代码仓库🚚 🌹🌹🌹关注我带你学习编程知识 注:为了防止与标准库当中的vector产生命名冲突

    2024年02月05日
    浏览(43)
  • 第11章:C语言数据结构与算法初阶之排序

    排序是一种非常重要的算法。 排序 :所谓排序,就是使一串记录,按照其中的某个或某些的大小,递增或递减的排列起来的操作。 稳定性 :假定在待排序的记录序列中,存在多个具有相同的的记录,若经过排序,这些记录的相对次序保持不变,即在原序列中,

    2024年02月12日
    浏览(46)
  • 【数据结构初阶】算法的时间复杂度和空间复杂度

    1.1 如何衡量一个算法的好坏 如何衡量一个算法的好坏呢? 比如对于以下斐波那契数列: 斐波那契数列的递归实现方式非常简洁,但简洁一定好吗?那该如何衡量其好与坏呢? 1.2 算法的复杂度 算法在编写成可执行程序后,运行时需要耗费时间资源和空间(内存)资源 。因此

    2024年02月08日
    浏览(74)
  • 『初阶数据结构 • C语言』③ - 算法分析专业工具——大O记法

    本文内容借鉴一本我非常喜欢的书——《数据结构与算法图解》。学习之余,我决定把这本书精彩的部分摘录出来与大家分享。        从之前的章节中我们了解到,影响算法性能的主要因素是其所需的步数。 然而,我们不能简单地把一个算法记为“22步算法”,把另一个算

    2024年02月16日
    浏览(47)
  • 数据结构初阶(用C语言实现简单数据结构)--栈和队列

    ✨✨欢迎来到T_X_Parallel的博客!!       🛰️博客主页:T_X_Parallel       🛰️专栏 : 数据结构初阶       🛰️欢迎关注:👍点赞🙌收藏✍️留言 这小猫真好看 言归正传,通过上篇有关顺序表和链表的博客,可以了解到线性表的一些大致特征,这篇博

    2024年02月08日
    浏览(41)
  • 【数据结构初阶】之堆(C语言实现)

    前言 :在二叉树基础篇我们提到了二叉树的顺序实现,今天让我们来学习一下特殊的二叉树———堆的相关知识。 📃 博客主页: 小镇敲码人 💞 热门专栏:数据结构与算法 🚀 欢迎关注:👍点赞 👂🏽留言 😍收藏 🌏 任尔江湖满血骨,我自踏雪寻梅香。 万千浮云遮碧月

    2024年04月09日
    浏览(81)
  • 初阶数据结构之队列的实现(六)

    👻作者简介:M malloc,致力于成为嵌入式大牛的男人 👻专栏简介:本文收录于 初阶数据结构 ,本专栏主要内容讲述了初阶的数据结构,如顺序表,链表,栈,队列等等,专为小白打造的文章专栏。 👻相关专栏推荐:LeetCode刷题集,C语言每日一题。 本章我将详细的讲解关于

    2024年02月08日
    浏览(43)
  • 【数据结构之树】初阶数据结构之树的实现及其各种方式(上)

    👻作者简介:M malloc,致力于成为嵌入式大牛的男人 👻专栏简介:本文收录于 初阶数据结构 ,本专栏主要内容讲述了初阶的数据结构,如顺序表,链表,栈,队列等等,专为小白打造的文章专栏。 👻相关专栏推荐:LeetCode刷题集,C语言每日一题。 本篇文章我将详细的讲解

    2024年02月17日
    浏览(48)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包