STL中的list容器

以下轉自http://www.cnblogs.com/BeyondAnyTime/archive/2012/08/10/2631191.html

STL中的list容器的一點總結

1.關於list容器

list是一種序列式容器。list容器完成的功能實際上和數據結構中的雙向鏈表是極其相似的,list中的數據元素是通過鏈表指針串連成邏輯意義上的線性表,也就是list也具有鏈表的主要優點,即:在鏈表的任一位置進行元素的插入、刪除操作都是快速的。list的實現大概是這樣的:list的每個節點有三個域:前驅元素指針域、數據域和後繼元素指針域。前驅元素指針域保存了前驅元素的首地址;數據域則是本節點的數據;後繼元素指針域則保存了後繼元素的首地址。其實,list和循環鏈表也有相似的地方,即:頭節點的前驅元素指針域保存的是鏈表中尾元素的首地址,list的尾節點的後繼元素指針域則保存了頭節點的首地址,這樣,list實際上就構成了一個雙向循環鏈。由於list元素節點並不要求在一段連續的內存中,顯然在list中是不支持快速隨機存取的,因此對於迭代器,只能通過“++”或“--”操作將迭代器移動到後繼/前驅節點元素處。而不能對迭代器進行+n或-n的操作,這點,是與vector等不同的地方。

 

我想把三個常用的序列式放在一起對比一下是有必要的:

vector : vector和built-in數組類似,擁有一段連續的內存空間,能非常好的支持隨即存取,即[]操作符,但由於它的內存空間是連續的,所以在中間進行插入和刪除會造成內存塊的拷貝,另外,當插入較多的元素後,預留內存空間可能不夠,需要重新申請一塊足夠大的內存並把原來的數據拷貝到新的內存空間。這些影響了vector的效率,但是實際上用的最多的還是vector容器,建議大多數時候使用vector效率一般是不錯的。vector的用法解析可以參考本人的另一篇隨筆:http://www.cnblogs.com/BeyondAnyTime/archive/2012/08/08/2627666.html

list:      list就是數據結構中的雙向鏈表(根據sgi stl源代碼),因此它的內存空間是不連續的,通過指針來進行數據的訪問,這個特點使得它的隨即存取變的非常沒有效率,因此它沒有提供[]操作符的重載。但由於鏈表的特點,它可以以很好的效率支持任意地方的刪除和插入。

deque: deque是一個double-ended queue,它的具體實現不太清楚,但知道它具有以下兩個特點:它支持[]操作符,也就是支持隨即存取,並且和vector的效率相差無幾,它支持在兩端的操作:push_back,push_front,pop_back,pop_front等,並且在兩端操作上與list的效率也差不多。

 

因此在實際使用時,如何選擇這三個容器中哪一個,應根據你的需要而定,具體可以遵循下面的原則
1. 如果你需要高效的隨即存取,而不在乎插入和刪除的效率,使用vector
2. 如果你需要大量的插入和刪除,而不關心隨即存取,則應使用list
3. 如果你需要隨即存取,而且關心兩端數據的插入和刪除,則應使用deque。

 

2.list中常用的函數

2.1list中的構造函數:

list() 聲明一個空列表;

list(n) 聲明一個有n個元素的列表,每個元素都是由其默認構造函數T()構造出來的

list(n,val) 聲明一個由n個元素的列表,每個元素都是由其複製構造函數T(val)得來的

list(n,val) 聲明一個和上面一樣的列表

list(first,last) 聲明一個列表,其元素的初始值來源於由區間所指定的序列中的元素


 

2.2 begin()和end():通過調用list容器的成員函數begin()得到一個指向容器起始位置的iterator,可以調用list容器的 end() 函數來得到list末端下一位置,相當於:int a[n]中的第n+1個位置a[n],實際上是不存在的,不能訪問,經常作爲循環結束判斷結束條件使用。


 

2.3 push_back() 和push_front():使用list的成員函數push_back和push_front插入一個元素到list中。其中push_back()從list的末端插入,而 push_front()實現的從list的頭部插入。


 

