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

一、经典的vector类问题

C++ STL中的vector就类似于C语言当中的数组,但是vector又拥有很多数组没有的接口,使用起来更加方便。
相比于STL中的string,vector可以定义不同的数据类型。

1.1 前期准备

迭代器的本质可以暂时看作是指针,模拟实现vector,需要定义三个指针:指向起始位置_start,指向最后一个有效元素的下一个位置_finish,指向容器最后一个位置_end_of_storage
在这里插入图片描述

namespace zl {  //自定义命名空间
	template<class T>     //定义模板类型
	class vector{
	public:
		typedef T* iterator;   //迭代器  等价于  T 类型指针
	private:
		iterator _start;   //空间起始位置
		iterator _finish;  //最后一个有效元素的下一个位置
		iterator _end_of_storage;  //最后一个容量空间位置
	};
}

二、vector的默认成员函数

2.1 构造函数

2.1.1 无参构造

构造一个空vector,size和capacity为0,将_start,_finish,_end_of_storage都置为空指针即可

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

2.1.2 构造具有n个对象值为val的容器(数据类型为模板类型T)

vector(size_t n, const T& val = T())
{
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
	
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

vector(int n, const T& val = T())
{
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
	
	reserve(n);
	for (int i = 0; i < n; ++i)
	{
		push_back(val);
	}
}

2.1.3 拷贝构造

传统写法
(1)新开辟一块和v同样容量的空间,更新_startt,_finish,_end_of_storage
(2)将v中的数据拷贝到新开辟的空间中

vector(const vector<T>& v)
{
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
	
	//reserve(v.capacity());
	_start = new T[v.capacity()];
	//memcpy(_start, v._start, sizeof(T) * v.size());
	for (size_t i = 0; i < v.size(); ++i)
	{
		_start[i] = v._start[i];
	}

	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

现代写法
使用范围for进行遍历,变量e是v中元素的拷贝,如果v中元素是需要深拷贝的自定义类型,会调用拷贝构造函数构造e,从而使e和v中元素所指向的空间不一样(auto& e:v也可以,因为push_back在实现的时候还会调用深拷贝类型的赋值运算符重载)

vector(const vector<T>& v)
{
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
	reserve(v.capacity());
	for (auto e : v)
	{
		push_back(e);
	}
}

新写法

vector(const vector<T>& v)
{
	vector<T> tmp(v.begin(), v.end());  //定义临时对象--调用迭代器构造方法
	swap(tmp);   //进行资源交换
}

2.2 swap(operator=需要用)

调用全局的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);
}

2.3 复制重载operator=

传统写法
(1)释放原空间,新开一块容量和v一样大的空间,更新_start,_finish,_end_of_storage
(2)将v中的数据拷贝到新空间中

vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		delete[]_start; // 释放原空间
		_start = new T[v.capacity()]; // 开辟新空间
		for (size_t i = 0; i < v.size(); i++) // 拷贝数据
		{
			_start[i] = v[i];
		}
		_finish = _start + v.size(); // 更新_finish
		_endofstorage = _start + v.capacity(); // 更新_capacity
	}
	return *this;
}

现代写法
(1)调用拷贝构造函数生成tmp对象
(2)分别交换tmp和this的_start,_finish,_end_of_storage

vector<T>& operator=(const vector<T>& v)
{
	if (this != &v) // 防止自己给自己赋值
	{
		vector<T> tmp(v); // 拷贝构造tmp对象
		swap(tmp); // 交换_start,_finish, _endofstorage
	}
	return *this;
}
vector<T>& operator=(vector<T> v) // 拷贝构造v对象
{
	swap(v); // 交换_start,_finish, _endofstorage
	return *this;
}

2.4 析构函数

将空间释放掉,_start,_finish,_end_of_storage置为空指针

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

三、vector的三种遍历方式

3.1 size和capacity

size返回容器中有效数据的个数,用_finish-_start即可

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

capacity返回容器的容量,用_end_of_storage-_start即可

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

3.2 operator[]遍历

返回底层数组对应位置的引用即可,需要用const和非const两个版本

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

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

3.3 迭代器iterator遍历和范围for

迭代器也需要写const和非const两个版本,且一个容器只要支持迭代器,就可以用范围for操作,因为范围for本质也是迭代器

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

四、vector相关的增容和删除

4.1 reserve

