[STL]vector模拟实现


一、vector源码学习

在访问stl3.0版本的时候,具体访问里面一个文件:stl_vector.h浏览
在浏览原码的时候,正确做法是先去了解框架,而非去一句一句的去阅读代码

节选自stl_vector.h

template <class T, class Alloc = alloc>
class vector {
public:
  typedef T value_type;
  typedef value_type* pointer;
  typedef const value_type* const_pointer;
  typedef value_type* iterator;
  typedef const value_type* const_iterator;
  typedef value_type& reference;
  typedef const value_type& const_reference;
  typedef size_t size_type;
  typedef ptrdiff_t difference_type;

#ifdef __STL_CLASS_PARTIAL_SPECIALIZATION
  typedef reverse_iterator<const_iterator> const_reverse_iterator;
  typedef reverse_iterator<iterator> reverse_iterator;
#else /* __STL_CLASS_PARTIAL_SPECIALIZATION */
  typedef reverse_iterator<const_iterator, value_type, const_reference, 
                           difference_type>  const_reverse_iterator;
  typedef reverse_iterator<iterator, value_type, reference, difference_type>
          reverse_iterator;
#endif /* __STL_CLASS_PARTIAL_SPECIALIZATION */
protected:
  typedef simple_alloc<value_type, Alloc> data_allocator;
  iterator start;
  iterator finish;
  iterator end_of_storage;
  void insert_aux(iterator position, const T& x);
  void deallocate() {
    if (start) data_allocator::deallocate(start, end_of_storage - start);
  }

在以往,我们实现顺序表的时候,使用了一个数组、size、capacity,但是这里是使用了三个指针 iterator start; iterator finish; iterator end_of_storage;
将上述代码进行简略的浓缩,便有了:

template <class T, class Alloc = alloc>
class vector {
public:
  typedef T value_type;
  typedef T* iterator;
	
  // 使用了三个指针去做	
  iterator start;
  iterator finish;
  iterator end_of_storage;
};

在这里插入图片描述
如上图,此时start就是_a,finish就是_a + size,_a + capacity = end_of_storage


二、vector模拟实现

2.1 实现简易的vector

初始版本:

namespace zmm
{
	template <class T>
	class vector
	{
		typedef T* iterator;
	public:
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

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

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				// 扩容
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				T* tmp = new T[newCapacity];
				if (_start) // 一开始为空,就不需要拷贝数据进这个新的空间
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start; // 释放掉旧空间
				}
				_start = tmp;
				_finish = _start + size();
				_end_of_storage = _start + newCapacity;
			}
			*_finish = x;
			++_finish;
		}
		T& operator[](size_t i)
		{
			assert(i < size());
			return _start[i];
		}

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

	void test_vector1()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		for (int i = 0; i < v.size(); ++i)
		{
			cout << v[i] << " ";
		}
		cout << endl;
	}
}

上述代码是实现的最初版本,但是这样子还是有很大的问题的:
问题:出现在push_back内部,此时我们用这个push_back代码会发生问题

namespace zmm
{
	template <class T>
	class vector
	{
		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				// 扩容
				size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
				T* tmp = new T[newCapacity];
				if (_start) // 一开始为空,就不需要拷贝数据进这个新的空间
				{
					memcpy(tmp, _start, sizeof(T) * size());
					delete[] _start; // 释放掉旧空间
				}
				_start = tmp;
				_finish = _start + size();
				_end_of_storage = _start + newCapacity;
			}
			*_finish = x;
			++_finish;
		}

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

分析问题:
在这里插入图片描述
在这里插入图片描述
此时finish - start就可能是一个非常大的数

_start = tmp;
_finish = _start + size();
_end_of_storage = _start + newCapacity;

在调试过程中,我们会发现_finish还是空,这是因为这几行代码我们在实现的时候,当_start = tmp的时候,_start就进行了改变,因此在计算size()的时候,用_finish - _start就有误了,正确方法应该是旧的start去进行运算

那如果我们将代码两行互换一下呢?

_finish = _start + size();
_start = tmp;
_end_of_storage = _start + newCapacity;

依然是错误的,因为这时候_start是不对的,应该改为:

_finish = tmp + size();
_start = tmp;
_end_of_storage = _start + newCapacity;