2.4 empty():利用empty() 判斷list是否爲空。


 

2.5 resize(): 如果調用resize(n)將list的長度改爲只容納n個元素,超出的元素將被刪除,如果需要擴展那麼調用默認構造函數T()將元素加到list末端。如果調用resize(n,val),則擴展元素要調用構造函數T(val)函數進行元素構造,其餘部分相同。


 

2.6 clear(): 清空list中的所有元素。


 

2.7 front()和back(): 通過front()可以獲得list容器中的頭部元素,通過back()可以獲得list容器的最後一個元素。但是有一點要注意,就是list中元素是空的時候,這時候調用front()和back()會發生什麼呢?實際上會發生不能正常讀取數據的情況,但是這並不報錯,那我們編程序時就要注意了,個人覺得在使用之前最好先調用empty()函數判斷list是否爲空。


 

2.8 pop_back和pop_front():通過刪除最後一個元素,通過pop_front()刪除第一個元素;序列必須不爲空,如果當list爲空的時候調用pop_back()和pop_front()會使程序崩掉。


 

2.9 assign():具體和vector中的操作類似,也是有兩種情況,第一種是:l1.assign(n,val)將 l1中元素變爲n個T(val)。第二種情況是:l1.assign(l2.begin(),l2.end())將l2中的從l2.begin()到l2.end()之間的數值賦值給l1。


 

2.10 swap():交換兩個鏈表(兩個重載),一個是l1.swap(l2); 另外一個是swap(l1,l2),都可能完成連個鏈表的交換。


 

2.11 reverse():通過reverse()完成list的逆置。


 

2.12 merge():合併兩個鏈表並使之默認升序(也可改),l1.merge(l2,greater<int>()); 調用結束後l2變爲空,l1中元素包含原來l1 和 l2中的元素,並且排好序,升序。其實默認是升序,greater<int>()可以省略,另外greater<int>()是可以變的,也可以不按升序排列。

看一下下面的程序:

View Code
 1 #include <iostream>
 2 #include <list>
 3 
 4 using namespace std;
 5 
 6 int main()
 7 {
 8     list<int> l1;
 9     list<int> l2(2,0);
10     list<int>::iterator iter;
11     l1.push_back(1);
12     l1.push_back(2);
13     l2.push_back(3);
14     l1.merge(l2,greater<int>());//合併後升序排列,實際上默認就是升序
15     for(iter = l1.begin() ; iter != l1.end() ; iter++)
16     {
17         cout<<*iter<<" ";
18     }
19     cout<<endl<<endl;
20     if(l2.empty())
21     {
22         cout<<"l2 變爲空 !!";
23     }
24     cout<<endl<<endl;
25     return 0;
26 }

運行結果:


 

2.13 insert():在指定位置插入一個或多個元素(三個重載):

l1.insert(l1.begin(),100); 在l1的開始位置插入100。

l1.insert(l1.begin(),2,200); 在l1的開始位置插入2個100。

l1.insert(l1.begin(),l2.begin(),l2.end());在l1的開始位置插入l2的從開始到結束的所有位置的元素。


 

2.14 erase():刪除一個元素或一個區域的元素(兩個重載)

l1.erase(l1.begin()); 將l1的第一個元素刪除。

l1.erase(l1.begin(),l1.end()); 將l1的從begin()到end()之間的元素刪除。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

以下轉自http://blog.csdn.net/mazidao2008/article/details/4802617

