IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> vector的模拟实现 -> 正文阅读

[数据结构与算法]vector的模拟实现

📌vector的模拟实现

😊本文为小碗里原创,CSDN首发
📅发布时间:2022/3/20
🙌欢迎大家👍点赞?收藏?加关注
?本文大约2200词左右
🙏笔者水平有限,如有错误,还望告诉笔者,万分感谢!
🚩有什么问题也可在评论区一起交流哦!

📣前言

在熟悉了vector的使用后,让我们来模拟一下vector的实现吧!

(声明:标准库中vector的实现比较复杂,本文旨在加深对vector使用的理解,若按照标准库的标准模拟实现,要牵扯其他的知识,不利于我们学习vector)

🎨模拟实现

一.主干结构

namespace V
{
    //类模板
	template<class T>
	class vector
	{
        //迭代器(指针是一个天然的随机迭代器)
        //关于迭代器,之后我们会详谈
		typedef T* iterator;
	public:
		//...
	private:
        //迭代器可看作一个指针,但也有区别
		iterator _start;//起始位置
		iterator _finish;//最后一个元素下一位置
		iterator _end_of_Storage;//最大容量位置下一位
	};
}

这里是用迭代器去控制vector的,我们知道vector可看作一个可控大小的顺序表,对于顺序表的实现,我们知道有三个参数,一个是指向数组的指针a,一个是数组的大小size,还有一个是数组的容量capacity.所以对于vector来说,我们可以对比着顺序表来看.其实这三个迭代器就类似于顺序表中的三个参数.start指向内存空间起始位置,finish指向最后一个元素的下一位,end_of_storage指向最大容量位置下一位.

在这里插入图片描述

二.成员函数

🚀取容量 capacity()

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

🚀取大小 size()

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

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

🚀构造函数(1)

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

🚀析构函数

~vector()
	{
		//_start != nullptr
		if (_start)
		{
			delete[] _start;
			_start = _finish = _end_of_Storage = nullptr;
		}
	}

🚀拷贝构造函数

Traditional

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

Modern

vector(const vector<T>& v)
		:_start(nullptr), _finish(nullptr), _end_of_Storage(nullptr)
		{
         //调用构造函数
			vector<T> tmp(v.begin(), v.end());
         //自己实现的swap效率高,若用库里的swap(v1,v2),会有3次深拷贝
			swap(v);
		}

这里需要说明的是,对于vector拷贝构造函数的现代写法的思想与之前string的拷贝构造函数的现代写法思想一致,就是希望定义一个临时对象,其调用构造函数来开辟一块空间,但是对于vector来说,它在这里就不能用一开始写的构造函数了.

对于该使用需求,vector还有一种构造函数,它是通过一个迭代器区间初始化.

🚀构造函数(2)

//对于InputIterator的取名不能随意改,这是规范
//一个类模板的成员函数,也可以是一个函数模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
			:_start(nullptr), _finish(nullptr), _end_of_Storage(nullptr)
		{
			while (first != last)
			{
            //尾插,下文给出
				push_back(*first);
				first++;
			}
		}

🚀尾插 push_back

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

🚀增容 reserve

对于增容reserve函数,这里有一个易错点,以下是错误代码,大家能看出哪儿错了吗?

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();
				_end_of_Storage = _start + n;
			}
		}

让我们揭晓答案~~

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;
            //size()函数内部的实现是_finish-_start
            //原空间被释放后,_start指向新开辟的空间的起始位置,而_finish还是指向原来的空间位置
            //故在求size()时答案错误
				_finish = _start + size();
				_end_of_Storage = _start + n;
			}
		}

不难发现,问题就出在求size()的时候start的位置更新了,要解决这个问题就是要保持start和finish的相对位置,或是保存一份size(),所以这里就有两种解决方法:

//写法一:
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
                    //内存拷贝(字节序拷贝)
					memcpy(tmp, _start, sizeof(T)*size());
					delete[] _start;
				}
				//求得size()后再更新_start
				_finish = tmp + size();
				_start = tmp;
				_end_of_Storage = _start + n;
			}
		}
//写法二:
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//保存一份size
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T)*sz);
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_Storage = _start + n;
			}
		}

🚀重置大小 resize()

 //缺省值是一个匿名对象,调用了它自己的构造函数
void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);//增容
				}
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}

对于resize()的实现,我相信大家都看得懂,但是大家可能会有一个疑惑:

🔎这里使用了匿名对象传缺省值,匿名对象的生命周期不是只在当前行吗?

🔎val是T()的引用,那该行结束,T()生命周期结束,val不也应该失效吗?

🔎那为什么程序没有出错?