(1)当n大于对象当前的capacity时,将capacity扩大到n或大于n
(2)当n小于对象当前的capacity时,什么也不做
增容前就需要算出size的大小
因为增容涉及新和旧空间的问题,比如_finish=tmp+size(),而size的求法是用_finish-_start,但_start已指向新空间,可_finish还是指向旧空间,两块不连续的空间相减就是随机值了,故要在增容之前就把大小算出来,不然增容之后的大小就是随机值了。
reserve需要更新_finish
因为_finish是指针,如果一个对象它上来就reserve保存一定容量,然后直接扩容,但是插入数据肯定涉及*_finish操作,若不更新_finish,_finish初始化为nullptr就非法访问内存了,故reserve中也要更改_finish。
不能使用memcpy拷贝数据
因为增容中使用了memcpy,memcpy导致了问题的出现,因为memcpy是按字节拷贝的,它本质上造成了浅拷贝问题,memcpy使新旧空间都指向了一份数据,旧空间释放后,它指向的对应数据也被释放,而新空间指针还是指向这份旧空间,这就造成了非法访问,所以新空间与旧空间不应该指向同一份数据,应该不用memcopy,写成深拷贝(将旧空间的每个值赋值给新空间对应的每个值就好了,就很实现深拷贝了,指向不同的数据)
在这里插入图片描述

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t sz = size();
		T* tmp = new T[n];
		if (_start)
		{
			//memcpy(tmp, _start, sizeof(T) * size());
			for (size_t i = 0; i < sz; ++i)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}

		_start = tmp;
		_finish = tmp + sz;
		_end_of_storage = tmp + n;
	}
}

4.2 resize

(1)当n<size时,直接将_finish=_start+n(将有效数据长度缩小)即可
(2)当size<n<=capacity时,将有效数据的长度增加到n,增加出来的有效数据内容是val
(3)当n>capacity时,先调用上面的reserve函数进行增容,再将有效数据的长度增加到n,增加出来的有效数据内容是val

void resize(size_t n,T val=T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;

			}
			else
			{
				if (n > capacity())
					reserve(n);

				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}

			}
		}

4.3 push_back

尾插数据,首先要检查是否已满,已满则进行增容,增容后尾插即可

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

4.4 pop_back

尾插时,首先要判断容器是否为空,若为空,则断言报错,不为空,_finish–即可

void pop_back()
{
	assert(!empty());

	--_finish;
}

4.5 insert

(1)容量不够,先增容,增容前先记录pos-_start的值,否则增容之后,pos还指向原来已经被释放的空间
(2)将pos位置往后的数据往后挪一位,在pos位置插入值val
为什么需要提前计算出len
有迭代器失效的问题,因为pos是迭代器类型,扩容前指向旧空间的某一位置,而reserve调用后会扩容,而我们是扩容完才插入数据的,此时pos无效,因为旧空间已经释放了,它这个迭代器还指向那里就失效了,故我们要更新pos位置,使它指向新空间,所以要先算len,即原pos的位置

//迭代器失效:扩容引起,野指针问题
iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start);
	assert(pos <= _finish);

	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);

		//扩容后更新pos,解决pos失效的问题
		pos = _start + len;
	}

	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;

	return pos;
}

4.6 erase

erase是返回被删除数据的下一个位置,当要被删除的数据被删除了,erase原地不动的话,就已自动指向了下一位置,因为那个数据被删除了

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

	iterator start = pos + 1;
	while (start != _finish)
	{
		*(start - 1) = *start;
		++start;
	}
	--_finish;

	return pos;
}

4.7 empty

判断是否为空

bool empty()
{
	return _start == _finish;
}

五、完整代码

5.1 vector.h

#pragma once
#include <assert.h>


