你知道vector
底层是如何实现的吗?
vector
底层使用动态数组来存储元素对象,同时使用size
和capacity
记录当前元素的数量和当前动态数组的容量。如果持续的push_back(emplace_back)
元素,当size
大于capacity
时,需要开辟一块更大的动态数组,并把旧动态数组上的元素搬移到当前动态数组,然后销毁旧的动态数组。
你知道新开辟的动态数组的容量是就数组的多少倍比较合适?
这个值在不同的编译器上不是固定的。MSVC 是1.5,而GCC是2。
有没有什么好的办法提升vector连续插入效率?
如果知道数据的大概量,我们可以使用reserve
方法直接为vector
扩容这个量级。这样在后续的数据插入时就不会因为频繁的capacity
被用尽而导致的多次的数据搬移,从而提升vector
插入效率。
push_back
和emplace_back
有什么区别?
两者都可以在容器尾部插入元素。在GCC中,如果插入的元素是右值,两者都会move
元素到容器。如果是左值,两者都会copy
元素到容器。唯一不同的一点是,当C++版本高于C++17时,emplace_back
返回当前插入的值的引用,而push_back
返回void
。
erase
和remove
有什么区别?
erase
属于成员函数,erase
删除了元素,remove
属于算法库函数,而remove
只会把元素移动到尾部。
哪些情况下迭代器会失效?
迭代器失效主要有两种情况引起:
1.插入数据。由于插入数据可能导致数据搬移(size > capacity
),所以迭代器失效。
2.删除数据。当使用erase
删除数据时,被删除数据后面的数据依次向前移一位。这会导致被删除数据之后的迭代器失效。
如何快速的清空vector
容器并释放vector
容器所占用的内存?
有两种方法:
- 第一种,使用
clear
方法清空所有元素。然后使用shrink_to_fit
方法把capacity
和size(0)
对齐,达到释放内存的作用; - 第二种,使用
swap
方法;
你知道vector<bool>
是如何实现的吗?
vector<bool>
的实现和其他实现容器的实现不一致。**每个元素被当作一个位而不是一个字节存储。**这导致我们不能直接访问该元素,也无法对每个元素取地址(8
个元素可能在同一个字节中存储)。所以不建议使用vector<bool>
,必要时可以使用std::bitset
替代。
介绍三个和大小、容量与内存相关的函数:
- 一个是_M_shrink_to_fit函数,可以使用这个函数将_M_finish和_M_end_of_storage之间的内存释放掉,正好使分配的内存大小满足vector中元素量。
- 一个是reserve函数,可以使用这个函数控制整个vector容量的作用,避免重新分配内存,例如我们如果知道元素最多有80个,我们就可以使用v.reserve(80)来一次分配80个内存。但是值得注意的是这个函数不能用来缩减容量
- 一个是resize函数,这个函数实际就是改变_M_start和_M_finish之间的相对位置,改变的是元素的个数,如果resize(n)的n小于当前个数就删除元素,如果大于就追加默认函数生成的对象。
vector是怎么变长的
typename vector<_Tp, _Alloc>::iterator
vector<_Tp, _Alloc>::insert(iterator __position, const value_type& __x)
{const size_type __n = __position – begin();//如果还有空位,插入到最后一个位置,相当于push_backif (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage&& __position == end()){_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish, __x);++this->_M_impl._M_finish;}else{_M_insert_aux(__position, __x);}return iterator(this->_M_impl._M_start + __n);
}
当容量不足的时候,就是_M_insert_aux函数发挥的时候,进行容量的扩展,实现如下:
template<typename _Tp, typename _Alloc>
void vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{//内存空间足够if (this->_M_impl._M_finish != this->_M_impl._M_end_of_storage){_Alloc_traits::construct(this->_M_impl, this->_M_impl._M_finish,_GLIBCXX_MOVE(*(this->_M_impl._M_finish- 1)));++this->_M_impl._M_finish;//__position后的元素依次向后移动一个位置_GLIBCXX_MOVE_BACKWARD3(__position.base(),this->_M_impl._M_finish - 2,this->_M_impl._M_finish – 1);//目标地址赋值*__position = _Tp(std::forward<_Args>(__args)...);}else{//内存加倍const size_type __len =_M_check_len(size_type(1), "vector::_M_insert_aux");const size_type __elems_before = __position - begin();pointer __new_start(this->_M_allocate(__len));pointer __new_finish(__new_start);__try{//给position位置赋值_Alloc_traits::construct(this->_M_impl,__new_start + __elems_before,std::forward<_Args>(__args)...);__x);__new_finish = 0;//拷贝position位置前元素__new_finish = std::__uninitialized_move_if_noexcept_a(this->_M_impl._M_start, __position.base(),__new_start, _M_get_Tp_allocator());++__new_finish;//拷贝position位置后元素__new_finish= std::__uninitialized_move_if_noexcept_a(__position.base(), this->_M_impl._M_finish,__new_finish, _M_get_Tp_allocator());}__catch(...){if (!__new_finish)_Alloc_traits::destroy(this->_M_impl,__new_start + __elems_before);elsestd::_Destroy(__new_start, __new_finish, _M_get_Tp_allocator());_M_deallocate(__new_start, __len);__throw_exception_again;}//析构原vector所有元素std::_Destroy(this->_M_impl._M_start, this->_M_impl._M_finish,_M_get_Tp_allocator());//释放原vector内存空间_M_deallocate(this->_M_impl._M_start,this->_M_impl._M_end_of_storage,this->_M_impl._M_start);//vector内存地址指向新空间this->_M_impl._M_start = __new_start;this->_M_impl._M_finish = __new_finish;this->_M_impl._M_end_of_storage = __new_start + __len;}
}
其中_M_check_len函数的实现如下:
size_type
_M_check_len(size_type __n, const char* __s) const
{if (max_size() - size() < __n)__throw_length_error(__N(__s));//如果n小于当前size,内存加倍,否则内存增长n。const size_type __len = size() + std::max(size(), __n);return (__len < size() || __len > max_size()) ? max_size() : __len;
}
可以发现vector内存分配策略并不是简单的加倍,如果n小于当前size,内存加倍,否则内存增长n。
接着我们讲一下vector怎么删除元素。vector删除元素的函数有以下几个:
// 删除某个位置上的元素,返回下一个元素的位置
erase(const_iterator __position)
// 删除一个区间内的所有元素
erase(const_iterator __first, const_iterator __last)
// 清空所有元素
clear()
// 借助remove删除与val相等的所有元素
erase(remove(begin(), end(), val), end());
// 借助remove_if删除满足某个条件的所有元素
erase(remove_if(begin(), end(), <lambda>), end());
vector使用陷阱
-
vector的函数除了at函数之外,都不抛出异常,如果要取元素的话,均需要判断是否越界,不然会引发未知的错误;
-
vector的内存占用空间只增不减,在使用vector时要及时清除元素和回收内存,尤其是在static对象中使用vector保存元素时。vector提供的删除元素的成员函数,只是调用元素的析构函数析构掉元素,已经分配的内存并不会收回;
-
所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。
-
如果需要空间动态缩小,可以考虑使用deque。如果非vector不可,可以用swap()来帮助你释放内存。具体方法如下:
-
如果vector中存放的是指针,那么当vector销毁的时候,这些指针指向的对象不会被销毁,所占用的内存也不会被释放。