SGI STL順序容器 vector
- vector vs array
- vector的迭代器
- vector的資料結構
- vector的構造與記憶體管理
- vector的查詢
- vector元素操作
- vector比較操作
- vector特殊操作swap
vector vs array
在SGI STL中,vector和array都是陣列容器,兩種操作非常相似。區別在於:array是靜態空間,一旦配置就不能改變;vector是動態空間,隨著新元素加入,內部機制或自行擴充空間以容納新元素。
vector的迭代器
vector維護的是一個連續線性空間,不論元素型別是什麼,普通指標都可以作為vector的迭代器而滿足所有必要條件,因為vector需要的操作:operator*,operator->,operatro++, operator--, operator+, operator-, operator+=, operator-=。而普通指標天生就具備這些操作。vector支援隨機存取(operator[]),而普通指標也有這樣的能力。因此,vector提供的迭代器是Random Access Iterators。
// vector迭代器關聯型別 template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) > class vector : protected _Vector_base<_Tp, _Alloc> { ... public: // 定義vector的迭代器關聯型別, 可用於iterator_traits萃取特性, 相容STL架構 typedef _Tp value_type; typedef value_type* pointer; typedef const value_type* const_pointer; typedef value_type* iterator; // vector的迭代器是普通指標 typedef const value_type* const_iterator; typedef value_type& reference; typedef const value_type& const_reference; ... };
也就是說,如果客戶端寫出這樣的程式碼:
vector<int>::iterator ivite;
vector<Shape>::iterator svite;
ivite的型別其實是int,svite的型別是Shape。
vector的資料結構
vector採用的資料結構就是一個線性連續空間。它以兩個迭代器start和finish分別指向配置得來的連續空間中目前已被使用的範圍,並以迭代器end_of_storage指向整塊連續空間(含備用空間)的尾端。
vector通過基類_Vector_base的三個迭代器start、finish、end_of_storage,提供線性空間的首尾表示、大小、容量、空容器判斷、隨機訪問運算子operator[]、最前端元素值、最後端元素值等功能。
vector主要通過下面這部分,對外提供介面訪問內部資料結構儲存的元素:
template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc>
{
...
public:
iterator begin() { return _M_start; } // 線性空間起始位置對應迭代器
const_iterator begin() const { return _M_start; } // 線性空間起始位置對應const迭代器
iterator end() { return _M_finish; } // 線性空間末尾位置對應迭代器
const_iterator end() const { return _M_finish; } // 線性空間末尾位置對應const迭代器
reverse_iterator rbegin()
{ return reverse_iterator(end()); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(end()); }
reverse_iterator rend()
{ return reverse_iterator(begin()); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(begin()); }
size_type size() const // 當前元素個數
{ return size_type(end() - begin()); }
size_type max_size() const // 理論上最大能支援的元素個數
{ return size_type(-1) / sizeof(_Tp); }
size_type capacity() const // 當前容量, 即線性空間最大能存放的元素個數
{ return size_type(_M_end_of_storage - begin()); }
bool empty() const // 判斷是否包含元素
{ return begin() == end(); }
reference operator[](size_type __n) { return *(begin() + __n); } // 隨機訪問第n-1個元素 (從0開始計), 返回元素reference
const_reference operator[](size_type __n) const { return *(begin() + __n); } // 隨機訪問第n-1個元素 (從0開始計), 返回元素const reference
reference front() { return *begin(); } // 第一個元素reference
const_reference front() const { return *begin(); } // 第一個元素對應const reference
reference back() { return *(end() - 1); } // 最後一個元素reference
const_reference back() const { return *(end() - 1); } // 最後一個元素對應const reference
...
};
vector對應儲存結構示意圖:
size()是指當前實際儲存元素的個數,capacity()是指底層用於儲存的線性空間總大小,即容量。有size() <= capacity()成了。
以vector
vector<int> iv(2,9);
iv.push_back(1);
iv.push_back(2);
iv.push_back(3);
iv.push_back(4);
往vector 呼叫push_back()插入元素空間變化示意圖:
當容量不足時,如果繼續插入元素,會導致vector擴容,擴容機制是這樣的:
1)當前容量為0,擴容後為1;
2)當然容量>0,擴容為原來的2倍。
擦除元素時,會導致finish指標移動,改變size()大小,但並不會導致end_of_storage指標變化,也就是不會導致容量變化。如果想要改變容量以節省空間,需要呼叫vector::shrink_to_fit(),減少容量以適應size()大小。不過shrink_to_fit()是C++11加入的內容,老版的SGI STL並沒有該功能。
簡而言之,老版SGI STL無法縮減vector容量。
vector的構造與記憶體管理
vector的空間配置與釋放
vector的空間配置是通過基類_Vector_base完成的,所有細節都封裝在基類內部。這樣,vector就無需關心空間如何配置、釋放的細節。
vector基類_Vector_base:
// vector基類, 負責維護vector底層資料結構的空間配置
template <class _Tp, class _Alloc>
class _Vector_base {
public:
typedef _Alloc allocator_type;
allocator_type get_allocator() const { return allocator_type(); } // 構造一個臨時allocator_type(空間配置器)物件
_Vector_base(const _Alloc&)
: _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
_Vector_base(size_t __n, const _Alloc&)
: _M_start(0), _M_finish(0), _M_end_of_storage(0)
{
_M_start = _M_allocate(__n); // 初始配置n byte空間
_M_finish = _M_start; // 初始finish與start在同一位置
_M_end_of_storage = _M_start + __n; // 初始end_of_storage位置
}
~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); } // 釋放整個線性空間
protected:
_Tp* _M_start; // 線性記憶體起始位置
_Tp* _M_finish; // 線性記憶體使用的結束位置
_Tp* _M_end_of_storage; // 線性記憶體的終點位置
typedef simple_alloc<_Tp, _Alloc> _M_data_allocator; // 一級空間配置/二級空間配置器的包裝器
_Tp* _M_allocate(size_t __n) // 為子類提供配置器的allocate()介面, 配置n byte空間
{ return _M_data_allocator::allocate(__n); }
void _M_deallocate(_Tp* __p, size_t __n) // 為子類提供配置器的deallocate()介面, 釋放起始地址為p的n byte空間
{ _M_data_allocator::deallocate(__p, __n); }
};
關於simple_alloc有一點需要注意:在vector中,所有記憶體操作都是以元素個數為單位,但對於一級/二級空間配置器,記憶體都是以byte為單位,這其中,就是simple_alloc做了轉換處理,將元素個數對應記憶體,轉換成了byte為單位的記憶體。
vector的構造
站在客戶端的角度,構造vector的方法:
vector<int> ivec; // vector內容為空
vector<int> ivec2(10); // size()為10, 內容0
vector<int> ivec3(20, 5); // 20個元素值為5
vector<int> ivec4(ivec3); // 20個元素值為5
vector<int> ivec5({1,2,3}); // 3個元素, {1,2,3}. C++11內容, 初值列表初始化vector, 這裡不列出
vector<int> ivec6(ivec3.begin(), ivec3.end()); // 用迭代區間構造vector, 內容為20個元素值5
vector的預設分配子是alloc(二級空間配置器),使用父類_Vector_base來遮蔽空間配置細節,方便以元素大小為配置單位。
// vector 建構函式
// 構造空vector, 對應客戶端vector<int> ivec
explicit vector(const allocator_type& __a = allocator_type())
: _Base(__a) {}
// 構造大小為n byte, 所有值為value的vector, 對應客戶端vector<int> ivec3(20, 5)
vector(size_type __n, const _Tp& __value,
const allocator_type& __a = allocator_type())
: _Base(__n, __a)
{ _M_finish = uninitialized_fill_n(_M_start, __n, __value); } // 用value填充未初始化記憶體區段(start, n)
// 構造大小為n byte的vector, 對應客戶端vector<int> ivec2(10)
explicit vector(size_type __n)
: _Base(__n, allocator_type())
{ _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); } // 這裡Tp是int, 用int()(預設值0)填充未初始化記憶體區段(start, n)
// 拷貝構造的vector, 對應客戶端vector<int> ivec4(ivec3)
vector(const vector<_Tp, _Alloc>& __x)
: _Base(__x.size(), __x.get_allocator())
{ _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); } // 將x對應的源迭代區間所有元素拷貝到未初始化記憶體段
// 用迭代區間[first, last)構造vector
vector(const _Tp* __first, const _Tp* __last,
const allocator_type& __a = allocator_type())
: _Base(__last - __first, __a)
{ _M_finish = uninitialized_copy(__first, __last, _M_start); } // 將源迭代區間所有內容拷貝到start起始的目的區間
vector的析構
vector的解構函式很簡單,呼叫全域性destroy() 釋放線性記憶體空間。
// 釋放迭代器區間[first, last), 如果迭代器指向基本型別, 什麼也不做; 如果迭代器指向class 型別, 就先逐個析構
~vector() { destroy(_M_start, _M_finish); }
// 注意vector析構之後, 會接著析構基類. 因此即使vector什麼也沒做, 基類會回收線性記憶體空間
// 析構基類會先回收記憶體段(start, end_of_storage - start)
// 空間配置那一章提到過, 如果是二級配置器, 記憶體>128byte, 會直接返還給OS; 如果記憶體<=128byte, 會加入到某個合適的free list, 留作備用
~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }
指定陣列初始大小reserve(), resize()
有沒有辦法讓vector一開始就保留指定大小的線性空間,而不是慢慢動態增長到?
答案是有的,可以用vector::reserve()。
// 讓vector容量 >= n 個元素
void reserve(size_type __n) {
if (capacity() < __n) { // 只有當前容量 < n時, 才需要重新配置空間
const size_type __old_size = size(); // 已經裝了元素個數
iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish); // 配置n個元素新空間, 並將[start, finish)元素拷貝到新空間
destroy(_M_start, _M_finish); // 呼叫全域性destroy()銷燬[start, finish)上的元素. 對於基本型別, 什麼也不做; 對於class型別, 析構物件
_M_deallocate(_M_start, _M_end_of_storage - _M_start); // 呼叫基類的deallocate() 釋放線性空間
// 重新配置start, finish, end_of_storage 管理線性空間
_M_start = __tmp;
_M_finish = __tmp + __old_size;
_M_end_of_storage = _M_start + __n;
}
}
// 配置n個元素新空間, 並將[start, finish)元素拷貝到新空間
// 遵循 "commit or rollback"規則
iterator _M_allocate_and_copy(size_type __n, const_iterator __first,
const_iterator __last)
{
iterator __result = _M_allocate(__n);
__STL_TRY {
uninitialized_copy(__first, __last, __result);
return __result;
}
__STL_UNWIND(_M_deallocate(__result, __n)); // commit or rollback精髓: 發生異常時, 釋放線性空間
}
還有一個跟reserve()類似的介面resize(),它是負責什麼的呢?
resize()用於指定vector的新size(),改變線性空間的finsih指標,但不改變start和end_of_storage指標。也就是說,resize()並不會影響capacity()大小。為滿足最終size()為新指定值,如果當前元素超過指定的size,就擦除多餘部分;如果當前元素佔用size()不足,就會用指定值插入vector。
// 如果size()超過new_size, 就擦除多餘的; 如果不超過, 就在末尾插入指定元素x. 最終目標是讓size()等於new_size
void resize(size_type __new_size, const _Tp& __x) {
if (__new_size < size()) // 新size < 現有size()時, 說明原來的size較大, 需要擦除一部分
erase(begin() + __new_size, end()); // 擦除多餘空間元素 [begin() + new_size, end())
else
insert(end(), __new_size - size(), __x);
}
void resize(size_type __new_size) { resize(__new_size, _Tp()); }
// 擦除指定位置position的元素, 後面的(position~末尾)元素整體向前移動
iterator erase(iterator __position) {
if (__position + 1 != end()) // 要刪除的元素不是末尾元素
copy(__position + 1, _M_finish, __position); // 將擦除位置後的區間[position+1, finish)元素, 拷貝到position起始處
--_M_finish; // 因為只擦除一個元素, finish向前移動1
destroy(_M_finish); // finish指向的就是要刪除的那個元素, 析構之, 但不釋放空間(尚未歸還給配置器)
return __position; // 返回銷燬元素的位置
}
// 擦除迭代區間[first, last), 後面的元素整體向前移動
iterator erase(iterator __first, iterator __last) {
iterator __i = copy(__last, _M_finish, __first); // 將擦除區間後的區間[last, finish)元素, 拷貝到first起始處
destroy(__i, _M_finish); // 析構[i, finish)物件, 但並沒有釋放空間
_M_finish = _M_finish - (__last - __first); // 先前移動finish指標
return __first; // 返回銷燬後的起始位置
}
vector的查詢
尺寸size()與容量capacity()
size()用於查詢vector當前元素個數,capacity()用於查詢當前vector線性空間最多容納元素個數。
size()和capacity()程式碼很簡單,利用了線性空間的3個指標(start, finish, end_of_storage)
size_type size() const // 當前元素個數
{ return size_type(end() - begin()); }
size_type capacity() const // 當前容量, 即線性空間最大能存放的元素個數
{ return size_type(_M_end_of_storage - begin()); }
迭代器訪問元素
vector支援迭代器訪問元素,提供iterator進行正向順序訪問,reverse_iterator進行反向訪問,以及它們的const版本。
所謂正向迭代器,是指從(地址空間)起始到末尾,從小下標到大下標的順序訪問;
所謂反向迭代器,是指從(地址空間)末尾到開始,從大下標到小下標的順序訪問。
// 正向迭代器
iterator begin() { return _M_start; } // 線性空間起始位置對應迭代器
const_iterator begin() const { return _M_start; } // 線性空間起始位置對應const迭代器
iterator end() { return _M_finish; } // 線性空間末尾位置對應迭代器
const_iterator end() const { return _M_finish; } // 線性空間末尾位置對應const迭代器
// 反向迭代器
reverse_iterator rbegin()
{ return reverse_iterator(end()); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(end()); }
reverse_iterator rend()
{ return reverse_iterator(begin()); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(begin()); }
引用訪問元素
通過兩類元素訪問方式:
1)通過front(),back()直接訪問第一個、最後一個元素,返回的是元素引用;
2)通過operator[] 隨機訪問指定下標的元素,返回的是元素引用。
reference operator[](size_type __n) { return *(begin() + __n); } // 隨機訪問第n-1個元素 (從0開始計), 返回元素reference
const_reference operator[](size_type __n) const { return *(begin() + __n); } // 隨機訪問第n-1個元素 (從0開始計), 返回元素const reference
reference front() { return *begin(); } // 第一個元素reference
const_reference front() const { return *begin(); } // 第一個元素對應const reference
reference back() { return *(end() - 1); } // 最後一個元素reference
const_reference back() const { return *(end() - 1); } // 最後一個元素對應const reference
vector元素操作
尾端插入元素push_back
push_back是線上性空間當前已經使用段的末尾,新增一個新物件,同時右移finish指標。
根據插入物件是否有引數,有兩個版本push_back:1)以值x構造的Tp物件;2)無參構造Tp物件,即不需要初值x用於構造Tp物件。
// 在尾端插入以x構造的物件(呼叫Tp(x)), 會導致size加1. 容量不夠時, 需要擴容
void push_back(const _Tp& __x) {
if (_M_finish != _M_end_of_storage) { // 備用空間足夠, 不需要擴容
construct(_M_finish, __x); // 在尾端finish所指位置用x構造物件Tp()
++_M_finish; // 尾端標記右移一格
}
else
_M_insert_aux(end(), __x); // 在指定位置end() 插入以x構造的物件Tp()
}
// 在尾端插入空物件(呼叫Tp()), 會導致size加1. 容量不夠時, 需要擴容
void push_back() {
if (_M_finish != _M_end_of_storage) { // 備用空間足夠, 不需要擴容
construct(_M_finish); // 在尾端finish所指位置構造物件Tp()
++_M_finish; // 尾端標記右移一格
}
else
_M_insert_aux(end()); // 在指定位置end() 插入新構造物件Tp()
}
指定位置插入元素insert
insert也是用於插入元素,跟push_back的區別在於insert是在指定位置(迭代器)插入新元素。
也就是說,push_back(x)相當於insert(end(), x)。
//-----------------------------
// insert單個物件
// 在指定位置position插入單個物件Tp(x)
iterator insert(iterator __position, const _Tp& __x) {
size_type __n = __position - begin();
if (_M_finish != _M_end_of_storage && __position == end()) {
construct(_M_finish, __x);
++_M_finish;
}
else
_M_insert_aux(__position, __x);
return begin() + __n;
}
// 在指定位置position插入單個物件Tp()
iterator insert(iterator __position) {
size_type __n = __position - begin();
if (_M_finish != _M_end_of_storage && __position == end()) {
construct(_M_finish);
++_M_finish;
}
else
_M_insert_aux(__position);
return begin() + __n;
}
//-----------------------------
// insertd多個物件
// 在指定位置position插入源區間[first, last)所有物件
void insert(iterator __position,
const_iterator __first, const_iterator __last);
// 在指定區間{pos, n}插入構造的Tp(x)物件
void insert (iterator __pos, size_type __n, const _Tp& __x)
{ _M_fill_insert(__pos, __n, __x); }
insert不僅支援插入單個元素,還支援插入多個元素。通過指定 起始位置 + 源迭代器區間,或者指定 起始位置+元素個數+元素值,就能在指定位置(區間)上一次插入多個元素。
insert批量插入程式碼較為複雜,我們直接圖解:
彈出最後一個元素pop_back
pop_back()彈出vector的最後一個元素,會讓size減-1,但不會影響capacity。
// 將尾端元素拿掉, 並調整大小
void pop_back() {
--_M_finish; // 尾端標記前移一格, 表示將放棄尾端元素
destroy(_M_finish); // 全域性函式, 如果finish所指元素是trivial type, 什麼也不做; 如果是class type, 析構finish所指物件
}
擦除元素earse
擦除元素分為兩類:1)擦除指定位置元素;2)擦除指定區間所有元素。
擦除元素後,後面的所有元素都會往前移動,以補充空位。另外,移動後的空位置,如果不是trivial type,需要呼叫其解構函式析構物件;如果是trivial type,可以什麼也不做。
擦除元素會影響size,因此需要移動尾端標記finish,移動格數取決於擦除的元素個數。但不會影響capacity。
// 擦除指定位置元素
iterator erase(iterator __position) {
if (__position + 1 != end()) // position所指並非最後一個元素, 說明後續還有元素, 需要往前移動補充擦除的空位
copy(__position + 1, _M_finish, __position); // 將元素從[position+1, finish)拷貝到[position, ...), 也就是position+1之後元素往前移動1格
--_M_finish; // 尾端標記前移一格
destroy(_M_finish); // 析構尾端元素物件
return __position; // 返回擦除元素的位置對應迭代器
}
// 擦除指定區間[first, last)的所有元素
iterator erase(iterator __first, iterator __last) {
iterator __i = copy(__last, _M_finish, __first); // 將源區間[last, finish)所有元素前移, 拷貝到目標區間[first, ...), 返回目標區間結尾位置
destroy(__i, _M_finish); // 銷燬拷貝後殘餘的物件[i, finish)
_M_finish = _M_finish - (__last - __first); // 尾端標記finish前移(last-first)格, 即已擦除元素個數
return __first;
}
注意:vector沒有像list那樣的remove移除元素介面。
清除所有元素clear
// 擦除所有元素, 如果是non-trivial type, 會對每個元素呼叫解構函式
void clear() { erase(begin(), end()); }
vector比較操作
SGI STL支援多種vector判斷操作,不過並不是作為vector member function,而是作為global function。這樣做,可以不用破壞容器的封裝性。
根據《Effective C++》Item19,operator>可以轉換為由operator<實現,operator!=可以轉換為由operator<實現。因此,只需要實現operator==和operator<即可。
參考:https://www.cnblogs.com/fortunely/p/15715265.html
operator==
operator==用於比較兩個vector是否相等。
// 比較兩個vector的所有元素是否相等, 相等性通過equal判斷
// 相等的兩個vector, 要求長度相等, 並且所有元素都相等(通過元素的 "!=" 判斷)
template <class _Tp, class _Alloc>
inline bool
operator==(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
{
return __x.size() == __y.size() &&
equal(__x.begin(), __x.end(), __y.begin());
}
operator<
operator<用於判斷第一個vector是否小於第二個。
// 字典序比較兩個vector的所有元素, 判斷第一個vector是否小於第二個
template <class _Tp, class _Alloc>
inline bool
operator<(const vector<_Tp, _Alloc>& __x, const vector<_Tp, _Alloc>& __y)
{
return lexicographical_compare(__x.begin(), __x.end(),
__y.begin(), __y.end());
}
vector特殊操作swap
swap()用於交換2個vector,代價很小,只需要交換底層線性空間的3個指標。
// 與x交換當前vector交換, 只需要交換維護底層線性空間3個指標即可, 無需將vector所有元素交換或拷貝
void swap(vector<_Tp, _Alloc>& __x) {
__STD::swap(_M_start, __x._M_start);
__STD::swap(_M_finish, __x._M_finish);
__STD::swap(_M_end_of_storage, __x._M_end_of_storage);
}