C++_vector简单源码剖析:vector模拟实现

文章目录

    • 🚀1.迭代器
    • 🚀2.构造函数与析构函数
      • [⚡️2.1 默认构造函数vector()](#⚡️2.1 默认构造函数vector())
      • [⚡️2.2 vector(int n, const T& value = T())](#⚡️2.2 vector(int n, const T& value = T()))
      • [⚡️2.3 赋值重载operator=](#⚡️2.3 赋值重载operator=)
      • [⚡️2.4 通用迭代器拷贝](#⚡️2.4 通用迭代器拷贝)
      • [⚡️2.5 vector(initializer_list<T> il)](#⚡️2.5 vector(initializer_list<T> il))
      • [⚡️2.6 拷贝构造vector(const vector<T>& v)](#⚡️2.6 拷贝构造vector(const vector<T>& v))
      • [⚡️2.6 析构函数~vector()](#⚡️2.6 析构函数~vector())
    • 🚀3.内存相关
    • 🚀4.获取
    • 🚀5.修改
      • [⚡️5.1 insert插入](#⚡️5.1 insert插入)
      • [⚡️5.2 erase删除](#⚡️5.2 erase删除)
      • [⚡️5.2 push_back尾插](#⚡️5.2 push_back尾插)
      • [⚡️5.3 pop_back尾删](#⚡️5.3 pop_back尾删)

大家好!本文会模拟一个基本的vector类,帮助我们更好的理解vector的内置函数的实现与规则。

先在.h文件声明每个需要实现的函数,需要实现的成员:

cpp 复制代码
namespace bit
{
	template<class T>
	class vector
	{
	public:
		//1.迭代器
		// Vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;
		iterator begin();
		iterator end();
		const_iterator begin() const ;
		const_iterator end() const;

		// 2.构造函数与析构函数
		vector();
		vector(int n, const T& value = T());
		vector<T>& operator= (vector<T> v);
	
		template<class InputIterator>
		vector(InputIterator first, InputIterator last);
		
		vector(initializer_list<T> il);
		
		vector(const vector<T>& v);
		
		~vector();
		// 3.内存相关
		size_t size() const;
		size_t capacity() const;
		void reserve(size_t n);
		void resize(size_t n, const T& value = T());
		//4.获取
		T& operator[](size_t pos);
		const T& operator[](size_t pos)const;
		//5.修改
		void push_back(const T& x);
		void pop_back();
		void swap(vector<T>& v);
		iterator insert(iterator pos, const T& x);
		iterator erase(Iterator pos);
	private:
		iterator _start; // 指向数据块的开始
		iterator _finish; // 指向有效数据的尾
		iterator _endOfStorage; // 指向存储容量的尾
	};
}

备注:private有三个成员变量,都是迭代器,_start 指向数据块的开始 ,_finish指向有效数据的尾 ,_endOfStorage指向存储容量的尾

接下来一步一步的剖析实现:

🚀1.迭代器

cpp 复制代码
	typedef T* iterator;
	typedef const T* const_iterator;
	iterator begin()
	{
		return _start;
	}
	iterator end()
	{
		return _finish;
	}
	const_iterator begin() const
	{
		return _start;
	}
	const_iterator end() const
	{
		return _finish;
	}

备注: begin()返回首元素的指针,end()返回尾元素下一个位置的指针,当然也要多实现一个const的版本,以适应const string类型

🚀2.构造函数与析构函数

cpp 复制代码
// 2.构造函数与析构函数
		vector();
		vector(int n, const T& value = T());
		vector<T>& operator= (vector<T> v);
	
		template<class InputIterator>
		vector(InputIterator first, InputIterator last);
		
		vector(initializer_list<T> il);
		
		vector(const vector<T>& v);
		~vector();

⚡️2.1 默认构造函数vector()

cpp 复制代码
vector() =default;

备注:vector 不需要特别的默认构造,用编译器生成的就行 ,我们知道,编译器在我们写了其他的构造函数时是不会生成默认构造的,所以该代码的意思是使编译器强制生成默认构造

⚡️2.2 vector(int n, const T& value = T())

cpp 复制代码
vector(int num, const T& temp = T())
{
	reserve(num);
	for (int i = 0; i < num; i++)
	{
		push_back(temp);
	}
};

备注: reserve是扩容函数,push_back是尾插函数,后面会实现。

⚡️内置类型也有构造函数

⚡️关于(重要)const T& temp = T()

在C++中,为了满足模板的需要,为内置类型也添加了默认构造函数

什么意思呢? 就是关于内置类型也可以这样初始化:

cpp 复制代码
int i = 0;
int j(1);
int k = int();
int x = int(2);

是不是很像类的初始化的形式 ?没错,在C++中,内置类型可以像类一样传参初始化,当然就如原本的内置类型一样,不传参就是随机值,传了的那个形参就是参数的值。

这样做有什么好处呢?我们回到本函数实现的代码,如果T = int , 则:

cpp 复制代码
vector(int num, const int& temp = int())
{
	reserve(num);
	for (int i = 0; i < num; i++)
	{
		push_back(temp);
	}
};

const int& temp = int()由于int也可以用类的方式给缺省值,被赋予了一个int类型的匿名临时对象,cosnt又为这个临时对象赋予常性,就可以起别名,所以这样的语法就可以通过了。

最后,const T& temp = T()的参数形式可以满足T为自定义类型,也可以满足内置类型

⚡️2.3 赋值重载operator=

cpp 复制代码
vector<T>& operator= (vector<T> v){
	swap(v);
	return (*this);
};

备注:swap是一个交换private内三个成员的函数,后面会实现。

⚡️2.4 通用迭代器拷贝

cpp 复制代码
template<class InputIterator>
vector(InputIterator first, InputIterator last){
	reserve(last- first);
	while(first != last){
		push_back(*first);
		first++;
	}
}

备注:

  1. 这里使用的是函数模板,由编译器推断迭代器类型,生成对应的函数。
  2. 该函数的意义是支持通过其他类型的迭代器来拷贝内容,例子如下:
cpp 复制代码
int main()
{
	string s1("123456");
	vector<int> test1(s1.begin(), s1.end());
	for (auto e : test1)
	{
		cout << e<<" ";
	}
}

输出:49 50 51 52 53 54

这里就做到通过string的迭代器拷贝整个s1到test1

⚡️2.5 vector(initializer_list il)

cpp 复制代码
vector(initializer_list<T> il){
	reserve(il.size());
	for (auto e : il)
	{
		push_back(e);
	}
}

备注:

  1. 先简单介绍一下 initializer_list 是什么, initializer_list是一种特殊的标准库类型,用于在函数参数或对象构造函数 中初始化列表初始化的一种方式。它允许你以简洁的方式向函数传递一组值,或者在对象的构造函数中初始化一组值,可以让函数接受不定数量的参数,而在对象构造函数中使用它可以方便地初始化成员变量。
cpp 复制代码
auto test = {1,2,3,4,5};
//这里编译器推断的类型是 initializer_list
  1. 借助 initializer_list 我们就可以传入{1,2,3,4}这种形式的数组进行初始化
cpp 复制代码
int main()
{
	vector<int> test1 = {1,2,3,4};
	for (auto e : test1)
	{
		cout << e<<" ";
	}
}

输出:1 2 3 4

⚡️2.6 拷贝构造vector(const vector& v)

cpp 复制代码
vector(const vector<T>& temp)
{
	reserve(temp.capcitity());
	for (auto e : temp)
	{
		push_back(e);
	}
};

备注:无。

⚡️2.6 析构函数~vector()

cpp 复制代码
~vector(){
	delete[] _start;
	_start = nullptr;
	_end_of_storage = nullptr;
	_finish = nullptr;
}

备注:只用释放 头迭代器_start 就行了。

🚀3.内存相关

cpp 复制代码
// 3.内存相关
		size_t size() const{
			_finish - _start;
		}
		size_t capacity() const{
			_end_of_storage - _start;
		}
		void reserve(size_t n){
			if( n  > capacity())
			{
				size_t len = size();
				iterator tmp = new iterator[n+1];
				if(_start){
					for(int i = 0 ; i < len ; i++){
						tmp[i] = (*this)[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = tmp+len;	
				_endOfStorage = tmp + n ;		
			}
		}
		
		void resize(size_t n, const T& value = T()){
			if(n <= size()){
				_finish = _start +n;
				return;
			}
			if(n > capacity())
			{
				reserve(n);
			}
			iterator it = _finish;
			_finish = _start +n;
			while(it !=_finish )
			{
				*it = value;
				it++;
			}
		}

备注:

  1. size() 返回 vector的数据个数, capacity() 返回 vector的数据个数的容量,迭代器相减(本质是指针相减)是迭代器指向位置的距离
  2. reserve()修改内存,本质上是new了一段新空间,将内容拷贝到新空间,再释放旧空间。
  3. 关于const T& value = T()的意思上文有讲,在2.2。

🚀4.获取

cpp 复制代码
//4.获取
		T& operator[](size_t pos)
		{
			return *(_start + x);
		}
		const T& operator[](size_t pos)const
		{
			return *(_start + x);
		}

备注:该函数使vector模板可以像数组一样访问元素,当然也要重载一个const版本。

🚀5.修改

cpp 复制代码
//5.修改
		iterator insert(iterator pos, const T& x);
		iterator erase(Iterator pos);
		void push_back(const T& x);
		void pop_back();
		void swap(vector<T>& v);

⚡️5.1 insert插入

cpp 复制代码
iterator insert(iterator pos, T x)
{
	int len = pos - _start;//记录pos的下标位置
	if (size() == capcitity())//判断扩容
	{
	size_t new_capcitity = capcitity() == 0 ? 4 : capcitity() * 2;
	reserve(new_capcitity);
	}
	iterator end = _finish - 1;//记录最后一个元素
	pos = _start + len;//重置pos,因为扩容后pos可能会失效
	while (end >= pos)//从最后一个数据开始,一个一个往后搬
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = x;
	_finish++;
	return pos; //返回pos位置的指针
};

备注:

  1. 关于重置pos,因为从上文的扩容函数可知,扩容的本质是开辟新空间,所以原来的pos可能不再指向新空间的pos位置了,则导致迭代器失效(迭代器指向错误的位置), 则需要重置。
  2. 同时在使用过insert函数的迭代器也是存在迭代器失效的问题,所以,建议失效后迭代器不要访问。除非赋值更新一下这个失效的迭代器,严格一点的编译器会直接报错。
  3. 为了解决迭代器失效的问题,insert以返回值的形式返回重新生效的迭代器。

例子:

cpp 复制代码
vector<int> test1 = {1,2,3,4};
int cnt = 2;
vector<int>::iterator pos = test1.begin()+1;
//错误写法,pos会失效
while (cnt--)
{
	test1.insert(pos, 0);
}
//实在要用的正确写法
while (cnt--)
{
	pos= test1.insert(pos, 0);
}

⚡️5.2 erase删除

cpp 复制代码
iterator erase(iterator pos)
{
	if (_start)
	{
		iterator head = pos;
		while (head < _finish)
		{
			*(head) = *(head + 1);
			head++;
		}
		_finish--;
	}
	return pos;
};

备注:传入erase的迭代器也不推荐再使用,不同的平台的情况可能不同,可能会出现迭代器失效的问题。

⚡️5.2 push_back尾插

cpp 复制代码
void push_back(const T& x)
{
	insert(_finish, x);
}

备注:复用insert。

⚡️5.3 pop_back尾删

cpp 复制代码
void pop_back()
{
	erase(_finish - 1);
}

备注:复用erase。

本文就到这里,感谢你看到这里!

我知道一些人看文章喜欢静静看,不评论,但是他会点赞,这样的人,帅气低调有内涵,美丽大方很优雅,明人不说暗话,要你手上的一个点赞!

相关推荐
小码农<^_^>8 分钟前
c++继承(下)
开发语言·c++
非著名架构师12 分钟前
js混淆的方式方法
开发语言·javascript·ecmascript
Themberfue13 分钟前
基础算法之双指针--Java实现(下)--LeetCode题解:有效三角形的个数-查找总价格为目标值的两个商品-三数之和-四数之和
java·开发语言·学习·算法·leetcode·双指针
深山夕照深秋雨mo21 分钟前
在Java中操作Redis
java·开发语言·redis
盒马盒马23 分钟前
Redis:cpp.redis++通用接口
数据库·c++·redis
barbyQAQ39 分钟前
Qt源码阅读——事件循环
开发语言·数据库·qt
记得开心一点嘛40 分钟前
在Java项目中如何使用Scala实现尾递归优化来解决爆栈问题
开发语言·后端·scala
敏编程1 小时前
网页前端开发之Javascript入门篇(5/9):函数
开发语言·javascript
柏箱1 小时前
PHP基本语法总结
开发语言·前端·html·php
学无止境\n1 小时前
[C语言]指针和数组
c语言·数据结构·算法