浅析c++ STL库——vector容器篇(一)

以前在初学c++的时候简单使用过STL库,不得不说c++的标准模板库是一个功能很强大的模板,它包括着容器,算法,迭代器,仿函数,容器配接器和空间配置器。STL中常用的容器有vector,list,stack,deque,queue,map,multimap,hash_set等。现在,就让我们来聊一聊vector。

vector在底层的数据结构是一个数组,我们定义他的方式通常都是

std::vector<int> vec0;
std::vector<int> vec1(10);
std::vector<int> vec2(10, 0);

首先,我们都学过c++,一定知道一个对象的初始化是要通过构造函数来实现的,并且缺省值是0,vector自然也不例外,那就让我们看一下vector的构造函数过程是怎样构造的。

	void _Alloc_proxy()
		{	// construct proxy
		_Alproxy _Proxy_allocator(_Getal());
		_Myproxy() = _Unfancy(_Proxy_allocator.allocate(1));
		_Alproxy_traits::construct(_Proxy_allocator, _Myproxy(), _Container_proxy());
		_Myproxy()->_Mycont = _STD addressof(_Get_data());
		}

从上面我们可以看出,在初始化的过程中,首先有一个指针指向一片连续的内存空间,然后向这个空间依次放入数据。因此我们可以推断出vector是一个动态的数组(顺序存储结构),并且他是我们由我们手动创建的对象(即建立在内存的堆上)。

vector既然是我们自己手动创建的,那么自然就需要我们做到有始有终,故而要主动通过析构函数来进行对象的释放。

	void _Free_proxy()
		{	// destroy proxy
		_Alproxy _Proxy_allocator(_Getal());
		_Orphan_all();
		_Alproxy_traits::destroy(_Proxy_allocator, _Myproxy());
		_Deallocate_plain(_Proxy_allocator, _Myproxy());
		_Myproxy() = nullptr;
		}

从上面的代码我们可以看出,释放的过程与初始化过程正好相反,首先清空迭代器中的数据,然后把指针置为空。所以我们可以从这推断出vector的释放过程仅仅是释放了数据,并没有释放掉内存空间。这也是vector的一个很重要的特点:内存只增不减!!!因此我们在实际的使用中对于小规模的数据不需要进行特别的操作,交给操作系统即可,但是对于大量数据我们要怎么办呢?在这里使用swap函数就可以做到,至于原因就当做是闲暇之余的思考吧。

既然是数组,那么就会有插入数据,vector的数据插入是通过insert函数及其重载来完成的,他有三种插入方式:在where位置插入一个val元素,

	iterator insert(const_iterator _Where, const _Ty& _Val)
		{	// insert _Val at _Where
		return (emplace(_Where, _Val));
		}