C++學習:list容器詳解

    (一)                                                                 list容器詳解

      首先說說STL

 

      STL就是Standard Template Library,標準模板庫。這可能是一個歷史上最令人興奮的工具的最無聊的術語。從根本上說,STL是一些“容器”的集合,這些“容器”有list,vector,set,map等,STL也是算法和其他一些組件的集合。這裏的“容器”和算法的集合指的是世界上很多聰明人很多年的傑作。

       STL的目的是標準化組件,這樣就不用重新開發,可以使用現成的組件。STL現在是C++的一部分,因此不用額外安裝什麼它被內建在你的編譯器之內。因爲STL的list是一個簡單的容器,所以我打算從它開始介紹STL如何使用。如果你懂得了這個概念,其他的就都沒有問題了。另外,list容器是相當簡單的,我們會看到這一點。

       STL容器可以保存對象,內建對象和類對象。它們會安全的保存對象,並定義我們能夠操作的這個對象的接口。放在蛋架上的雞蛋不會滾到桌上。它們很安全。因此,在STL容器中的對象也很安全。

       STL算法是標準算法,我們可以把它們應用在那些容器中的對象上。這些算法都有很著名的執行特性。它們可以給對象排序,刪除它們,給它們記數,比較,找出特殊的對象,把它們合併到另一個容器中,以及執行其他有用的操作。 
      STL iterator就象是容器中指向對象的指針STL的算法使用iterator在容器上進行操作。Iterator設置算法的邊界 ,容器的長度,和其他一些事情。舉個例子,有些iterator僅讓算法讀元素,有一些讓算法寫元素,有一些則兩者都行。 Iterator也決定在容器中處理的方向。
       你可以通過調用容器的成員函數begin()來得到一個指向一個容器起始位置的iterator。你可以調用一個容器的 end() 函數來得到過去的最後一個值(就是處理停在那的那個值)。

       這就是STL所有的東西,容器、算法、和允許算法工作在容器中的元素上的iterator。 算法以合適、標準的方法操作對象,並可通過iterator得到容器精確的長度。一旦做了這些,它們就在也不會“跑出邊界”。 還有一些其他的對這些核心組件類型有功能性增強的組件,例如函數對象。我們將會看到有關這些的例子,現在 ,我們先來看一看STL的list。

       1. 定義一個list
#include <string>
#include <list>
int main (void) 
{
 list<string> Milkshakes;
 return 0;
}

      這就行了,你已經定義了一個list。簡單嗎?list<string> Milkshakes這句是你聲明瞭list<string>模板類的一個實例然後就是實例化該類的一個對象。但是我們別急着做這個。在這一步其實你只需要知道你定義了一個字符串類型的list。你需要包含提供STL list類的頭文件#include <list>,注意iostream.h這個頭文件已經被STL的頭文件放棄了,所以可以不用這個頭文件。

      注:在LINUX中可以用 g++ test1.cpp -o test1 來編譯這個測試程序。

      2. 使用list的成員函數push_back和push_front插入一個元素到list中:

      現在我們有了一個list容器,我們可以使用它來裝東西了。我們將把一個字符串加到這個list裏。有一個非常重要的東西叫做list的值類型。值類型就是list中的對象的類型。在這個例子中,這個list的值類型就是字符串,string ,這是因爲這個list用來放字符串。
#include <string>
#include <list>

int main (void) 
{
 list<string> Milkshakes; //聲明瞭list<string>模板類的一個實例
 Milkshakes.push_back("Chocolate"); //實例化
 Milkshakes.push_back("Strawberry");
 Milkshakes.push_front("Lime");
 Milkshakes.push_front("Vanilla");
 return 0;
}

        我們現在有個4個字符串在list中。list的成員函數push_back()把一個對象放到一個list的後面,而 push_front()把對象放到前面。我通常把一些錯誤信息push_back()到一個list中去,然後push_front()一個標題到list中, 這樣它就會在這個錯誤消息以前打印它了。

 

      3. list的成員函數empty()

       知道一個list是否爲空很重要如果list爲空,empty()這個成員函數返回真。 我通常會這樣使用它。通篇程序我都用push_back()來把錯誤消息放到list中去。然後,通過調用empty() 我就可以說出這個程序是否報告了錯誤。如果我定義了一個list來放信息,一個放警告,一個放嚴重錯誤, 我就可以通過使用empty()輕易的說出到底有那種類型的錯誤發生了。 
      我可以整理這些list,然後在打印它們之前,用標題來整理它們,或者把它們排序成類。

     /**************************************************************
      //    Using a list to track and report program messages and status 

#include <iostream.h>
#include <string>
#include <list>
int main (void) 
{
 #define OK 0 
 #define INFO 1
 #define WARNING 2
 int return_code;
 list<string> InfoMessages;
 list<string> WarningMessages;

 // during a program these messages are loaded at various points
 InfoMessages.push_back("Info: Program started");
 // do work...
 WarningMessages.push_back("Warning: No Customer records have been found");
 // do work...

 return_code = OK; 

 if (!InfoMessages.empty()) {
  // there were info messages
  InfoMessages.push_front("Informational Messages:");
  // ... print the info messages list, we'll see how later
  return_code = INFO;
 }

 if (!WarningMessages.empty()) {
  // there were warning messages
  WarningMessages.push_front("Warning Messages:");
  // ... print the warning messages list, we'll see how later
  return_code = WARNING; 
 }

 // If there were no messages say so.
 if (InfoMessages.empty() && WarningMessages.empty()) {
  cout << "There were no messages " << endl;
 }

 return return_code;
}

******************************************************************/

     4.用for循環來處理list中的元素

     我們想要遍歷一個list,比如打印一個list中的所有對象來看看list上不同操作的結果。要一個元素一個元素的遍歷一個list, 我們可以這樣做:

       

 

      這個程序定義了一個iterator(類似指針),MilkshakeIterator。我們把它指向了這個list的第一個元素。 這可以調用Milkshakes.begin()來做到它會返回一個指向list開頭的iterator。然後我們把它和Milkshakes.end()的 返回值來做比較,當我們到了那兒的時候就停下來。 
  容器的end()函數會返回一個指向容器的最後一個位置的iterator當我們到了那裏,就停止操作。 我們不能不理容器的end()函數的返回值。我們僅知道它意味着已經處理到了這個容器的末尾,應該停止處理了。 所有的STL容器都要這樣做。 
  在上面的例子中,每一次執行for循環,我們就重複引用iterator來得到我們打印的字符串。

 

         在STL編程中,我們在每個算法中都使用一個或多個iterator。我們使用它們來存取容器中的對象要存取一個給定的對象,我們把一個iterator指向它,然後間接引用這個iterator。 
  這個list容器,就象你所想的,它不支持在iterator加一個數來指向隔一個的對象。 就是說,我們不能用Milkshakes.begin()+2來指向list中的第三個對象,因爲STL的list是以雙鏈的list來實現的, 它不支持隨機存取。vector和deque(向量和雙端隊列)和一些其他的STL的容器可以支持隨機存取。 

  上面的程序打印出了list中的內容。任何人讀了它都能馬上明白它是怎麼工作的。它使用標準的iterator和標準 的list容器。沒有多少程序員依賴它裏面裝的東西, 僅僅是標準的C++。這是一個向前的重要步驟。這個例子使用STL使我們的軟件更加標準。

/*
|| How to print the contents of a simple STL list. Whew! 
*/
#include <iostream.h>
#include <string>
#include <list>

int main (void) 
{
 list<string> Milkshakes;
 list<string>::iterator MilkshakeIterator;//爲Milkshakes實例容器定義一個iterator指針:MilkshakeIterator

 Milkshakes.push_back("Chocolate");
 Milkshakes.push_back("Strawberry");
 Milkshakes.push_front("Lime");
 Milkshakes.push_front("Vanilla");

 // print the milkshakes打印
 Milkshakes.push_front("The Milkshake Menu");
 Milkshakes.push_back("*** Thats the end ***");
 for (MilkshakeIterator=Milkshakes.begin();MilkshakeIterator!=Milkshakes.end(); ++MilkshakeIterator) 
 {
  // dereference the iterator to get the element
  cout << *MilkshakeIterator << endl; //輸出
 } 
}

 

(二)

