string类的erase函数属于stl吗_探索STL容器:vector
用了這么久的 vector ,今天終于有時間來看下STL的實現源碼了,開心?~
最近幾個月在刷 leetcode ,用的較多的數據結構就是STL里面的 vector 了,相比較于直接的 array 數組,它具備了靈活地根據需求去分配管理內存,用戶只管往里面扔東西,拿東西,而不用費心費力去解決C++里面的動態內存問題。
那么大致猜想一下,要實現一個這樣的容器,不難想出在 vector 中至少存在這樣三個私有成員:head(指向列表第一個元素位置), tail(指向列表最后一個元素位置), size(容器大小) ,
打開源碼看看,得到驗證:
template<typename?_Tp,?typename?_Alloc>struct?_Vector_base?{
????......
????struct?_Vector_impl_data?{
????????pointer?_M_start;??//?指向容器中的第一個元素,是一個指針,指針類型為?Tp?所示類型
????????pointer?_M_finish;?//?指向容器最后一個元素,也是一個指針
????????pointer?_M_end_of_storage;?//?指向容器最后的位置
????????......
????}
}
template<typename?_Tp,?typename?_Alloc?=?std::allocator<_tp>?>
class?vector?:?protected?_Vector_base<_tp>?{typedef?_Vector_base<_tp>?_Base;?//?如上?_Vector_base?所示,是一個基礎實現
????......public:typedef?_Tp?value_type;??//?數據類型typedef?typename?_Base::pointer?pointer;?//?全局數據指針
????......
}
總之就是一層套一層,封裝了一個又一個,來完成對數據的抽象。但是最后的接口是放在 vector 上放開的。
vector 支持動態內存分配,支持隨機存取訪問,因此在數據訪問上具備了指針有的特性。那么它在源碼上是怎么實現的呢?首先來看它的接口(一部分常用接口)都有哪些:
**注:**雖然包含 頭文件就可以用 vector ,但是它的實現源碼其實是在 bits/stl_vector.h 下的,而且下面的程序刪掉了一些注釋類、系統判斷類函數。
template<typename?_Tp,?typename?_Alloc?=?std::allocator<_tp>?>class?vector?:?protected?_Vector_base<_tp>?{
????...//?獲取指向第一個元素的指針
????iterator?begin()?{?return?iterator(this->_M_impl._M_start);?}//?獲取指向容器最后一個可存放位置的下一個位置iterator?end(){?return?iterator(this->_M_impl._M_finish);?}//?獲取容器當前存放了多少元素size_type?size()?const?{?return?size_type(this->_M_impl._M_finish?-?this->_M_impl._M_start);?
????}//?對容器進行重新分配大小void?resize(size_type?__new_size)?{if?(__new_size?>?size())
????????????_M_default_append(__new_size?-?size());else?if?(__new_size?????????????_M_erase_at_end(this->_M_impl._M_start?+?__new_size);
????}//?獲取容器自身大小size_type?capacity()?const??{return?size_type(this->_M_impl._M_end_of_storage
?????????????????????????-?this->_M_impl._M_start);
????}//?看看容器當前是否為空bool?empty()?const??{?return?begin()?==?end();?}//?獲取一個能夠對返回對象讀寫操作的引用類型,其實就是指針
????reference?operator[](size_type?__n)??{return?*(this->_M_impl._M_start?+?__n);
????}protected://?檢查訪問是否合法void?_M_range_check(size_type?__n)?const?{if?(__n?>=?this->size())
????????????__throw_out_of_range_fmt(__N("vector::_M_range_check:?__n?""(which?is?%zu)?>=?this->size()?""(which?is?%zu)"),
?????????????????????????????????????__n,?this->size());
????}public://?跟?[]?操作符差不多,只不過多了一層范圍檢查reference?at(size_type?__n)?{
????????_M_range_check(__n);return?(*this)[__n];
????}//?獲取能夠對頭部元素讀寫的指針reference?front()??{?return?*begin();?}//?獲取最后一個元素reference?back()??{?return?*(end()?-?1);?}//?向容器末尾追加一個元素進來void?push_back(value_type?&&__x)?{?emplace_back(std::move(__x));?}template<typename...?_Args>#if?__cplusplus?>?201402L
????reference#elsevoid#endif
????emplace_back(_Args?&&...?__args);//?將最后一個元素從容器中刪掉void?pop_back()??{
????????--this->_M_impl._M_finish;
????????_Alloc_traits::destroy(this->_M_impl,?this->_M_impl._M_finish);
????}//?在指定位置插入?n?個?__xiterator?insert(const_iterator?__position,?size_type?__n,?const?value_type?&__x)?{
????????difference_type?__offset?=?__position?-?cbegin();
????????_M_fill_insert(begin()?+?__offset,?__n,?__x);return?begin()?+?__offset;
????}//?擦除掉某一個位置上的元素iterator?erase(const_iterator?__position)?{?return?_M_erase(begin()?+?(__position?-?cbegin()));?
????}//?交換兩個容器中的東西void?swap(vector?&__x)??{this->_M_impl._M_swap_data(__x._M_impl);
????????_Alloc_traits::_S_on_swap(_M_get_Tp_allocator(),
??????????????????????????????????__x._M_get_Tp_allocator());
????}//?將容器內所有元素清空void?clear()??{?_M_erase_at_end(this->_M_impl._M_start);?}
};
程序中大量出現的 allocator ,是來自SGI STL的空間分配器,說白了就是用來分配空間內存的。在拿到這部分源碼后,vector 的實現原理也就可以大致有所掌握了(畢竟每個接口也不過幾行?,這可能就是優秀設計的結果??)。而在調用過程中無非就是 vector 調用 vector_base 再調用 allocator 再對 _Vector_impl_data 進行操作。
還有一個問題:在添加元素過程中,如果容器滿了,那么容器的容量是按照怎樣的規則遞增呢?參考了《STL源碼刨析》之后,得到這樣的結論:
如果超過當前容器容量,那么容量會擴增至兩倍,如果兩倍容量仍不夠,那么就擴張至足夠大的容量。
在容量的擴張過程中,必須經歷“重新分配內存,元素移動,釋放原有空間”三個操作,這是因為原有的空間之后不一定能夠滿足需求,所以統一進行這三個操作來完成。
如何知道是擴增兩倍的呢?最直觀直接的方法就是執行一下這個過程看看,例如:
int?main()?{????vector<int>?v;
????v.push_back(1);
????cout?<"?"?<endl;????//?>:?1?1
????v.push_back(2);
????cout?<"?"?<endl;????//?>:?2?2
????v.push_back(3);
????cout?<"?"?<endl;????//?>:?3?4
????v.push_back(4);
????cout?<"?"?<endl;????//?>:?4?4
????v.push_back(5);
????cout?<"?"?<endl;????//?>:?5?8
}
這樣的容量擴張過程,也帶來另一個問題:當容量為 2 時獲取到的 iterator ,那么在容量為 8 時,還可以用嘛?答案是不一定行,例如:
int?main()?{????vector<int>?v;
????v.push_back(1);
????v.push_back(2);
????v.push_back(3);
????auto?iter?=?v.begin();
????cout?<endl;??//?>:?1
????v.push_back(4);
????cout?<endl;??//?>:?1
????v.push_back(5);
????cout?<endl;??//?>:?310076128[具體運行結果視內存情況而定]
????iter?=?v.begin();
????cout?<endl;??//?>:?1
}
由此可知,如果當時的內存環境允許,會直接拼到原有容器后面去,如果不允許,那么就需要把當前容器的內容移動到其他地方去了,這時候原來的 iterator 就不能用了。務必小心!
從這一方面也可以體會到,其實 iterator 就是一個類型為傳入 vector 中 T 類型的指針。
在所有的接口中,覺得最有意思的就是 insert 接口了?,它的實現過程比較好玩。
首先,假設調用函數為:insert(position, n, x) ,而且剩余空間夠用,那么它需要分成兩種情況:
上面兩種情況分別對應下面圖中的左右兩邊:
內存操作示意圖(參考自《STL源碼刨析》)在有限的過程和空間里實現最高效的操作,不愧是STL???。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的string类的erase函数属于stl吗_探索STL容器:vector的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win7 右键计算机 服务 设备管理器,
- 下一篇: 箭头函数的this指向谁_高阶函数