在where位置插入count个val数据,

	iterator insert(const_iterator _Where, _CRT_GUARDOVERFLOW const size_type _Count, const _Ty& _Val)
		{	// insert _Count * _Val at _Where
 #if _ITERATOR_DEBUG_LEVEL == 2
		_STL_VERIFY(_Where._Getcont() == _STD addressof(this->_Get_data())
			&& _Where._Ptr >= this->_Myfirst()
			&& this->_Mylast() >= _Where._Ptr, "vector insert iterator outside range");
 #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

		const size_type _Whereoff = static_cast<size_type>(_Where._Ptr - this->_Myfirst());
		const bool _One_at_back = _Count == 1 && _Where._Ptr == this->_Mylast();

		if (_Count == 0)
			{	// nothing to do, avoid invalidating iterators
			}
		else if (_Count > _Unused_capacity())
			{	// reallocate
			const size_type _Oldsize = size();

			if (_Count > max_size() - _Oldsize)
				{
				_Xlength();
				}

			const size_type _Newsize = _Oldsize + _Count;
			const size_type _Newcapacity = _Calculate_growth(_Newsize);

			const pointer _Newvec = this->_Getal().allocate(_Newcapacity);
			const pointer _Constructed_last = _Newvec + _Whereoff + _Count;
			pointer _Constructed_first = _Constructed_last;

			_TRY_BEGIN
			_Ufill(_Newvec + _Whereoff, _Count, _Val);
			_Constructed_first = _Newvec + _Whereoff;

			if (_One_at_back)
				{	// provide strong guarantee
				_Umove_if_noexcept(this->_Myfirst(), this->_Mylast(), _Newvec);
				}
			else
				{	// provide basic guarantee
				_Umove(this->_Myfirst(), _Where._Ptr, _Newvec);
				_Constructed_first = _Newvec;
				_Umove(_Where._Ptr, this->_Mylast(), _Newvec + _Whereoff + _Count);
				}
			_CATCH_ALL
			_Destroy(_Constructed_first, _Constructed_last);
			this->_Getal().deallocate(_Newvec, _Newcapacity);
			_RERAISE;
			_CATCH_END

			_Change_array(_Newvec, _Newsize, _Newcapacity);
			}
		else if (_One_at_back)
			{	// provide strong guarantee
			_Emplace_back_with_unused_capacity(_Val);
			}
		else
			{	// provide basic guarantee
			const _Ty _Tmp = _Val;	// handle aliasing
			const pointer _Oldlast = this->_Mylast();
			const size_type _Affected_elements = static_cast<size_type>(_Oldlast - _Where._Ptr);
			_Orphan_range(_Where._Ptr, _Oldlast);

			if (_Count > _Affected_elements)
				{	// new stuff spills off end
				this->_Mylast() = _Ufill(_Oldlast, _Count - _Affected_elements, _Tmp);
				this->_Mylast() = _Umove(_Where._Ptr, _Oldlast, this->_Mylast());
				_Fill_unchecked(_Where._Ptr, _Oldlast, _Tmp);
				}
			else
				{	// new stuff can all be assigned
				this->_Mylast() = _Umove(_Oldlast - _Count, _Oldlast, _Oldlast);
				_Move_backward_unchecked(_Where._Ptr, _Oldlast - _Count, _Oldlast);
				_Fill_unchecked(_Where._Ptr, _Where._Ptr + _Count, _Tmp);
				}
			}

		return (this->_Make_iterator_offset(_Whereoff));
		}

把val数据移动到where位置上插入。

	iterator insert(const_iterator _Where, _Ty&& _Val)
		{	// insert by moving _Val at _Where
		return (emplace(_Where, _STD move(_Val)));
		}

从上面三个例子可以看出一个共同点,那就是vector的插入操作主要是通过emplace函数来完成的,而emplace是对元素的构造,并且他的一大优点是不会产生临时变量,且可以接受任意的参数。

		iterator emplace(const_iterator _Where, _Valty&&... _Val)
		{	// insert by perfectly forwarding _Val at _Where
		const pointer _Whereptr = _Where._Ptr;
		const pointer _Oldlast = this->_Mylast();
 #if _ITERATOR_DEBUG_LEVEL == 2
		_STL_VERIFY(_Where._Getcont() == _STD addressof(this->_Get_data())
			&& _Whereptr >= this->_Myfirst()
			&& _Oldlast >= _Whereptr, "vector emplace iterator outside range");
 #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

		if (_Has_unused_capacity())
			{
			if (_Whereptr == _Oldlast)
				{	// at back, provide strong guarantee
				_Emplace_back_with_unused_capacity(_STD forward<_Valty>(_Val)...);
				}
			else
				{
				_Ty _Obj(_STD forward<_Valty>(_Val)...);	// handle aliasing
				// after constructing _Obj, provide basic guarantee
				_Orphan_range(_Whereptr, _Oldlast);
				_Alty_traits::construct(this->_Getal(), _Unfancy(_Oldlast), _STD move(_Oldlast[-1]));
				++this->_Mylast();
				_Move_backward_unchecked(_Whereptr, _Oldlast - 1, _Oldlast);
				*_Whereptr = _STD move(_Obj);
				}

			return (this->_Make_iterator(_Whereptr));
			}

		return (this->_Make_iterator(_Emplace_reallocate(_Whereptr, _STD forward<_Valty>(_Val)...)));
		}

因此我们可以得出一个结论:vector的插入需要对现有数据进行复制移动(由于emplace函数),如果vector存储的对象很大或者构造函数很复杂,则开销较大。

vector的插入操作并不是在原空间后增加空间,而是以原大小的两倍另外配置一片新空间,然后将内容拷贝过来,并释放原来的空间。

由以上vector的特点我们可以知道,vector可以随机存储元素,但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值