namespace zl
{
	template<class T>
	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())
		{
			/*:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)*/
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		vector(int n, const T& val = T())
		{
			/*:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)*/
			reserve(n);
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		/*vector(const vector<T>& v)
		{
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}
		}*/


		//vector(const vector<T>& v)
		//{
		//	//reserve(v.capacity());
		//	_start = new T[v.capacity()];
		//	//memcpy(_start, v._start, sizeof(T) * v.size());
		//	for (size_t i = 0; i < v.size(); ++i)
		//	{
		//		_start[i] = v._start[i];
		//	}

		//	_finish = _start + v.size();
		//	_end_of_storage = _start + v.capacity();
		//}

		vector(const vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);
		}

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

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


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


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

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		void resize(size_t n,T val=T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;

			}
			else
			{
				if (n > capacity())
					reserve(n);

				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}

			}
		}



		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}

				_start = tmp;
				_finish = tmp + sz;
				_end_of_storage = tmp + n;
			}
		}

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

		void pop_back()
		{
			assert(!empty());

			--_finish;
		}

		//迭代器失效:扩容引起,野指针问题
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);

			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);

				//扩容后更新pos,解决pos失效的问题
				pos = _start + len;
			}

			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = val;
			++_finish;

			return pos;
		}

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

			iterator start = pos + 1;
			while (start != _finish)
			{
				*(start - 1) = *start;
				++start;
			}
			--_finish;

			return pos;

		}



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

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

		bool empty()
		{
			return _start == _finish;
		}

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

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

	private:
		iterator _start= nullptr;
		iterator _finish= nullptr;
		iterator _end_of_storage= nullptr;
	};

	void func(const vector<int>& v)
	{
		for (size_t i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;

		vector<int>::const_iterator it = v.begin();
		while (it != v.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_vector1()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		//v1.push_back(5);

		func(v1);

		for (size_t i = 0; i < v1.size(); ++i)
		{
			cout << v1[i] << " ";
		}
		cout << endl;

		v1.pop_back();
		v1.pop_back();
		v1.pop_back();
		//v1.pop_back();
		//v1.pop_back();
		//v1.pop_back();

		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

	//template<class T>
	//void f()
	//{
	//	T x = T();
	//	cout << x << endl;
	//}

	//void test_vector2()
	//{
	//	//内置类型有没有构造函数
	//	//int i = int();
	//	//int j = int(1);
	//	int* pi = int* ();

	//	f<int>();
	//	f<int*>();
	//	f<double>();
	//}


	void test_vector2()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);

		cout << v1.size() << endl;
		cout << v1.capacity() << endl;

		v1.resize(10);

		cout << v1.size() << endl;
		cout << v1.capacity() << endl;

		func(v1);

		v1.resize(3);

		func(v1);
	}

	void test_vector3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		//v1.push_back(5);
		func(v1);

		v1.insert(v1.begin(), 0);
		func(v1);

		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			//v1.insert(pos, 30);
			pos=v1.insert(pos, 30);
		}
		func(v1);

		//insert以后我们认为pos失效了,不能再使用
		//(*pos)++;
		func(v1);
	}


	void test_vector4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		//v1.push_back(5);
		func(v1);

		auto pos = find(v1.begin(), v1.end(), 3);
		if (pos != v1.end())
		{
			v1.erase(pos);
		}

		//erase以后,pos是否失效
		//pos失效了,不能访问并且进行强制检查
		//失效,不要访问,行为结果未定义   根据具体编译器实现有关,在g++中没报错 但是在vs下进行报错
		//(*pos)++;

		func(v1);
	}


	void test_vector5()
	{
		vector<int> v1;
		v1.push_back(10);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		func(v1);

		//要求删除所有的偶数
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			if (*it % 2 == 0)
			{
				it=v1.erase(it);
			}
			else
			{
				++it;
			}
		}
		func(v1);
	}


	void test_vector6()
	{
		//vector<int> v1(10u,5);    //u表示这个字面量为无符号
		vector<int> v1(10,5);   

		func(v1);

		vector<int> v2(v1.begin() + 1, v1.end() - 1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;

		std::string s1("hello");
		vector<int> v3(s1.begin(), s1.end());
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;


		int a[] = {30,20,10};
		vector<int> v4(a,a+3);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;


		v1.insert(v1.begin(), 10);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		sort(v1.begin(), v1.end());
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;


		for (auto e : a)
		{
			cout << e << " ";
		}
		cout << endl;

		//sort(a, a + sizeof(a) / sizeof(int));   //默认是升序<     降序>

		//greater<int> g;
		//sort(a, a + sizeof(a) / sizeof(int), g);

		sort(a, a + sizeof(a) / sizeof(int), greater<int> ());

		sort(a, a + sizeof(a) / sizeof(int));   
		for (auto e : a)
		{
			cout << e << " ";
		}
		cout << endl;
	}


	void test_vector7()
	{
		vector<int> v1(10, 5);
		for (auto e : v1)
		{
			cout << e << " ";
		}
		cout << endl;

		vector<int> v2(v1);
		for (auto e : v2)
		{
			cout << e << " ";
		}
		cout << endl;


		vector<std::string> v3(3, "1111111111111111");
		for (auto e : v3)
		{
			cout << e << " ";
		}
		cout << endl;


		vector<std::string> v4(v3);
		for (auto e : v4)
		{
			cout << e << " ";
		}
		cout << endl;

	}

}


5.2 test.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
using namespace std;
#include "vector.h"

class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}

	~A()
	{
		cout << "A()" << endl;
	}

};


int main()
{
	/*vector<int>::iterator it;
	cout << typeid(it).name() << endl;*/

	//zl::test_vector1();
	//zl::test_vector2();
	//zl::test_vector3();
	//zl::test_vector4();
	//zl::test_vector5();
	//zl::test_vector6();
	zl::test_vector7();


	//A a1;
	A();     //匿名对象生命周期只在当前一行,因为这行之后没人会用它了
	//const A& xx = A();    //const引用会延长匿名对象的生命周期到引用对象域结束,因为以后用xx就是用匿名对象
	//A a2;

	return 0;
}

  • 20
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值