STL vector的內部實現原理及基本用法
【原文:http://blog.csdn.net/u012658346/article/details/50725933】
本文基於STL vector原始碼,但是不考慮分配器allocator,迭代器iterator,異常處理try/catch等內容,同時對_Ucopy()、 _Umove()、 _Ufill()函式也不會過度分析。
一、vector的定義
template<class _Ty,
class _Ax>
class vector
: public _Vector_val<_Ty, _Ax>
{ // varying size array of values
public:
/********/
protected:
pointer _Myfirst; // pointer to beginning of array
pointer _Mylast; // pointer to current end of sequence
pointer _Myend; // pointer to end of array
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
簡單理解,就是vector是利用上述三個指標來表示的,基本示意圖如下:
兩個關鍵大小:
大小:size=_Mylast - _Myfirst;
容量:capacity=_Myend - _Myfirst;
分別對應於resize()、reserve()兩個函式。
size表示vector中已有元素的個數,容量表示vector最多可儲存的元素的個數;為了降低二次分配時的成本,vector實際配置的大小可能比客戶需求的更大一些,以備將來擴充,這就是容量的概念。即capacity>=size,當等於時,容器此時已滿,若再要加入新的元素時,就要重新進行記憶體分配,整個vector的資料都要移動到新記憶體。二次分配成本較高,在實際操作時,應儘量預留一定空間,避免二次分配。
二、構造與析構
1、構造
vector的建構函式主要有以下幾種:
vector() : _Mybase()
{ // construct empty vector
_Buy(0);
}
explicit vector(size_type _Count) : _Mybase()
{ // construct from _Count * _Ty()
_Construct_n(_Count, _Ty());
}
vector(size_type _Count, const _Ty& _Val) : _Mybase()
{ // construct from _Count * _Val
_Construct_n(_Count, _Val);
}
vector(const _Myt& _Right) : _Mybase(_Right._Alval)
{ // construct by copying _Right
if (_Buy(_Right.size()))
_Mylast = _Ucopy(_Right.begin(), _Right.end(), _Myfirst);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
vector優異效能的祕訣之一,就是配置比其所容納的元素所需更多的記憶體,一般在使用vector之前,就先預留足夠空間,以避免二次分配,這樣可以使vector的效能達到最佳。因此元素個數_Count是個遠比元素值 _Val重要的引數,因此當構造一個vector時,首要引數一定是元素個數。
由上各建構函式可知,基本上所有建構函式都是基於_Construct _n() 的
bool _Buy(size_type _Capacity)
{ // allocate array with _Capacity elements
_Myfirst = 0, _Mylast = 0, _Myend = 0;
if (_Capacity == 0) //_Count為0時,直接返回
return (false);
else
{ // nonempty array, allocate storage
_Myfirst = this->_Alval.allocate(_Capacity); //分配記憶體,並更新成員變數
_Mylast = _Myfirst;
_Myend = _Myfirst + _Capacity;
}
return (true);
}
void _Construct_n(size_type _Count, const _Ty& _Val)
{ // 構造含有_Count個值為_Val的元素的容器
if (_Buy(_Count))
_Mylast = _Ufill(_Myfirst, _Count, _Val);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
這樣就完成了vector容器的構造了。
2、析構
vector的解構函式很簡單,就是先銷燬所有已存在的元素,然後釋放所有記憶體
void _Tidy()
{ // free all storage
if (_Myfirst != 0)
{ // something to free, destroy and deallocate it
_Destroy(_Myfirst, _Mylast);
this->_Alval.deallocate(_Myfirst, _Myend - _Myfirst);
}
_Myfirst = 0, _Mylast = 0, _Myend = 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
三、插入和刪除元素
vector的插入和刪除元素是通過push_ back () 、 pop_back()兩個介面來實現的,他們的內部實現也非常簡單
void push_back(const _Ty& _Val)
{ // insert element at end
if (size() < capacity())
_Mylast = _Ufill(_Mylast, 1, _Val);
else
insert(end(), _Val); //空間不足時,就會觸發記憶體的二次分配
}
void pop_back()
{ // erase element at end
if (!empty())
{ // erase last element
_Destroy(_Mylast - 1, _Mylast);
--_Mylast;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
四、其他介面
1、reserve()操作
之前提到過reserve(Count) 函式主要是預留Count大小的空間,對應的是容器的容量,目的是保證(_Myend - _Myfirst)>=Count。只有當空間不足時,才會操作,即重新分配一塊記憶體,將原有元素拷貝到新記憶體,並銷燬原有記憶體
void reserve(size_type _Count)
{ // determine new minimum length of allocated storage
if (capacity() < _Count)
{ // not enough room, reallocate
pointer _Ptr = this->_Alval.allocate(_Count);
_Umove(begin(), end(), _Ptr);
size_type _Size = size();
if (_Myfirst != 0)
{ // destroy and deallocate old array
_Destroy(_Myfirst, _Mylast);
this->_Alval.deallocate(_Myfirst, _Myend - _Myfirst);
}
_Myend = _Ptr + _Count;
_Mylast = _Ptr + _Size;
_Myfirst = _Ptr;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
2、resize()操作
resize(Count) 函式主要是用於改變size的,也就是改變vector的大小,最終改變的是(_Mylast - _Myfirst)的值,當size < Count時,就插入元素,當size >Count時,就擦除元素。
void resize(size_type _Newsize, _Ty _Val)
{ // determine new length, padding with _Val elements as needed
if (size() < _Newsize)
_Insert_n(end(), _Newsize - size(), _Val);
else if (_Newsize < size())
erase(begin() + _Newsize, end());
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
3、_Insert_n()操作
resize()操作和insert()操作都會利用到_Insert_n()這個函式,這個函式非常重要,也比其他函式稍微複雜一點
雖然_Insert_n(_where, _Count, _Val ) 函式比較長,但是操作都非常簡單,主要可以分為以下幾種情況:
-
1、_Count == 0,不需要插入,直接返回
-
2、max_size() - size() < _Count,超過系統設定的最大容量,會溢位,造成Xlen()異常
- 3、_Capacity < size() + _Count,vector的容量不足以插入Count個元素,需要進行二次分配,擴大vector的容量。 在VS下,vector容量會擴大50%,即 _Capacity = _Capacity + _Capacity / 2;
若仍不足,則 _Capacity = size() + _Count;
else if (_Capacity < size() + _Count)
{ // not enough room, reallocate
_Capacity = max_size() - _Capacity / 2 < _Capacity
? 0 : _Capacity + _Capacity / 2; // try to grow by 50%
if (_Capacity < size() + _Count)
_Capacity = size() + _Count;
pointer _Newvec = this->_Alval.allocate(_Capacity);
pointer _Ptr = _Newvec;
_Ptr = _Umove(_Myfirst, _VEC_ITER_BASE(_Where),_Newvec); // copy prefix
_Ptr = _Ufill(_Ptr, _Count, _Val); // add new stuff
_Umove(_VEC_ITER_BASE(_Where), _Mylast, _Ptr); // copy suffix
//記憶體釋放與變數更新
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
這種情況下,資料從原始容器移動到新分配記憶體時是從前到後移動的
-
4、空間足夠,且被插入元素的位置比較靠近_Mylast,即已有元素的尾部
這種情況下不需要再次進行記憶體分配,且資料是從後往前操作的。首先是將where~last向後移動,為待插入資料預留Count大小的空間,然後從_Mylast處開始填充,然後將從where處開始填充剩餘元素
else if ((size_type)(_Mylast - _VEC_ITER_BASE(_Where)) < _Count)
{ // new stuff spills off end
_Umove(_VEC_ITER_BASE(_Where), _Mylast,
_VEC_ITER_BASE(_Where) + _Count); // copy suffix
_Ufill(_Mylast, _Count - (_Mylast - _VEC_ITER_BASE(_Where)),
_Val); // insert new stuff off end
_Mylast += _Count;
std::fill(_VEC_ITER_BASE(_Where), _Mylast - _Count,
_Val); // insert up to old end
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 5、空間足夠,但插入的位置比較靠前
{ // new stuff can all be assigned
_Ty _Tmp = _Val; // in case _Val is in sequence
pointer _Oldend = _Mylast;
_Mylast = _Umove(_Oldend - _Count, _Oldend,
_Mylast); // copy suffix
_STDEXT _Unchecked_move_backward(_VEC_ITER_BASE(_Where), _Oldend - _Count,
_Oldend); // copy hole
std::fill(_VEC_ITER_BASE(_Where), _VEC_ITER_BASE(_Where) + _Count,
_Tmp); // insert into hole
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
4、erase()操作
iterator erase(const_iterator _First_arg,
const_iterator _Last_arg)
{ // erase [_First, _Last)
iterator _First = _Make_iter(_First_arg);
iterator _Last = _Make_iter(_Last_arg);
if (_First != _Last)
{ // worth doing, copy down over hole
pointer _Ptr = _STDEXT unchecked_copy(_VEC_ITER_BASE(_Last), _Mylast,
_VEC_ITER_BASE(_First));
_Destroy(_Ptr, _Mylast);
_Mylast = _Ptr;
}
return (_First);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
主要操作就是將後半部分的有效元素向前拷貝,並將後面空間的無效元素析構,並更新_Mylast變數
5、assign()操作
assign()操作最終都會呼叫到下面的函式,主要操作是首先擦除容器中已有的全部元素,在從頭開始插入Count個Val元素
void _Assign_n(size_type _Count, const _Ty& _Val)
{ // assign _Count * _Val
_Ty _Tmp = _Val; // in case _Val is in sequence
erase(begin(), end());
insert(begin(), _Count, _Tmp);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
五、基本使用
在經過上述對vector內部實現的分析後,再來理解相應介面就變得簡單得多。
vector對外介面主要可以分為:
- 構造、析構:
ector<Elem> c
vector <Elem> c1(c2)
vector <Elem> c(n)
vector <Elem> c(n, elem)
vector <Elem> c(beg,end)
c.~ vector <Elem>()
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
- 插入、刪除、賦值
c.push_back(elem)
c.pop_back()
c.insert(pos,elem)
c.insert(pos,n,elem)
c.insert(pos,beg,end)
c.erase(pos)
c.erase(beg,end)
c.clear()
c.assign(beg,end)
c.assign(n,elem)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 大小相關
c.capacity()
c.max_size()
c.resize(num)
c.reserve()
c.size()
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
- 獲取迭代器
c.begin()
c.end()
c.rbegin()
c.rend()
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
- 獲取資料
operator[]
c.at(idx)
c.front()
c.back()
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
相關文章
- C++STL第二篇(vector的原理用法)C++
- STL:vector用法總結
- vector的基本用法
- stl中map的基本用法
- mysqldump的內部實現原理MySql
- webpack基本用法及原理(10000+)Web
- npm基本用法及原理(10000+)NPM
- 【譯】Go 切片:用法和內部實現Go
- STL容器---Vector
- RPC基本原理及實現RPC
- STL使用篇__vector
- C++ STL -- vectorC++
- STL---vector(向量)
- Java 阻塞佇列(BlockingQueue)的內部實現原理Java佇列BloC
- 初探STL容器之Vector
- 聊聊 TokenBucket 限流器的基本原理及實現
- 快速理解Go陣列和切片的內部實現原理Go陣列
- MongoDB 及 PyMongo 的基本用法MongoDB
- STL原始碼剖析——vector容器原始碼
- 最詳細STL(一)vector
- STL----vector注意事項
- STL-Vector容量問題:
- gostring的內部實現Go
- 太極1:STL:vector和string
- Ruby 探針的基本實現原理
- vim配置及基本用法
- ThreadLocal用法及原理thread
- Spark的基本結構及SparkSQL元件的基本用法SparkSQL元件
- 從原始碼的角度來談一談HashMap的內部實現原理原始碼HashMap
- Java HashMap原理及內部儲存結構JavaHashMap
- iOS中atomic和nonatomic區別及內部實現iOS
- C++標準模板庫(STL)迭代器的原理與實現C++
- Promise實現的基本原理(一)Promise
- Promise實現的基本原理(二)Promise
- SPA路由實現的基本原理路由
- allure用法(一)-配置資訊及基本用法
- Promise含義及基本用法Promise
- Intellij安裝及基本用法IntelliJ
- 基於Yii2對RabbitMQ的基本用法封裝及佇列實現(一)MQ封裝佇列