以前在初学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可以随机存储元素,但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁。