STL list鏈表的用法詳解

 

-------------------------------------------------------------------------------html

 

原來... ios

STL list鏈表的用法詳解

本文以List容器爲例子,介紹了STL的基本內容,從容器到迭代器,再到普通函數,並且例子豐富,通俗易懂。不失爲STL的入門文章,新手不容錯過!程序員

 

  0 前言算法

  1 定義一個list編程

  2 使用list的成員函數push_backpush_front插入一個元素到list安全

  3 list的成員函數empty()app

  4 for循環來處理list中的元素函數

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

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

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

  8 使用STL通用算法find()list中查找對象

  9 使用STL通用算法find_if()list中搜索對象

  10 使用STL通用算法searchlist中找一個序列

  11 使用list的成員函數sort()排序一個list

  12 list的成員函數插入元素到list

  13 List 構造函數

  14 使用list成員函數從list中刪除元素

  15 list成員函數remove()list中刪除元素。

  16 使用STL通用算法remove()list中刪除元素

  17 使用STL通用算法stable_partition()list成員函數splice()來劃分一個list

  18 結論

  在field中使用STL

  19 參考書目

 

 

0 前言

 

    這篇文章是關於C++語言的一個新的擴展——標準模板庫的(Standard Template Library),也叫STL

 

    當我第一次打算寫一篇關於STL的文章的時候,我不得不認可我當時低估了這個話題的深度和廣度。有不少內容要含蓋,也有不少詳細描述STL的書。所以我從新考慮了一下我原來的想法。我爲何要寫這篇文章,又爲何要投稿呢?這會有什麼用呢?有再來一篇關於STL的文章的必要嗎?

    當我翻開Musser and Saini的頁時,我看到了編程時代在我面前消融。我能看到深夜消失了,目標軟件工程出現了。我看到了可維護的代碼。一年過去了,我使用STL寫的軟件仍然很容易維護。讓人吃驚的是其餘人能夠沒有我而維護的很好!

    然而,我也記得在一開始的時候很難弄懂那些技術術語。一次,我買了Musser&Saini,每件事都依次出現,可是在那之前我最渴望獲得的東西是一些好的例子。

    當我開始的時候,做爲C++一部分的Stroustrup還沒出來,它覆蓋了STL

    所以我想寫一篇關於一個STL程序員的真實生活的文章可能會有用。若是我手上有一些好的例子的話,特別是象這樣的新題目,我會學的更快。

    另一件事是STL應該很好用。所以,理論上說,咱們應該能夠立刻開始使用STL

    什麼是STL呢?STL就是Standard Template Library,標準模板庫。這多是一個歷史上最使人興奮的工具的最無聊的術語。從根本上說,STL是一些容器的集合,這些容器list,vector,set,map等,STL也是算法和其餘一些組件的集合。這裏的容器和算法的集合指的是世界上不少聰明人不少年的傑做。

    STL的目的是標準化組件,這樣你就不用從新開發它們了。你能夠僅僅使用這些現成的組件。STL如今是C++的一部分,所以不用額外安裝什麼。它被內建在你的編譯器以內。由於STLlist是一個簡單的容器,因此我打算從它開始介紹STL如何使用。若是你懂得了這個概念,其餘的就都沒有問題了。另外,list容器是至關簡單的,咱們會看到這一點。

    這篇文章中咱們將會看到如何定義和初始化一個list,計算它的元素的數量,從一個list裏查找元素,刪除元素,和一些其餘的操做。要做到這些,咱們將會討論兩個不一樣的算法,STL通用算法都是能夠操做不止一個容器的,而list的成員函數是list容器專有的操做。

    這是三類主要的STL組件的簡明綱要。STL容器能夠保存對象,內建對象和類對象。它們會安全的保存對象,並定義咱們可以操做的這個對象的接口。放在蛋架上的雞蛋不會滾到桌上。它們很安全。所以,在STL容器中的對象也很安全。我知道這個比喻聽起來很老土,可是它很正確。

    STL算法是標準算法,咱們能夠把它們應用在那些容器中的對象上。這些算法都有很著名的執行特性。它們能夠給對象排序,刪除它們,給它們記數,比較,找出特殊的對象,把它們合併到另外一個容器中,以及執行其餘有用的操做。

    STL iterator就象是容器中指向對象的指針。STL的算法使用iterator在容器上進行操做。Iterator設置算法的邊界,容器的長度,和其餘一些事情。舉個例子,有些iterator僅讓算法讀元素,有一些讓算法寫元素,有一些則二者都行。 Iterator也決定在容器中處理的方向。

    你能夠經過調用容器的成員函數begin()來獲得一個指向一個容器起始位置的iterator。你能夠調用一個容器的 end() 函數來獲得過去的最後一個值(就是處理停在那的那個值)。

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

 

 

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

 