🔎如果T是像int,double这样的内置类型,它们也有自己的构造函数?

为了不影响本文的篇幅和结构,关于匿名对象生命周期的问题,我在下面链接所指的文章中有详细的解释,感兴趣的老铁可以点击链接,前去查看~~

关于匿名对象生命周期的讨论

🚀赋值运算符重载 operator=

vector<T>& operator=(const vector<T> v)//v是实参的拷贝构造出来的临时对象
		{                                //函数调用完自动调用析构函数销毁
			swap(v);
			return *this;
		}

🚀插入 insert

iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start&&pos <= _finish);
			if (_finish == _end_of_Storage)
			{
				//保存pos位置,防止增容后pos位置失效
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//更新pos位置
			}
			iterator end = _finish;
			while (end != pos)
			{
				*end = *(end - 1);
				end--;
			}
			*pos = x;
			_finish++;
			return pos;
		}

对于插入操作,要注意的就是增容带来的迭代器失效问题!

不仅如此,我们发现insert还返回了插入的位置,原因是insert的pos实参到形参是传值,如果增容了,函数里的pos更新了,但是外面的pos没有更新,为了不让外面的pos迭代器失效,insert支持了返回更新后的pos.

🚀尾删 pop_back

void pop_back()
		{
			assert(_finish > _start);
			_finish--;
		}

🚀删除 erase

iterator erase(iterator pos)
		{
			assert(pos >= _start&&pos < _finish);
			iterator cur = pos + 1;
			while (cur < _finish)
			{
				*(cur - 1) = *cur;
				cur++;
			}
			_finish--;
			return pos;//更新pos位置
		}

三.完整代码

#include<iostream>
#include<assert.h>
using namespace std;

namespace V
{
	template<class T>
	class vector
	{
		typedef T* iterator;
	public:
		size_t capacity() const 
		{
			return _end_of_Storage - _start;
		}

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

		vector()
			:_start(nullptr), _finish(nullptr), _end_of_Storage(nullptr)
		{}
		~vector()
		{
			//_start != nullptr
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_Storage = nullptr;
			}
		}
      
		//Modern写法
		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(const vector<T>& v)
		:_start(nullptr), _finish(nullptr), _end_of_Storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(v);
		}

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

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

		//写法一:
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T)*size());
					delete[] _start;
				}
				//求得size()后再更新_start
				_finish = tmp + size();
				_start = tmp;
				_end_of_Storage = _start + n;
			}
		}
		//写法二:
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				//保存一份size
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T)*sz);
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_Storage = _start + n;
			}
		}
      
        //缺省值是一个匿名对象,调用了它自己的构造函数
		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);//增容
				}
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}

		vector<T>& operator=(const vector<T> v)
		{
			swap(v);
			return *this;
		}

		void pop_back()
		{
			assert(_finish > _start);
			_finish--;
		}

		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start&&pos <= _finish);
			if (_finish == _end_of_Storage)
			{
				//保存pos位置,防止增容后pos位置失效
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;//更新pos位置
			}
			iterator end = _finish;
			while (end != pos)
			{
				*end = *(end - 1);
				end--;
			}
			*pos = x;
			_finish++;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start&&pos < _finish);
			iterator cur = pos + 1;
			while (cur < _finish)
			{
				*(cur - 1) = *cur;
				cur++;
			}
			_finish--;
			return pos;//更新pos位置
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_Storage;
	};
}

🎶代码练习

大家可以将下面代码拷贝到编译器上,自己写一写,练习一下!

#include<iostream>
#include<assert.h>
using namespace std;

namespace V
{
	template<class T>
	class vector
	{
		typedef T* iterator;
	public:
      //取容量
		size_t capacity() const 
		
		//取大小
		size_t size() const
		
		//构造函数
		vector()
			
      //析构函数
		~vector()

      //拷贝构造函数
		vector(const vector<T>& v)

      //迭代器实现构造函数
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)

      //尾插
		void push_back(const T& x)

		//增容(两种写法)
		void reserve(size_t n)

      //重置大小
		void resize(size_t n, const T& val = T())

      //赋值运算符重载
		vector<T>& operator=(const vector<T> v)

      //pos位置插入数据
		iterator insert(iterator pos, const T& x)

      //删除pos位置元素
		iterator erase(iterator pos)

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_Storage;
	};
}

文章到这就结束了,大家有什么问题欢迎在评论区讨论,博主一定及时回复!觉得文章写的不错的老铁也可以点赞收藏加关注,后续还会有更多的好文分享哦!

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-03-22 20:49:54  更:2022-03-22 20:50:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/9 1:56:48-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码