天天看點

stl容器使用中的經驗(一)--stl容器的選擇和基本概念

1、關于容器的兩個概念

1、連續記憶體容器(基于數組的容器)

将其元素放在一塊或多塊(動态配置設定)的記憶體中,每塊記憶體中有多個元素。當有新元素插入或者已有元素删除時,統一記憶體塊中的其他元素要向前或向後移動,以便為新元素讓出空間,或者填補删除元素的空間。這種移動會影響效率和異常安全,标準的連續容器:vector list deque。

2、關聯容器(基于節點的容器)

基于節點的容器在每一個(動态配置設定)記憶體塊上隻存儲一個元素。元素的插入和删除隻會影響指向節點的指針,并不會影響節點本身的内容。是以當插入或者删除時,元素的值不需要移動。

2、容器的選擇和容器切換之間的問題

日常經驗中,容器的選擇是個艱難的過程,雖然我們按照自己的需求确定好了容器,但無可避免的,我們可能會意識到自己的選擇并不是最佳答案的時候,改變容器的類型就會是一個痛苦的過程(比如我們從vector容器切換為map容器)。

我們不僅要修改編譯器診斷出的問題,還要進行一次比較詳細的檢查,因為新容器的性能特點,它使疊代器、指針、應用可能無效的規則。

我們考慮用封裝來解決這個問題。

class Widget{...}
vector<Widget> vw;
Widget bestWidget;

vector<Widget>::iterator iter = find(vw.begin(), vw.end(), bestWidget);
           

上面的例子定義了一個普通的vector容器,也是我們在日常中最常用的一種方式,但是如果我們按照上面的案例編寫寫代碼,并不可避免的進行容器的更換,那麼我們就會解決上面提到的所有問題。

class Widget{...}
typedef vector<Widget> WidgetContainer;
typedef WidgetContainer::iterator WCIterator;
WidgetContainer cw;
Widget bestWidget;

WCIterator iter = find(cw.begin(), cw.end(), bestWidget);
           

上面提到的解決辦法是,我們将容器進行封裝,這樣,在我們能切換容器的時候,或許隻是會修改容器類型。

3、區間成員函數優先于針對單元素的成員函數

為什麼說區間成員函數優先于與之對應的單元素的成員函數?好處不僅僅是隻有一點。

如果需要對矢量進行賦初值或者進行拷貝的時候,區間成員函數,顯示出了較單元素來說非常強大的代碼能力。

通常我們的做法會如下:

vector<int> v1;
vector<int> v2;

v1.clear();
for(vector<int>::iterator iter = v2.bengin(); iter != v2.end(); ++iter)
{
    v1.push_back(*iter);
}
           

通常的我們的做法是寫一個循環,對每一個元素進行插入,而這樣就是我們不可避免的要使用循環。就算我們使用一個其他的方法,比如:

vector<int> v1;
vector<int> v2{1, 3, 4, 6};
copy(v2.bengin(), v2.end(), back_inserter(v1));
           

或者

如上,上述的算法例子,雖然我們表面看不到有循環的出現,但是函數的背後肯定是有的。如下,我們直接使用assign成員函數,直接簡明。

vector<int> v1;
vector<int> v2;

v1.assign(v2.bengin(), v2.end());
           

1、能夠減少代碼

2、能夠是代碼意圖更明顯

4、stl容器線程安全性

  • 多線程讀取時安全的。多個線程和同時讀取同一個容器,但在讀的過程中不能有寫操作。
  • 多個線程對不同的容器做寫操作時安全的。

是以,在多線程中,我們需要考慮:

  • 對容器成員函數的每次調用,都鎖住容器直到調用結束;
  • 對容器傳回的每個疊代器的生命周期中,都鎖住容器;
  • 對于作用于容器的每個算法,都鎖住該容器,直到算法調用結束。

5、對容器判空時使用empty()而非size() == 0

對于一個容器,其實

if( size() == 0 ){...}

if( empty() ){...}

本質上等價的。

那麼為什麼會推薦使用

empty()

呢?主要是因為:該方法對任何一個容器來說,時間消耗都是常數級的,而

size()

對有些list的實作來說,耗時是線性的。

因為 list 是可以 splice 的,也就是list的拼接時并不知道自己本身元素的大小,如果想讓list也能夠有 size()常數級的操作,那就要保證list的每個成員函數都需要更新其size的大小。