用STL的通用算法for_each來處理list中的元素

  使用STL list和 iterator,我們要初始化、比較和給iterator增量來遍歷這個容器。STL通用的for_each 算法能夠減輕我們的工作。 

/*
|| How to print a simple STL list MkII
*/
#include <iostream.h>
#include <string>
#include <list>
#include <algorithm>

PrintIt (string& StringToPrint) {
 cout << StringToPrint << endl;
}

int main (void) {
 list<string> FruitAndVegetables;
 FruitAndVegetables.push_back("carrot");
 FruitAndVegetables.push_back("pumpkin");
 FruitAndVegetables.push_back("potato");
 FruitAndVegetables.push_front("apple");
 FruitAndVegetables.push_front("pineapple");

 for_each (FruitAndVegetables.begin(), FruitAndVegetables.end(), PrintIt);
}


  在這個程序中我們使用STL的通用算法for_each()來遍歷一個iterator的範圍,然後調用PrintIt()來處理每個對象。 我們不需要初始化、比較和給iterator增量。for_each()爲我們漂亮的完成了這些工作。我們執行於對象上的 操作被很好的打包在這個函數以外了,我們不用再做那樣的循環了,我們的代碼更加清晰了。 

  for_each算法引用了iterator範圍的概念,這是一個由起始iterator和一個末尾iterator指出的範圍。 起始iterator指出操作由哪裏開始,末尾iterator指明到哪結束,但是它不包括在這個範圍內。   

     用STL的通用算法count()來統計list中的元素個數

  STL的通用算法count()和count_it()用來給容器中的對象記數。就象for_each()一樣,count()和count_if() 算法也是在iterator範圍內來做的。 

  讓我們在一個學生測驗成績的list中來數一數滿分的個數。這是一個整型的List。 

/*
|| How to count objects in an STL list
*/
#include <list>
#include <algorithm>
#
int main (void) 
{
 list<int> Scores;
 #
 Scores.push_back(100); Scores.push_back(80);
 Scores.push_back(45); Scores.push_back(75);
 Scores.push_back(99); Scores.push_back(100);
 #
 int NumberOf100Scores(0); 
 count (Scores.begin(), Scores.end(), 100, NumberOf100Scores);
 #
 cout << "There were " << NumberOf100Scores << " scores of 100" << endl;
}


  count()算法統計等於某個值的對象的個數。上面的例子它檢查list中的每個整型對象是不是100。每次容器中的對象等於100,它就給NumberOf100Scores加1。這是程序的輸出: 

There were 2 scores of 100


  用STL的通用算法count_if()來統計list中的元素個數

  count_if()是count()的一個更有趣的版本。他採用了STL的一個新組件,函數對象。count_if() 帶一個函數對象的參數。函數對象是一個至少帶有一個operator()方法的類。有些STL算法作爲參數接收 函數對象並調用這個函數對象的operator()方法。 

  函數對象被約定爲STL算法調用operator時返回true或false。它們根據這個來判定這個函數。舉個例子會 說的更清楚些。count_if()通過傳遞一個函數對象來作出比count()更加複雜的評估以確定一個對象是否應該被 記數。在這個例子裏我們將數一數牙刷的銷售數量。我們將提交包含四個字符的銷售碼和產品說明的銷售記錄。 

/*
|| Using a function object to help count things
*/
#include <string>
#include <list>
#include <algorithm>

const string ToothbrushCode("0003");

class IsAToothbrush 
{
 public: 
  bool operator() ( string& SalesRecord ) 
  {
   return SalesRecord.substr(0,4)==ToothbrushCode;
  } 
};

