vector的使用和模拟实现

有了前面的经验,这里对于vector的使用我们就简单看一眼:

cpp 复制代码
void test_vector()
{
	vector<int>v1;
	vector<int>v2(10, 1);//初始化 v2 里面有10个1

	vector<int>v3(v2.begin(), v2.end());//用一段初始化区间来初始化


	for (size_t i = 0; i < v3.size(); i++)
		cout << v3[i] << " ";


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

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

vector中我们同样可以使用reserve来为数组预留空间。但vectorstring多出了一个resize的操作。

cpp 复制代码
void test_vector()
{
	vector<int>v;
	v.reserve(100);//预留100个空间,至少开足够v的内容,所以可能多一些,具体实现看编译器,但不会对内容产生影响。VS下不会缩容

	v.resize(200, 2);//将v的size改为200,如果原来的size大于,则删除后面的数据,
	                 //如果小于,先看是否有给出数据,有的话按给出的数据(2)补齐,如果没有,则按默认构造补齐数据
	                 //当容量不够的时候可能会扩容但不会缩容
}

vector的插入元素主要依赖push_backinsert两种操作,但值得注意的是,insert操作只支持迭代器操作,同理,erase操作同样也只支持迭代器操作。

cpp 复制代码
void test_vector()
{
	vector<int> v(10, 1);
	v.push_back(2);
	v.insert(v.begin(), 0);//intsert只支持迭代器操作:在开头位置插入一个0

	v.erase(v.begin());//删除操作,也只支持迭代器操纵
}

我们可以使用vector模拟实现出一个string,但两者存在一个重要的区别:string中存在\0,但vector中不存在。

vector的模拟实现:

下面我们来到今天的重头戏,vector的模拟实现:

这里我们用模板类型的指针来代替迭代器,在写一个类模板之前,首先我们需要实现函数的默认构造和析构函数,防止在具体使用的时候出错导致程序崩溃。

cpp 复制代码
namespace vector
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//C++11 强制生成默认构造
		vector() = default;

		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		vector(size_t n, const T& val = T())
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}
		
		// 类模板的成员函数,还可以继续是函数模板
		// 写成模板的价值:任意容器迭代器初始化,但类型必须是匹配的
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
}

接着来实现容器的核心功能:迭代器。这是后续其他函数书写和使用的基础。从上文中typedef中可以看出,迭代器同样分为正常迭代器和const迭代器,以便能够实现不同的任务需要。

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

在完成初始化以及迭代器的实现之后,我们可以利用他们去实现一些vector中的基础函数:

cpp 复制代码
size_t size() const
{
	return _finish - _start;
}

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

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

void pop_back()
{
	assert(!empty());
	--finish;
}

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

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

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

	++_finish;
}

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();
		T* tmp = new T[n];
		//memcpy(tmp, _start, size() * sizeof(T));//memcpy浅拷贝,这里拷贝的是指针指向的空间中的东西,按字节拷贝,
		//  里面的东西依然是指向被拷贝对象的指针,后面delete之后会出错
		//  需要我们自己实现一个深拷贝
		for (size_t i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];//调用赋值
		}
		
		delete[] _start;
		_finish = tmp + old_size;
		_start = tmp;
		_end_of_storage = _start + n;
	}
}

void resize(size_t n, T val = T())
{
	if (n < size())
		_finish = _start + n;
	else
	{
		reserve(n);
		while (_finish < _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

void erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);
	iterator begin = pos + 1;
	while (begin < _finish)
	{
		*(begin - 1) = *begin;
		++begin;
	}
	--_finish;
}

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

	*_finish = x;
	++_finish;
}

这里涉及到的内容都比较简单且在之前的文章中基本上都讲解过原理了,这里就不再过多赘述了。

在上面的函数实现的过程中,我们用到了[],但在此之前我们还没有实现对其的重载和实现,因此理论上来讲上面的函数实现是不能用的,那么现在我们再来模拟试下一些对[]的重载实现:

cpp 复制代码
T& operator[](size_t i)
{
	assert(i < size());
	return _start[i];
}

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

这里还有一个比较有意思的函数重载:=的重载,这里容笔者稍微卖弄一下,分为传统写法和现代写法两种写法,正常来讲选择自己最熟悉的就可以了。

传统写法:

cpp 复制代码
void clear()
{
	_finish = _start;
}
vector<T>& operator=(const vector<T>& v)
{
	if (this != &v)
	{
		clear();
		reserve(v.size());

		for (auto& e : v)
		{
			push_back(e);
		}
		
	}
	return *this;
}

现代写法:

cpp 复制代码
void swap()
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

最后,在日常使用中,我们可能会遇到迭代器失效的问题,这个问题分两种情况:

  1. 扩容导致的迭代器失效:迭代器失效的本质是指向的内存空间被释放了,变成了野指针
  2. 非扩容导致的迭代器失效:由于数据挪动,迭代器指向的元素改变了,此时迭代器也失效了,不要访问

好了,本章的内容就到此结束,感谢阅读!

相关推荐
前端不太难2 小时前
RN 构建包体积过大,如何瘦身?
前端·react native
九河云2 小时前
直播电商数字化:用户行为 AI 分析与选品推荐算法平台建设
人工智能·物联网·算法·推荐算法
小光学长2 小时前
基于web的影视网站设计与实现14yj533o(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
CoovallyAIHub2 小时前
深大团队UNeMo框架:让机器人学会“预判”,效率提升40%
深度学习·算法·计算机视觉
何中应2 小时前
【面试题-2】Java集合
java·开发语言·后端·面试题
BullSmall2 小时前
Tomcat SSL 配置及常见问题
java·tomcat·ssl
vocoWone2 小时前
📰 前端资讯 - 2025年12月10日
前端
璞瑜无文2 小时前
Unity 游戏开发之方块随机生成(三)
java·unity·游戏引擎
周杰伦_Jay2 小时前
【JVM深度解析】运行时数据区+类加载+GC+调优实战(附参数示例)
java·jvm·spring boot·分布式·架构·java-ee
李少兄2 小时前
前端开发中的多列布局(Multi-column Layout)
前端·css