C++必修:STL之vector的模拟实现
创始人
2024-11-15 05:36:36
0

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

为了让我们更加深入理解vector,接下来我们将模拟实现一个·简易版的vector。而为了和STL库中的vecotr以示区分,我们将使用命名空间namespace对其封装。

1. vector的成员变量

vector的底层其实就是我们之前在数据结构学习的顺序表,但是与顺序表不同的是vector的成员变量是三个迭代器,也可以说是三个指针。

下面是vector的成员变量:

namespace betty  { 	template 	class vector      { 	public:     //... 	private: 		iterator _start; 		iterator _finish; 		iterator _end_of_storage; 	}; } 

其中start指向起始位置,_finish指向有效数据末尾的后一个位置,最后_end_of_storage指向容量大小末尾的后一个位置。

img

2. vector的成员函数

在知道vector的成员变量之后,接下来我们将探究vector的成员函数,而常见成员函数的用法我们早在之前就已经介绍过了 ,下面我们将来具体实现一下:

2.1. vector的迭代器

首先我们来模拟实现一下迭代器iterator,而在vector中迭代器iteratorstring中的迭代器类似就是一个指针。所以我们直接使用typedef实现

typedef char* iterator;//普通迭代器 typedef const char* const_iterator;//const迭代器 

接下来我们来实现begin()end(),其中begin()指向的是数组的起始位置即_start,而end指向有效长度最后的下一位即_finish的位置。

iterator begin() { 	return _start; }  iterator end() { 	return _finish; } 

实现完普通迭代器之后,我们可以顺便重载一个const_iterator的版本。

const_iterator begin()  const { 	return _start; }  const_iterator end()	const { 	return _finish; } 

我们知道在vector中还有一个反向迭代器,这个我们在之后会统一实现。

2.2. vector的初始化与销毁

2.2.1. 构造函数与拷贝构造

我们之前在学习vector时知道其初始化方式有很多,可以通过默认构造函数给其初始化,n个val初始化,也可以通过迭代器初始化。

首先我们写一个默认构造函数,将其所有变量都设为空。

vector()     :_start(nullptr)     ,_finish(nullptr)     ,_end_of_storage(nullptr) {     ; } 

接下来我们来实现迭代器初始化,而因为我们可以通过其他容器的迭代器对其初始化,所以要通过模版来实现。

template vector(InputIterator first, InputIterator last) {     while (first != last)     {         push_back(*first);         ++first;     } } 

最后我们来实现n个val初始化。

vector(size_t n, const T& val = T()) {     resize(n, val); } vector(int n, const T& val = T()) {     resize(n, val); } 

至于为什么要同时重载intsize_t两种不同类型,那是为了防止在传两个int类型的参数时被编译器交给模版InputIterator识别,然后报错。

拷贝构造也十分简单,直接拷贝就行,但是也有一些注意事项。

vector(const vector& v) {     _start = new T[v.capacity()];//开辟capacity的空间     for (size_t i = 0; i < v.size(); ++i)     {         _start[i] = v._start[i];//进行深拷贝     }     _finish = _start + v.size();//更新_finish     _end_of_storage = _start + v.capacity();//更新_end_of_storage } 

这里注意不能利用memcpy()等库函数进行拷贝,因为这些函数都是进行的浅拷贝。如果模版参数Tstringvector等自定义类型,当程序结束回收内存时就会发生内存错误。

img

当然我们也可以通过一个取巧的方式来实现拷贝构造。

vector(vector& v) {     // 根据v的capacity()去开出对应的空间     reserve(v.capacity());     //进行深拷贝     for (size_t i = 0; i < v.size(); i++)     {         push_back(v[i]);     } } 

首先通过构造出一个与数组相同的数组v,然后让this所指向的数组与其交换,这样出了作用域之后销毁的就是原this所指向的数组。当然我们必须先将this所指向的数组先初始化扩容。

2.2.2. 赋值重载与析构函数

赋值运算符重载与拷贝构造的实现就非常类似了,直接实现即可。

vector operator = (vector v) {     swap(v);     return *this; } 

最后我们实现析构函数,只需要清理资源即可

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

2.3. vector的容量操作

2.3.1. 有效长度与容量大小

首先我们先实现返回数组有效长度的size() 与容量大小的capacity()。并且为了适配const对象,最后用const修饰this指针。

size_t size() const {     return _finish - _start; } size_t capacity() const {     return _end_of_storage - _start; } 
2.3.2. 容量操作

接下来我们来实现扩容函数reserve()与·resize(),其中reserve()最简单,只要新容量大于旧容量就发生扩容,其中注意需要提前记录size大小,防止数组异地扩容原数组释放之后找不到原数组大小。

void reserve(size_t n) {     //提前原本记录长度     size_t sz = size();     if (n > capacity())     {         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 + sz;         _end_of_storage = _start + n;     } } 

resize()的逻辑就比较复杂,需要分三种情况讨论。设字符串原来有效长度为size,容量为capacity,新容量为n

  1. n时,resize会删除有效字符到指定大小。
  2. size时,resize会补充有效字符(默认为0)到指定大小。
  3. n>capacity时,resize会补充有效字符(默认为0)到指定大小。
void resize(size_t n,const T&val=T()) {     if (n < size())     {         //更新数组大小         _finish = _start + n;     }     else     {         //扩容         reserve(n);         while (_finish != _start + n)         {             *_finish = val;             ++_finish;         }     } } 

2.4. vector的访问操作

为了符合我们C语言访问数组的习惯,我们可以先重载operator[]。当然我们也要提供两种不同的接口:可读可写与可读不可写。并且使用引用返回,减少不必要的拷贝。

// 可读可写 T& operator[](size_t pos) {     assert(pos < size());     return _start[pos]; } // 可读不可写 T& operator[](size_t pos)const {     assert(pos < size());     return _start[pos]; } 

同理我们也可以实现front()back()函数。

// 可读可写 char& front() { 	return _start[0]; } char& back() { 	return _start[_size() - 1]; } // 可读不可写 const char& front()const { 	return _start[0]; } const char& back()const { 	return _start[_size() - 1]; } 

2.5. vector的修改操作

2.5.1. 常见的修改操作

首先我们将实现两个常用的修改函数:push_back()pop_back()

void push_back(const T& x) {     //判断是否扩容     if (_finish == _end_of_storage)     {         size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity();         reserve(newCapacity);     }     *_finish = x;     ++_finish; } void pop_back() {     --_finish; } 

随后我们来实现数组的交换swap()函数,我们知道vector的交换其实就是指针_start_finish_end_of_storage的交换。

void swap(vector& v) {     std::swap(_start, v._start);     std::swap(_finish, v._finish);     std::swap(_end_of_storage, v._end_of_storage); } 

img

2.5.2. 迭代器失效

接下来我们实现insert()erase()两个函数。其中insert()在插入时可能扩容,这时就需要记录起始长度,方便更新迭代器返回。

iterator insert(iterator pos, const T& x) {     assert(pos <= _finish && pos >= _start);     //检查是否扩容     if (_finish == _end_of_storage)     {         //先记录长度         size_t len = pos - _start;         size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;         reserve(newCapacity);         //更新迭代器指向新空间         pos = _start + len;     }     //往后覆盖     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 end = pos + 1;     while (end != _finish)     {         *(end - 1) = *end;         ++end;     }     --_finish;     return pos; } 

3. 源码

#pragma once namespace betty { 	template 	class vector 	{ 	public: 		typedef T* iterator; 		typedef const T* const_iterator; 		vector() 			:_start(nullptr) 			,_finish(nullptr) 			,_end_of_storage(nullptr) 		{ 			; 		} 		vector(size_t n, const T& val = T()) 		{ 			resize(n, val); 		} 		vector(int n, const T& val = T()) 		{ 			resize(n, val); 		} 		template 		vector(InputIterator first, InputIterator last) 		{ 			while (first != last) 			{ 				push_back(*first); 				++first; 			} 		} 		//vector(const vector& v) 		//{ 		//	_start = new T[v.capacity()];//开辟capacity的空间 		//	for (size_t i = 0; i < v.size(); ++i) 		//	{ 		//		_start[i] = v._start[i];//循环拷贝 		//	} 		//	_finish = _start + v.size();//更新_finish 		//	_end_of_storage = _start + v.capacity();//更新_end_of_storage 		//} 		vector(vector& v) 		{ 			// 根据v的capacity()去开出对应的空间 			reserve(v.capacity()); 			//进行深拷贝 			for (size_t i = 0; i < v.size(); i++) 			{ 				push_back(v[i]); 			} 		} 		vector operator=(vector v) 		{ 			swap(v); 			return *this; 		} 		iterator begin() 		{ 			return _start; 		} 		iterator end() 		{ 			return _finish; 		} 		const_iterator begin()const 		{ 			return _start; 		} 		const_iterator end()const 		{ 			return _finish; 		} 		size_t size() const 		{ 			return _finish - _start; 		} 		size_t capacity() const 		{ 			return _end_of_storage - _start; 		} 		void reserve(size_t n) 		{ 			//提前原本记录长度 			size_t sz = size(); 			if (n > capacity()) 			{ 				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 + sz; 				_end_of_storage = _start + n; 			} 		} 		void push_back(const T& x) 		{ 			//判断是否扩容 			if (_finish == _end_of_storage) 			{ 				size_t newCapacity = capacity() == 0 ? 4 : 2 * capacity(); 				reserve(newCapacity); 			} 			*_finish = x; 			++_finish; 		} 		void resize(size_t n,const T&val=T()) 		{ 			if (n < size()) 			{ 				_finish = _start + n; 			} 			else 			{ 				reserve(n); 				while (_finish != _start + n) 				{ 					*_finish = val; 					++_finish; 				} 			} 		} 		T& operator[](size_t pos) 		{ 			assert(pos < size()); 			return _start[pos]; 		} 		T& operator[](size_t pos)const 		{ 			assert(pos < size()); 			return _start[pos]; 		} 		iterator insert(iterator pos, const T& x) 		{ 			assert(pos <= _finish && pos >= _start); 			//检查是否扩容 			if (_finish == _end_of_storage) 			{ 				//先记录长度 				size_t len = pos - _start; 				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2; 				reserve(newCapacity); 				//更新迭代器指向新空间 				pos = _start + len; 			} 			//往后覆盖 			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 end = pos + 1; 			while (end != _finish) 			{ 				*(end - 1) = *end; 				++end; 			} 			--_finish; 			return pos; 		} 		void pop_back() 		{ 			--_finish; 		} 		void swap(vector& v) 		{ 			std::swap(_start, v._start); 			std::swap(_finish, v._finish); 			std::swap(_end_of_storage, v._end_of_storage); 		} 		~vector() 		{ 			delete[]_start; 			_start = _finish = _end_of_storage = nullptr; 		} 	private: 		iterator _start; 		iterator _finish; 		iterator _end_of_storage; 	}; } ase(iterator pos) 		{ 			assert(pos >= _start && pos < _finish); 			iterator end = pos + 1; 			while (end != _finish) 			{ 				*(end - 1) = *end; 				++end; 			} 			--_finish; 			return pos; 		} 		void pop_back() 		{ 			--_finish; 		} 		void swap(vector& v) 		{ 			std::swap(_start, v._start); 			std::swap(_finish, v._finish); 			std::swap(_end_of_storage, v._end_of_storage); 		} 		~vector() 		{ 			delete[]_start; 			_start = _finish = _end_of_storage = nullptr; 		} 	private: 		iterator _start; 		iterator _finish; 		iterator _end_of_storage; 	}; } 

相关内容

热门资讯

专业人士如何选?揭秘4款202... 在这个数字化时代,无论是教学、演示、游戏直播还是软件操作,电脑录屏软件已...
人生低谷来撸C#--021 多... 1、概念线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的...
Python应用—加密、解密文... 1.创作需求日常生活中我们有很多文件想要保密。这个脚本可以方便大家对所有的文件类型进行加密ÿ...
CSS雷达光波效果(前端雷达光... 前言CSS雷达光波效果是一种视觉动画效果,常用于模仿雷达扫描或检测的视觉反馈。这种效果...
前端必知必会-html中inp... 文章目录HTML type的设置输入类型文本输入类型密码输入类型提交输入类型重置输入类型单选按钮输入...
Docker 网络模式 目录一. 默认网络驱动程序a. Bridge 网络b. Host 网络c. Overlay 网络d....
C++ bind复杂回调逻辑分...  回调函数基本知识回顾回调函数是什么函数指针或者函数对象作为参数传递给另一个函数的机制,...
mac清除dns缓存指令 ma... 你是否曾经被要求清理dns缓存并刷新?清理dns缓存一般是由于修改了主机文件ÿ...
63、ELK安装和部署 一、ELK日志系统1.1、ELK平台的定义ELK平台是一套完整的日志集中处理解决方案,...
wifi无线使用adb 要通过Wi-Fi使用ADB连接安卓设备,可以遵循以下步骤进行操作:通过U...