int main (void) 
{
 list<string> SalesRecords;

 SalesRecords.push_back("0001 Soap");
 SalesRecords.push_back("0002 Shampoo");
 SalesRecords.push_back("0003 Toothbrush");
 SalesRecords.push_back("0004 Toothpaste");
 SalesRecords.push_back("0003 Toothbrush");

 int NumberOfToothbrushes(0); 
 count_if (SalesRecords.begin(), SalesRecords.end(), 
 IsAToothbrush(), NumberOfToothbrushes);

 cout << "There were " 
 << NumberOfToothbrushes 
 << " toothbrushes sold" << endl;
}


  這是這個程序的輸出: 

  There were 2 toothbrushes sold 這個程序是這樣工作的:定義一個函數對象類IsAToothbrush,這個類的對象能判斷出賣出的是否是牙刷 。如果這個記錄是賣出牙刷的記錄的話,函數調用operator()返回一個true,否則返回false。 

  count_if()算法由第一和第二兩個iterator參數指出的範圍來處理容器對象。它將對每個 IsAToothbrush()返回true的容器中的對象增加NumberOfToothbrushes的值。 

  最後的結果是NumberOfToothbrushes這個變量保存了產品代碼域爲"0003"的記錄的個數,也就是牙刷的個數。 

  注意count_if()的第三個參數IsAToothbrush(),它是由它的構造函數臨時構造的一個對象。你可以把IsAToothbrush類的一個臨時對象 傳遞給count_if()函數。count_if()將對該容器的每個對象調用這個函數。 

    使用count_if()的一個更加複雜的函數對象

  我們可以更進一步的研究一下函數對象。假設我們需要傳遞更多的信息給一個函數對象。我們不能通過 調用operator來作到這點,因爲必須定義爲一個list的中的對象的類型。 然而我們通過爲IsAToothbrush指出一個非缺省的構造函數就可以用任何我們所需要的信息來初始化它了。 例如,我們可能需要每個牙刷有一個不定的代碼。我們可以把這個信息加到下面的函數對象中: 

/*
|| Using a more complex function object
*/
#include <iostream.h>
#include <string>
#include <list>
#include <algorithm>

class IsAToothbrush 
{
 public:
  IsAToothbrush(string& InToothbrushCode) : 
  ToothbrushCode(InToothbrushCode) {}
  bool operator() (string& SalesRecord) 
  {
   return SalesRecord.substr(0,4)==ToothbrushCode;
  } 
 private:
  string ToothbrushCode; 
};

int main (void) 
{
 list<string> SalesRecords;

 SalesRecords.push_back("0001 Soap");
 SalesRecords.push_back("0002 Shampoo");
 SalesRecords.push_back("0003 Toothbrush");
 SalesRecords.push_back("0004 Toothpaste");
 SalesRecords.push_back("0003 Toothbrush");

 string VariableToothbrushCode("0003");

 int NumberOfToothbrushes(0); 
 count_if (SalesRecords.begin(), SalesRecords.end(), 
 IsAToothbrush(VariableToothbrushCode),
 NumberOfToothbrushes);
 cout << "There were "
 << NumberOfToothbrushes 
 << " toothbrushes matching code "
 << VariableToothbrushCode
 << " sold" 
 << endl;
}

  程序的輸出是: 

  There were 2 toothbrushes matching code 0003 sold 這個例子演示瞭如何向函數對象傳遞信息。你可以定義任意你想要的構造函數,你可以再函數對象中做任何你 想做的處理,都可以合法編譯通過。 

  你可以看到函數對象真的擴展了基本記數算法。 

  到現在爲止,我們都學習了: 

   ·定義一個list 

   ·向list中加入元素 

   ·如何知道list是否爲空 

   ·如何使用for循環來遍歷一個list 

   ·如何使用STL的通用算法for_each來遍歷list 

   ·list成員函數begin() 和 end() 以及它們的意義 

   ·iterator範圍的概念和一個範圍的最後一個位置實際上並不被處理這一事實 

   ·如何使用STL通用算法count()和count_if()來對一個list中的對象記數 

   ·如何定義一個函數對象 

  我選用這些例子來演示list的一般操作。如果你懂了這些基本原理,你就可以毫無疑問的使用STL了 建議你作一些練習。我們現在用一些更加複雜的操作來擴展我們的知識,包括list成員函數和STL通用算法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章