但是最好还是初始时刻去记录这个值,因此最终改为下面的形式

void push_back(const T& x)
{
	if (_finish == _end_of_storage)
	{
		// 扩容
		size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
		size_t sz = size();
		T* tmp = new T[newCapacity];
		if (_start) // 一开始为空,就不需要拷贝数据进这个新的空间
		{
			memcpy(tmp, _start, sizeof(T) * size());
			delete[] _start; // 释放掉旧空间
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + newCapacity;
	}
	*_finish = x;
	++_finish;
}

2.2 vector基础结构

template <class T>
class vector
{
public:
	typedef T* iterator;
	vector()
		:_start(nullptr)
		, _finish(nullptr)
		, _end_of_storage(nullptr)
	{}

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

	void push_back(const T& x)
	{
		if (_finish == _end_of_storage)
		{
			// 扩容
			size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
			size_t sz = size();
			T* tmp = new T[newCapacity];
			if (_start) // 一开始为空,就不需要拷贝数据进这个新的空间
			{
				memcpy(tmp, _start, sizeof(T) * size());
				delete[] _start; // 释放掉旧空间
			}
			_start = tmp;
			_finish = _start + sz;
			_end_of_storage = _start + newCapacity;
		}
		*_finish = x;
		++_finish;
	}
	T& operator[](size_t i)
	{
		assert(i < size());
		return _start[i];
	}
	const T& operator[](size_t i) const
	{
		assert(i < size());
		return _start[i];
	}

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


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

2.3 vector完善

2.3.1 拓展:匿名对象的生命周期

匿名对象只在当前行有生命周期,当前行结束后,便自行销毁,如果前有const,则会延长它的生命周期
在这里插入图片描述
第一个匿名对象当前行结束后就立马销毁了,第二个const的匿名对象,当前行结束后没有立马销毁,所以const匿名对象会延迟它的生命周期

2.3.2 resize()

// T()是常对象,所以必须要加const,否则会编译不通过
void resize(size_t n, const T& val = T()) // 给一个T类型的缺省值
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		if (n > capacity())
		{
			reserve(n);
		}
		while (_finish != _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
}

2.3.3 迭代器区间初始化

使用一个迭代器区间去构造的vector
一个类模板的成员函数,又可以是一个函数模板InputIterator
就相当于是用了T,InputIterator函数模板的优势也可以去传其他其他类型迭代器

template <class InputIterator>
vector(InputIterator first, InputIterator last) // 必须要传迭代器
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

2.3.4 深拷贝

传统写法

// 自己开空间,自己换数据
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));
}

现代写法

vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	vector<T> tmp(v.begin(), v.end()); // 用一段迭代器区间去构造了tmp
	swap(_start, tmp._start);
	swap(_finish, tmp._finish);
	swap(_end_of_storage, tmp._end_of_storage);
	// tmp出了作用域是随机值,会调用析构函数就会崩,所以要把他初始化为空
}

2.3.5 operator=()

// 现代 v1 = v2
vector<T>& operator=(vector<T> v) // 这里用传值,v就是v2拷贝构造出来的
{
	swap(_start, v._start);
	swap(_finish, v._finish);
	swap(_end_of_storage, v._end_of_storage);
	return *this;
}

出了作用域v要销毁,然后此时空间交换过,v1就是v,v也就一次销毁掉了

swap(v1,v3)是3个深拷贝
v1.swap(v3)仅仅交换成员变量指针

2.3.6 修改完善深拷贝

	template <class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _end_of_storage = nullptr;
			}
		}

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

		template <class InputIterator>
		vector(InputIterator first, InputIterator last) // 必须要传迭代器
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

		// 现代写法
		// v2(v1)
		vector(const vector<T>& v) //也可以不加模板参数 vector(const vector& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			vector<T> tmp(v.begin(), v.end()); // 用一段迭代器区间去构造了tmp
			// this->swap(tmp);
			swap(tmp); // 可以省略this
			// tmp出了作用域是随机值,会调用析构函数就会崩,所以要把他初始化为空
		}

		// 现代 v1 = v2
		// 这里也是一样,也可以不去写类模板参数vector& operator=(vector v)
		vector<T>& operator=(vector<T> v) // 这里用传值,v就是v2拷贝构造出来的
		{
			/*swap(_start, v._start);
			swap(_finish, v._finish);
			swap(_end_of_storage, v._end_of_storage);*/
			swap(v);
			return *this;
		}
}

