1. 程式人生 > 其它 >SGI STL順序容器 vector

SGI STL順序容器 vector

目錄

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 iv(2,9)為例,經過下面操作:

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);
  }