自己动手实现简易STL
自己動手實現簡易STL
- STL六個組件
- 迭代器 & 算法
- 容器,迭代器部分
- 適配器
- 仿函數
- 額外的工作
- 小結
之前學習C++看侯老師的書的時候實現了一下STL的基本組件,包括了6個組件,allocator, iterator, container, trait, functor, algorithm的組件,也是終于搞清楚了6個組件之間的相互關聯.分享給大家。
STL六個組件
比較難理解的就是萃取器和迭代器還有算法之間的交互。其實就是一個算法庫根據你迭代器屬于不同類型進行的一個選擇,選擇相對最優的方法。下面展示一下核心部分。對應于STL的容器,分別是單向,雙向,隨機訪問迭代器。
class input_iterator {}; class forward_iterator :public input_iterator {}; class bidirectional_iterator :public forward_iterator {}; class random_acess_iterator final: public bidirectional_iterator {};用繼承關系來表達這樣一個迭代器的從屬關系的原因是由于C++的一個機制,是利用重載的機制。我們首先考慮public繼承關系是一個 is a 的關系,那么再這個層次的繼承關系中,random_acess_iterator 是最為特殊的一個。
所以從特殊性的角度來考慮 (從特殊性來比較): random_acess_iterator > bidirectional_iterator > forward_iterator ,他們都是input_iterator。
C++的重載是有一個機制,如果有多個同時可以匹配的選擇最特殊的那個,這個機制仔細想想也OK,既然我可以匹配更特殊的,何必匹配更加泛化的呢。
下面舉一個最簡單的算法例子就懂了。
計算兩個迭代器之間的距離。
迭代器 & 算法
下面這個是調用函數,可以看到他還調用了一個_distance函數來作為輔助函數。
template<class InputIterator> unsigned int distance(InputIterator first, InputIterator last) {return _distance(first, last, Traits<InputIterator>::iterator_type()); }輔助函數一:
template<class InputIterator> unsigned int _distance(InputIterator first, InputIterator last, random_acess_iterator iter){return abs::abs(last - first); }第一行是為了debug的,后面一行可以看到只需要做一個減法就可以計算first到last的長度了。也就是O(1)的時間。為什么可以用減號,因為所有的隨機訪問迭代器都重載了 operator -
可以看到第三個參數就是做了這樣一個選擇。
輔助函數二:
template<class InputIterator> unsigned int _distance(InputIterator first, InputIterator last, forward_iterator iter){unsigned int dis = 0;for (; first != last; ++first)++dis;return dis; }對于其他類型來說,我不能和隨機訪問迭代器一樣,因此只能一個一個地加了,所以是O(last - first)的時間復雜度。
這里就體現了為什么需要迭代器,就是為了性能!
Q:為什么我不直接用這種重載的方式,非要加一個間接的調用層啊。
A: 因為容器有很多種,每個容器對應的迭代器實際是對應容器的特化的迭代器。但是這種重載機制需要迭代器的類型。而這個需要的類型是特化的那幾種迭代器。
舉個例子就是 deque和vector 都是random_acess_iterator.但是相應的算法具體是針對某個iterator。所以加入直接使用而不加這個distance的間接,是沒有辦法找到。或者你需要向里面一樣復雜的寫法,而又希望接口簡單,隱藏細節,因此采用這種寫法。
容器,迭代器部分
對于每個可以整迭代器的容器,都會有一個與之相當于的迭代器的,并且他們的關系是耦合的,也就是你實現一個容器,需要實現一個對應的迭代器才能加入STL的大家庭。當然也不是必須這么做,因為有的數據結構沒法設計迭代器。能這么設計的好處就是你可以用標準庫的算法。
下面簡單列一下大概的結構。
這樣一說,就只有allocator沒有說了,這里推薦看侯捷老師的內存管理,里面講到了這種簡單的實現,以及SGI的二層分配,也講了一個loki的能夠消除內部碎片的分配器。
這里為了簡化,只是用了簡單的new delete。
注意這里是不能使用 malloc 或者free的 除非你顯示地調用對象析構函數。
適配器
這個沒有太多可以說的,你可以認為就是接口之間的轉換。比如queue stack。舉個例子就是queue 我需要先進先出 也就是push_back() & pop_front()兩種操作, 因此我可以選擇list deque。
那么對于stack來說 我需要push_back() & pop_back() 那么vector list deque都可以作為我的底層實現。你在使用標準庫的時候也會看到你可以再模板自己定義一個類型。
仿函數
functor 就是為了讓標準庫的容器, 算法使用更加靈活。C++11之后很多接口可以直接用lambda 非常好用。
額外的工作
當時覺得這么寫代碼(CTRL + C + V)有點沒有意思,于是當時想能不能稍微擴充個整個小玩具。于是封裝win平臺的窗口作為一個類, 實現了一個相當于一個 容器的 一個可視化。(并沒有很好的完善,后面完善一下 貼出源代碼。) 封裝window窗口創建作為一個類 網絡上可以查一下,有點點技巧。
使用方式類似與迭代器,因為是直接和容器相關的。類似于這樣是直接耦合再一起
能夠簡單的可視化一些數據結構,也還是稍微有點意思吧。。。還增加的個按鈕 可以看到你最近的操作是怎么變化的。
這個就是一個二叉樹的啦。可以看到插入一個元素對他帶來的變化。
紅黑樹 具體的變化:
紅黑樹就是不像AVL樹對于高度那樣嚴格,通過紅黑節點的定義(有證明說明紅黑樹是和2-3-4樹本質是一樣的)
并且紅黑樹旋轉的一個很重要性質就是: O(1)的旋轉進行插入或者刪除,對比AVL樹是O(lgn)的旋轉數。
這個是list的window…
而且用這個窗口可以很好的debug,比如樹可以直觀的看里面的東西對不對,print出來不太直觀。
這些實現要注意窗口resize的消息,需要重新繪制一下。沒有加入滾動條的功能,有時間再加。
小結
非常推薦去實現一個vector 包括所有的組件,其實也不是那么簡單。不需要實現那么多算法 容器等,知道6個組件具體是怎么工作的就夠了。
這里簡潔一下6個組件如何交互,下篇文章會用最簡單的vector舉例子 示范一個具體的實現。
了解標準庫,可以更好地擴展他。比如怎么使用它底層的分配器,容器等等。根據自己的需求進行選擇,甚至可以做必要的擴充。
初學者,歡迎各位指出問題。
總結
以上是生活随笔為你收集整理的自己动手实现简易STL的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL 速成
- 下一篇: S32K144 EVB之FTM