2.3.7 迭代器分类

函数模板的模板参数要传送迭代器区间时,是有命名规范的

迭代器分类:
Input_Iterator  只写迭代器——没有实际对应的类型(任意迭代器都满足)
Output_Iterator  只读迭代器——没有实际对应的类型
forward_Iterator  单向迭代器——只能++ ,forward_list、unordered_map、unordered_set
Bidirectional_Iterator  双向迭代器——可以++,--,list、map、set
Randomaccess_Iterator  随机迭代器——可以++,--,+,-,deque、vector可以访问任意位置

为什么会有这么多的迭代器?
因为容器有不同的结构
如reverse是要传双向迭代器,但是可以传随机迭代器吗?——可以,因为随机也是一个特殊的双向迭代器(满足++和–)
sort是要传随机迭代器,但是如果传双向就会报错
在这里插入图片描述

void test_vector5()
{
	vector<int> v; // 随机迭代器——可以任意访问
	v.push_back(1);
	v.push_back(4);
	v.push_back(3);
	v.push_back(2);
	sort(v.begin(), v.end());


	// 下面这种是不行的
	list<int> lt;
	lt.push_back(40);
	lt.push_back(30);
	lt.push_back(50);
	sort(lt.begin(), lt.end());
}

在这里插入图片描述

void test_vector5()
{
	vector<int> v; // 随机迭代器——可以任意访问
	v.push_back(1);
	v.push_back(4);
	v.push_back(3);
	v.push_back(2);
	sort(v.begin(), v.end());


	// 下面这种是不行的
	list<int> lt;
	lt.push_back(40);
	lt.push_back(30);
	lt.push_back(50);
	// sort(lt.begin(), lt.end()); // 要传随机迭代器,因为里面包含+,-,三数取中操作

	reverse(lt.begin(), lt.end()); // reverse要传双向迭代器
	reverse(v.begin(), v.end()); // 这里传随机迭代器也可以,因为随机迭代器满足双向迭代器的所有东西
	
	// forward_list底层就是一个单链表
	forward_list<int> flt;
	flt.push_front(1); // 支持头插
	flt.push_front(2); 
	flt.push_front(4); 
	reverse(flt.begin(), flt.end()); // 不能传单向
}

补充:static修饰的变量就不存在栈里面了,放到了静态区里面,这样子作用域还是局部的,但是生命周期变为全局的了

2.3.8 pop_back

void pop_back()
{
	assert(_finish > _start); // 必须要有数据
	--finish;
}

2.3.9 迭代器失效

在pos之前插入一个数据

void insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	// 满了就扩容
	if (_finish == _end_of_storage)
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	// 从end开始依次往后挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}
void test_vector6()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	// v1.push_back(4);——加上这句就会崩溃
	// 算法库里面的find要传迭代器区间(input迭代器)
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 2);
	if (pos != v1.end())
	{
		v1.insert(pos, 20);
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
}

在这里插入图片描述

上述代码为什么加上v1.push_back(4);就会崩溃?原本只有1、2、3的时候,不涉及扩容,所以是正常的,但是满了以后就会扩容,扩容会扩在新空间,此时旧空间就会被释放(使用权还给系统了,此时pos还指向一块被释放的空间,pos就是一个野指针,所以就错误了)

如何解决迭代器失效问题?
计算原本pos和start的相对距离,之后在新开的空间在新的start的基础上加上相对距离即可

因此我们就可以更改insert函数

void insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	// 满了就扩容
	if (_finish == _end_of_storage)
	{
		// 扩容会导致pos失效,扩容需要更新pos
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}
	// 从end开始依次往后挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

但是此时还是有问题
因为我们使用insert函数传参,我们是将it传过去,传值调用,而insert里面的pos只是它的一份拷贝,所以it还是会失效