1 定義一個list

 

咱們能夠象這樣來定義一個STLlist

#include <string>

#include <list>

int main (void)

{

list<string> Milkshakes;

return 0;

}

這就好了,你已經定義了一個list。簡單嗎?list<string> Milkshakes這句是你聲明瞭list<string>模板類的一個實例,而後就是實例化這個類的一個對象。可是咱們別急着作這個。在這一步其實你只須要知道你定義了 一個字符串的list。你須要包含提供STL list類的頭文件。我用gcc 2.7.2在個人Linux上編譯這個測試程序,例如:

 

g++ test1.cpp -o test1

 

注意iostream.h這個頭文件已經被STL的頭文件放棄了。這就是爲何這個例子中沒有它的緣由。

 

如今咱們有了一個list,咱們能夠看實使用它來裝東西了。咱們將把一個字符串加到這個list裏。有一個很是 重要的東西叫作list的值類型。值類型就是list中的對象的類型。在這個例子中,這個list的值類型就是字符串,string 這是由於這個list用來放字符串。

 

 

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

 

2 使用list的成員函數push_backpush_front插入一個元素到list

 

#include <string>

#include <list>

 

int main (void)

{

list<string> Milkshakes;

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 咱們能夠這樣作:

 

/*

|| 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.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;

    }

}

這個程序定義了一個iteratorMilkshakeIterator。咱們把它指向了這個list的第一個元素。 這能夠調用Milkshakes.begin()來做到,它會返回一個指向list開頭的iterator。而後咱們把它和Milkshakes.end() 返回值來作比較,當咱們到了那兒的時候就停下來。

 

容器的end()函數會返回一個指向容器的最後一個位置的iterator。當咱們到了那裏,就中止操做。 咱們不能不理容器的end()函數的返回值。咱們僅知道它意味着已經處理到了這個容器的末尾,應該中止處理了。 全部的STL容器都要這樣作。

 

在上面的例子中,每一次執行for循環,咱們就重複引用iterator來獲得咱們打印的字符串。

 

STL編程中,咱們在每一個算法中都使用一個或多個iterator。咱們使用它們來存取容器中的對象。 要存取一個給定的對象,咱們把一個iterator指向它,而後間接引用這個iterator

 

這個list容器,就象你所想的,它不支持在iterator加一個數來指向隔一個的對象。 就是說,咱們不能用Milkshakes.begin()+2來指向list中的第三個對象,由於STLlist是以雙鏈的list來實現的, 它不支持隨機存取。vectordeque(向量和雙端隊列)和一些其餘的STL的容器能夠支持隨機存取。

 

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

 

 

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

 

5 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中的元素個數。

 

 

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

 

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

 

STL的通用算法count()count_if()用來給容器中的對象記數。就象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);

    NumberOf100Scores = count(Scores.begin(), Scores.end(), 100);

    cout << "There were " << NumberOf100Scores << " scores of 100" << endl;

}

 

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

程序的輸出:

There were 2 scores of 100

 

 

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

 

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

 

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

    函數對象被約定爲STL算法調用operator時返回truefalse。它們根據這個來斷定這個函數。舉個例子會說的更清楚些。

    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()將對該容器的每一個對象調用這個函數。

 

 

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

 

7 使用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通用算法。

 

輸出是:

Pineapple

 

若是沒有找到指出的對象,就會返回Fruit.end()的值,要是找到了就返回一個指着找到的對象的iterator

 

 

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

 

8.使用STL通用算法find()list中查找對象

 

咱們如何在list中查找東西呢?

STL的通用算法find()find_if()能夠作這些。

就象for_each(), count(), count_if() 同樣,這些算法也使用iterator範圍,這個範圍指出一個list或任意其餘容器中的一部分來處理。一般首iterator指着開始的位置,次iterator指着中止處理的地方。由次iterator指出的元素不被處理。

這是find()如何工做:

/*

|| How to find things in an STL list

*/

#include <string>

#include <list>

#include <algorithm>

 

int main (void)

{

    list<string> Fruit;

    list<string>::iterator FruitIterator;

 

    Fruit.push_back("Apple");

    Fruit.push_back("Pineapple");

    Fruit.push_back("Star Apple");

 

    FruitIterator = find (Fruit.begin(), Fruit.end(), "Pineapple");

 

    if (FruitIterator == Fruit.end())

    {

        cout << "Fruit not found in list" << endl;

    }

    else

    {

        cout << *FruitIterator << endl;

    }

}

 

輸出是:

Pineapple

若是沒有找到指出的對象,就會返回Fruit.end()的值,要是找到了就返回一個指着找到的對象i

 

 

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

 

9.使用STL通用算法find_if()list中搜索對象

相關文章
相關標籤/搜索