void test_vector6()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	// v1.push_back(4);——加上这句就会崩溃
	// 算法库里面的find要传迭代器区间(input迭代器)
	vector<int>::iterator it = find(v1.begin(), v1.end(), 2);
	if (it != v1.end())
	{
	 	// 如果insert中发生了扩容,会导致it指向的空间被释放,it本质就是一个野指针了,这种问题就叫做迭代器失效——这里不能传引用,使用不当的话在某些场景还是会出错——因此我们使用返回值解决
		v1.insert(it, 20);
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
}
iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	// 满了就扩容
	if (_finish == _end_of_storage)
	{
		// 扩容会导致pos失效,扩容需要更新pos
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}
	// 从end开始依次往后挪动数据
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
	return pos;
}

继续实现erase函数

void erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	iterator begin = pos + 1;
	// == finish就结束了
	while (begin < _finish)
	{
		*(begin - 1) = *begin;
		++begin;
	}
	--_finish;
}

erase迭代器会失效吗?——会
比如要删除这个vector里面所有的偶数
要去使用三种场景来测试该函数
1 2 3 4 5 -> 正常
1 2 3 4 -> 崩溃
1 2 4 5 -> 没删除完——erase以后,it指向位置的意义已经变了,直接++it可能会导致一些意料之外的结果,连续的偶数,可能会到导致后一个偶数没有被删除掉
在这里插入图片描述
而且erase删除,有些版本vector的实现,可能会缩容(因为删除的数据太多,浪费空间太多,就会缩容),这样子erase以后,it也可能是野指针,与insert类似

在这里插入图片描述
这种情况后,it就一直不等于end,就全是越界访问了

因此,我们也采用返回值的形式修改erase,控制了以后删除了元素以后也不会跳到下一个位置了

iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	iterator begin = pos + 1;
	// == finish就结束了
	while (begin < _finish)
	{
		*(begin - 1) = *begin;
		++begin;
	}
	--_finish;
	return pos;
}
void test_vector8()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	// 算法库里面的find要传迭代器区间(input迭代器)
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		if (*it % 2 == 0)
		{
			it = v1.erase(it);
		}
		else
		{
			++it;
		}
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
}

erase以后it失效了,会返回下一个数据位置的迭代器

vector的迭代器失效主要是在insert和erase
string的insert和erase迭代器是否会失效?——会
结论:只要使用迭代器访问的容器,都可能涉及迭代器失效(注意这里前提是迭代器)
string迭代器什么时候会失效?——参考vector,与vector一样

2.3.10 拷贝的又一问题

当我们在使用如下程序的时候,就会发生错误

void test_vector9()
{
	vector<string> v;
	v.push_back("111111111111111111"); 
	v.push_back("11111111");
	v.push_back("11111111");
	v.push_back("11111111");
	v.push_back("11111111");
	for (auto& e : v) // 这里最好用引用,否则就是一个深拷贝
	{
		cout << e << " ";
	}
	cout << endl;
}

此时程序在析构的时候就会发生问题
在这里插入图片描述
此程序在memcpy拷贝的时候,是发生的浅拷贝,所以析构的时候就出现了问题

所以我们要用赋值来完成深拷贝,一个一个值去拷贝过去

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 = _start + sz;
		_end_of_storage = _start + n;
	}
}

在上述push_back的字符串的时候,用原先的memcpy完成的浅拷贝,如果字符串长一点会发生乱码,字符串短一点则不会乱码——Linux上,短的长的都会乱码,但是vs上只有字符串长的才会乱码,原因是什么?
在这里插入图片描述
可以发现s1的buf中是一个char _buf[16]:<16的时候,存放在buf中,也就是栈中
s2中明显是超过了16,buf就会浪费掉,就存放在ptr中,是在堆上的,也就是一种空间换时间
在这里插入图片描述
如果数据大我们就放在堆上了
在这里插入图片描述
但是他们sizeof还是一样的大小
原因是string就相当于是:

class string
{
private:
	char _Buf[16]; // 字符长度小于16,存放在这个数组中
	char* _Ptr; // 大于等于16就会去堆上申请空间
	size_t _mySize;
	size_t _myRes;
};

这样子就能解释刚刚的问题了,当字符串长的时候,在堆上
短的话在对象本身里面,memcpy使用了以后就拷贝下来了,也不会造成问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值