這篇文章是關於C++語言的一個新的擴展——標準模板庫的(Standard Template Library),也叫STL。
當我第一次打算寫一篇關於STL的文章的時候,我不得不認可我當時低估了這個話題的深度和廣度。有不少內容要含蓋,也有不少詳細描述STL的書。所以我從新考慮了一下我原來的想法。我爲何要寫這篇文章,又爲何要投稿呢?這會有什麼用呢?有再來一篇關於STL的文章的必要嗎?linux
當我翻開Musser and Saini的頁時,我看到了編程時代在我面前消融。我能看到深夜消失了, 目標軟件工程出現了。我看到了可維護的代碼。一年過去了,我使用STL寫的軟件仍然很容易維護。 讓人吃驚的是其餘人能夠沒有我而維護的很好!ios
然而,我也記得在一開始的時候很難弄懂那些技術術語。一次,我買了Musser&Saini,每件事都依次出現,可是在那之前我最渴望獲得的東西是一些好的例子。程序員
當我開始的時候,做爲C++一部分的Stroustrup還沒出來,它覆蓋了STL。web
所以我想寫一篇關於一個STL程序員的真實生活的文章可能會有用。若是我手上有一些好的例子的話,特別是象這樣的新題目,我會學的更快。算法
另一件事是STL應該很好用。所以,理論上說,咱們應該能夠立刻開始使用STL。編程
什麼是STL呢?STL就是Standard Template Library,標準模板庫。這多是一個歷史上最使人興奮的工具的最無聊的術語。從根本上說,STL是一些「容器」的集合,這些「容器」有list,vector,set,map等,STL也是算法和其餘一些組件的集合。這裏的「容器」和算法的集合指的是世界上不少聰明人不少年的傑做。數組
STL的目的是標準化組件,這樣你就不用從新開發它們了。你能夠僅僅使用這些現成的組件。STL如今是C++的一部分,所以不用額外安裝什麼。它被內建在你的編譯器以內。由於STL的list是一個簡單的容器,因此我打算從它開始介紹STL如何使用。若是你懂得了這個概念,其餘的就都沒有問題了。另外,list容器是至關簡單的,咱們會看到這一點。安全
這篇文章中咱們將會看到如何定義和初始化一個list,計算它的元素的數量,從一個list裏查找元素,刪除元素,和一些其餘的操做。要做到這些,咱們將會討論兩個不一樣的算法,STL通用算法都是能夠操做不止一個容器的,而list的成員函數是list容器專有的操做。app
這是三類主要的STL組件的簡明綱要。STL容器能夠保存對象,內建對象和類對象。它們會安全的保存對象,並定義咱們可以操做的這個對象的接口。放在蛋架上的雞蛋不會滾到桌上。它們很安全。所以,在STL容器中的對象也很安全。我知道這個比喻聽起來很老土,可是它很正確。ide
STL算法是標準算法,咱們能夠把它們應用在那些容器中的對象上。這些算法都有很著名的執行特性。它們能夠給對象排序,刪除它們,給它們記數,比較,找出特殊的對象,把它們合併到另外一個容器中,以及執行其餘有用的操做。
STL iterator就象是容器中指向對象的指針。STL的算法使用iterator在容器上進行操做。Iterator設置算法的邊界 ,容器的長度,和其餘一些事情。舉個例子,有些iterator僅讓算法讀元素,有一些讓算法寫元素,有一些則二者都行。 Iterator也決定在容器中處理的方向。
你能夠經過調用容器的成員函數begin()來獲得一個指向一個容器起始位置的iterator。你能夠調用一個容器的 end() 函數來獲得過去的最後一個值(就是處理停在那的那個值)。
這就是STL全部的東西,容器、算法、和容許算法工做在容器中的元素上的iterator。 算法以合適、標準的方法操做對象,並可經過iterator獲得容器精確的長度。一旦作了這些,它們就在也不會「跑出邊界」。 還有一些其餘的對這些核心組件類型有功能性加強的組件,例如函數對象。咱們將會看到有關這些的例子,如今 ,咱們先來看一看STL的list。
咱們能夠象這樣來定義一個STL的list:
#include <string> #include <list> int main (void) { list<string> Milkshakes; }
這就好了,你已經定義了一個list。簡單嗎?list<string> Milkshakes這句是你聲明瞭list<string>模板類 的一個實例,而後就是實例化這個類的一個對象。可是咱們別急着作這個。在這一步其實你只須要知道你定義了 一個字符串的list。你須要包含提供STL list類的頭文件。我用gcc 2.7.2在個人Linux上編譯這個測試程序,例如:
g++ test1.cpp -otest1
注意iostream.h這個頭文件已經被STL的頭文件放棄了。這就是爲何這個例子中沒有它的緣由。
如今咱們有了一個list,咱們能夠看實使用它來裝東西了。咱們將把一個字符串加到這個list裏。有一個很是 重要的東西叫作list的值類型。值類型就是list中的對象的類型。在這個例子中,這個list的值類型就是字符串,string , 這是由於這個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"); }
We now have a list with four strings in it. The list member function push_back() places an object onto the back of the list. The list member function push_front() puts one on the front. I often push_back() some error messages onto a list, and then push_front() a title on the list so it prints before the error messages. 咱們如今有個4個字符串在list中。list的成員函數push_back()把一個對象放到一個list的後面,而 push_front()把對象放到前面。我一般把一些錯誤信息push_back()到一個list中去,而後push_front()一個標題到list中, 這樣它就會在這個錯誤消息之前打印它了。
知道一個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; }
咱們想要遍歷一個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; } }
這個程序定義了一個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使咱們的軟件更加標準。
使用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()和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; }
The count() algorithm counts the number of objects equal to a certain value. In the above example it checks each integer object in a list against 100. It increments the variable NumberOf100Scores each time a container object equals 100. The output of the program is count()算法統計等於某個值的對象的個數。上面的例子它檢查list中的每一個整型對象是否是100。每次容器中的對象等於100,它就給NumberOf100Scores加1。這是程序的輸出:
There were 2 scores of 100
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()將對該容器的每一個對象調用這個函數。
咱們能夠更進一步的研究一下函數對象。假設咱們須要傳遞更多的信息給一個函數對象。咱們不能經過 調用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的通常操做。若是你懂了這些基本原理,你就能夠毫無疑問的使用STL了 建議你做一些練習。咱們如今用一些更加複雜的操做來擴展咱們的知識,包括list成員函數和STL通用算法。
咱們如何在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()的值,要是找到了就返回一個指着找到的對象的iterator
這是find()的一個更強大的版本。這個例子演示了find_if(),它接收一個函數對象的參數做爲參數, 並使用它來作更復雜的評價對象是否和給出的查找條件相付。
假設咱們的list中有一些按年代排列的包含了事件和日期的記錄。咱們但願找出發生在1997年的事件。
/* || How to find things in an STL list MkII */ #include <string> #include <list> #include <algorithm> # class EventIsIn1997 { public: bool operator () (string& EventRecord) { // year field is at position 12 for 4 characters in EventRecord return EventRecord.substr(12,4)=="1997"; } }; # int main (void) { list<string> Events; # // string positions 0123456789012345678901234567890123456789012345 Events.push_back("07 January 1995 Draft plan of house prepared"); Events.push_back("07 February 1996 Detailed plan of house prepared"); Events.push_back("10 January 1997 Client agrees to job"); Events.push_back("15 January 1997 Builder starts work on bedroom"); Events.push_back("30 April 1997 Builder finishes work"); # list<string>::iterator EventIterator = find_if (Events.begin(), Events.end(), EventIsIn1997()); # // find_if completes the first time EventIsIn1997()() returns true // for any object. It returns an iterator to that object which we // can dereference to get the object, or if EventIsIn1997()() never // returned true, find_if returns end() if (EventIterator==Events.end()) { cout << "Event not found in list" << endl; } else { cout << *EventIterator << endl; } }
這是程序的輸出:
10 January 1997 Client agrees to job
一些字符在STL容器中很好處理,讓咱們看一看一個難處理的字符序列。咱們將定義一個list來放字符。
list<char> Characters;
如今咱們有了一個字符序列,它不用任何幫助就知道而後管理內存。它知道它是從哪裏開始、到哪裏結束。 它很是有用。我不知道我是否說過以null結尾的字符數組。
讓咱們加入一些咱們喜歡的字符到這個list中:
Characters.push_back('\0');
Characters.push_back('\0');
Characters.push_back('1');
Characters.push_back('2');
咱們將獲得多少個空字符呢?
int NumberOfNullCharacters(0); count(Characters.begin(), Characters.end(), '\0', NumberOfNullCharacters); cout << "We have " << NumberOfNullCharacters << endl;
讓咱們找字符'1'
list<char>::iterator Iter; Iter = find(Characters.begin(), Characters.end(), '1'); cout << "We found " << *Iter << endl;
這個例子演示了STL容器容許你以更標準的方法來處理空字符。如今讓咱們用STL的search算法來搜索容器中 的兩個null。
就象你猜的同樣,STL通用算法search()用來搜索一個容器,可是是搜索一個元素串,不象find()和find_if() 只搜索單個的元素。
/* || How to use the search algorithm in an STL list */ #include <string> #include <list> #include <algorithm> # int main ( void ) { # list<char> TargetCharacters; list<char> ListOfCharacters; # TargetCharacters.push_back('\0'); TargetCharacters.push_back('\0'); # ListOfCharacters.push_back('1'); ListOfCharacters.push_back('2'); ListOfCharacters.push_back('\0'); ListOfCharacters.push_back('\0'); # list<char>::iterator PositionOfNulls = search(ListOfCharacters.begin(), ListOfCharacters.end(), TargetCharacters.begin(), TargetCharacters.end()); # if (PositionOfNulls!=ListOfCharacters.end()) cout << "We found the nulls" << endl; }
The output of the program will be 這是程序的輸出:
We found the nulls
search算法在一個序列中找另外一個序列的第一次出現的位置。在這個例子裏咱們在ListOfCharacters中 找TargetCharacters這個序列的第一次出現,TargetCharacters是包含兩個null字符的序列。
search的參數是兩個指着查找目標的iterator和兩個指着搜索範圍的iterators。 所以咱們咱們在整個的ListOfCharacters的範圍內查找TargetCharacters這個list的整個序列。
若是TargetCharacters被發現,search就會返回一個指着ListOfCharacters中序列匹配的第一個 字符的iterator。若是沒有找到匹配項,search返回ListOfCharacters.end()的值。
要排序一個list,咱們要用list的成員函數sort(),而不是通用算法sort()。全部咱們用過的算法都是 通用算法。然而,在STL中有時容器支持它本身對一個特殊算法的實現,這一般是爲了提升性能。
在這個例子中,list容器有它本身的sort算法,這是由於通用算法僅能爲那些提供隨機存取裏面元素 的容器排序,而因爲list是做爲一個鏈接的鏈表實現的,它不支持對它裏面的元素隨機存取。因此就須要一個特殊的 sort()成員函數來排序list。
因爲各類緣由,容器在性能須要較高或有特殊效果需求的場合支持外部函數(extra functions), 這經過利用構造函數的結構特性能夠做到。
/* || How to sort an STL list */ #include <string> #include <list> #include <algorithm> # PrintIt (string& StringToPrint) { cout << StringToPrint << endl;} # int main (void) { list<string> Staff; list<string>::iterator PeopleIterator; # Staff.push_back("John"); Staff.push_back("Bill"); Staff.push_back("Tony"); Staff.push_back("Fidel"); Staff.push_back("Nelson"); # cout << "The unsorted list " << endl; for_each(Staff.begin(), Staff.end(), PrintIt ); # Staff.sort(); # cout << "The sorted list " << endl; for_each(Staff.begin(), Staff.end(), PrintIt); }
輸出是:
The unsorted list John Bill Tony Fidel Nelson The sorted list Bill Fidel John Nelson Tony
list的成員函數push_front()和push_back()分別把元素加入到list的前面和後面。你可使用insert() 把對象插入到list中的任何地方。
insert()能夠加入一個對象,一個對象的若干份拷貝,或者一個範圍之內的對象。這裏是一些 插入對象到list中的例子:
/* || Using insert to insert elements into a list. */ #include <list> # int main (void) { list<int> list1; # /* || Put integers 0 to 9 in the list */ for (int i = 0; i < 10; ++i) list1.push_back(i); # /* || Insert -1 using the insert member function || Our list will contain -1,0,1,2,3,4,5,6,7,8,9 */ list1.insert(list1.begin(), -1); # /* || Insert an element at the end using insert || Our list will contain -1,0,1,2,3,4,5,6,7,8,9,10 */ list1.insert(list1.end(), 10); # /* || Inserting a range from another container || Our list will contain -1,0,1,2,3,4,5,6,7,8,9,10,11,12 */ int IntArray[2] = {11,12}; list1.insert(list1.end(), &IntArray[0], &IntArray[2]); # /* || As an exercise put the code in here to print the lists! || Hint: use PrintIt and accept an interger */ }
注意,insert()函數把一個或若干個元素插入到你指出的iterator的位置。你的元素將出如今 iterator指出的位置之前。
咱們已經象這樣定義了list:
list<int> Fred;
你也能夠象這樣定義一個list,並同時初始化它的元素:
// define a list of 10 elements and initialise them all to 0 list<int> Fred(10, 0); // list now contains 0,0,0,0,0,0,0,0,0,0
或者你能夠定義一個list並用另外一個STL容器的一個範圍來初始化它,這個STL容器不必定是一個list, 僅僅須要是元素類型相同的的容器就能夠。
vector<int> Harry; Harry.push_back(1); Harry.push_back(2); # // define a list and initialise it with the elements in Harry list<int> Bill(Harry.begin(), Harry.end()); // Bill now contains 1,2
list成員函數pop_front()刪掉list中的第一個元素,pop_back()刪掉最後一個元素。 函數erase()刪掉由一個iterator指出的元素。還有另外一個erase()函數能夠刪掉一個範圍的元素。
/* || Erasing objects from a list */ #include <list> # int main (void) { list<int> list1; // define a list of integers # /* || Put some numbers in the list || It now contains 0,1,2,3,4,5,6,7,8,9 */ for (int i = 0; i < 10; ++i) list1.push_back(i); # list1.pop_front(); // erase the first element 0 # list1.pop_back(); // erase the last element 9 # list1.erase(list1.begin()); // erase the first element (1) using an iterator # list1.erase(list1.begin(), list1.end()); // erase all the remaining elements # cout << "list contains " << list1.size() << " elements" << endl; }
輸出是:
list contains 0 elements
list的成員函數remove()用來從list中刪除元素。
/* || Using the list member function remove to remove elements */ #include <string> #include <list> #include <algorithm> # PrintIt (const string& StringToPrint) { cout << StringToPrint << endl; } # int main (void) { list<string> Birds; # Birds.push_back("cockatoo"); Birds.push_back("galah"); Birds.push_back("cockatoo"); Birds.push_back("rosella"); Birds.push_back("corella"); # cout << "Original list with cockatoos" << endl; for_each(Birds.begin(), Birds.end(), PrintIt); # Birds.remove("cockatoo"); # cout << "Now no cockatoos" << endl; for_each(Birds.begin(), Birds.end(), PrintIt); }
輸出是:
Original list with cockatoos cockatoo galah cockatoo rosella corella Now no cockatoos galah rosella corella
通用算法remove()使用和list的成員函數不一樣的方式工做。通常狀況下不改變容器的大小。
/* || Using the generic remove algorithm to remove list elements */ #include <string> #include <list> #include <algorithm> # PrintIt(string& AString) { cout << AString << endl; } # int main (void) { list<string> Birds; list<string>::iterator NewEnd; # Birds.push_back("cockatoo"); Birds.push_back("galah"); Birds.push_back("cockatoo"); Birds.push_back("rosella"); Birds.push_back("king parrot"); # cout << "Original list" << endl; for_each(Birds.begin(), Birds.end(), PrintIt); # NewEnd = remove(Birds.begin(), Birds.end(), "cockatoo"); # cout << endl << "List according to new past the end iterator" << endl; for_each(Birds.begin(), NewEnd, PrintIt); # cout << endl << "Original list now. Care required!" << endl; for_each(Birds.begin(), Birds.end(), PrintIt); }
The output will be
Original list cockatoo galah cockatoo rosella king parrot List according to new past the end iterator galah rosella king parrot Original list now. Care required! galah rosella king parrot rosella king parrot
通用remove()算法返回一個指向新的list的結尾的iterator。從開始到這個新的結尾(不含新結尾元素)的範圍 包含了remove後剩下全部元素。你能夠用list成員函數erase函數來刪除重新結尾到老結尾的部分。
咱們將完成一個稍微有點複雜的例子。它演示STL通用算法stable_partition()算法和一個list成員函數 splice()的變化。注意函數對象的使用和沒有使用循環。 經過簡單的語句調用STL算法來控制。
stable_partition()是一個有趣的函數。它從新排列元素,使得知足指定條件的元素排在 不知足條件的元素前面。它維持着兩組元素的順序關係。
splice 把另外一個list中的元素結合到一個list中。它從源list中刪除元素。
在這個例子中,咱們想從命令行接收一些標誌和四個文件名。文件名必須’按順序出現。經過使用stable_partition() 咱們能夠接收和文件名混爲任何位置的標誌,而且不打亂文件名的順序就把它們放到一塊兒。
因爲記數和查找算法都很易用,咱們調用這些算法來決定哪一個標誌被設置而哪一個標誌未被設置。 我發現容器用來管理少許的象這樣的動態數據。
/* || Using the STL stable_partition algorithm || Takes any number of flags on the command line and || four filenames in order. */ #include <string> #include <list> #include <algorithm> # PrintIt ( string& AString ) { cout << AString << endl; } # class IsAFlag { public: bool operator () (string& PossibleFlag) { return PossibleFlag.substr(0,1)=="-"; } }; # class IsAFileName { public: bool operator () (string& StringToCheck) { return !IsAFlag()(StringToCheck); } }; # class IsHelpFlag { public: bool operator () (string& PossibleHelpFlag) { return PossibleHelpFlag=="-h"; } }; # int main (int argc, char *argv[]) { # list<string> CmdLineParameters; // the command line parameters list<string>::iterator StartOfFiles; // start of filenames list<string> Flags; // list of flags list<string> FileNames; // list of filenames # for (int i = 0; i < argc; ++i) CmdLineParameters.push_back(argv[i]); # CmdLineParameters.pop_front(); // we don't want the program name # // make sure we have the four mandatory file names int NumberOfFiles(0); count_if(CmdLineParameters.begin(), CmdLineParameters.end(), IsAFileName(), NumberOfFiles); # cout << "The " << (NumberOfFiles == 4 ? "correct " : "wrong ") << "number (" << NumberOfFiles << ") of file names were specified" << endl; # // move any flags to the beginning StartOfFiles = stable_partition(CmdLineParameters.begin(), CmdLineParameters.end(), IsAFlag()); # cout << "Command line parameters after stable partition" << endl; for_each(CmdLineParameters.begin(), CmdLineParameters.end(), PrintIt); # // Splice any flags from the original CmdLineParameters list into Flags list. Flags.splice(Flags.begin(), CmdLineParameters, CmdLineParameters.begin(), StartOfFiles); # if (!Flags.empty()) { cout << "Flags specified were:" << endl; for_each(Flags.begin(), Flags.end(), PrintIt); } else { cout << "No flags were specified" << endl; } # // parameters list now contains only filenames. Splice them into FileNames list. FileNames.splice(FileNames.begin(), CmdLineParameters, CmdLineParameters.begin(), CmdLineParameters.end()); # if (!FileNames.empty()) { cout << "Files specified (in order) were:" << endl; for_each(FileNames.begin(), FileNames.end(), PrintIt); } else { cout << "No files were specified" << endl; } # // check if the help flag was specified if (find_if(Flags.begin(), Flags.end(), IsHelpFlag())!=Flags.end()) { cout << "The help flag was specified" << endl; } # // open the files and do whatever you do # }
給出這樣的命令行:
test17 -w linux -o is -w great
輸出是:
The wrong number (3) of file names were specified Command line parameters after stable partition -w -o -w linux is great Flags specified were: -w -o -w Files specified (in order) were: linux is great
咱們僅僅簡單的談了談你能夠用list作的事情。咱們沒有說明一個對象的用戶定義類,雖然這個不難。
若是你懂了剛纔說的這些算法背後的概念,那麼你使用剩下的那些算法就應該沒有問題了。使用STL 最重要的東西就是獲得基本理論。
STL的關鍵其實是iterator。STL算法做爲參數使用iterator,他們指出一個範圍,有時是一個範圍, 有時是兩個。STL容器支持iterator,這就是爲何咱們說 list<int>::iterator, 或 list<char>::iterator, 或 list<string>::iterator.
iterator有很好的定義繼承性。它們很是有用。某些iterator僅支持對一個容器只讀,某些 僅支持寫,還有一些僅能向前指,有一些是雙向的。有一些iterator支持對一個容器的隨機存取。
STL算法須要某個iterator做爲「動力」 若是一個容器不提供iterator做爲「動力」,那麼這個算法將沒法編譯。例如,list容器僅提供雙向的 iterator。一般的sort()算法須要隨機存取的iterator。這就是爲何咱們須要一個特別的list成員函數sort()。
要合適的實際使用STL,你須要仔細學習各類不一樣的iterator。你須要知道每種容器都支持那類iterator。 你還須要知道算法須要那種iterator,你固然也須要懂得你能夠有那種iterator。
去年,我曾用STL寫過幾個商業程序。它在不少方面減小了個人工做量,也排除了不少邏輯錯誤。
最大的一個程序有大約5000行。可能最驚人的事情就是它的速度。它讀入並處理一個1-2兆的 報告文件僅花大約20秒。我是在linux上用gcc2.7.2開發的,如今運行在HP-UX機器上。 它一共用了大約50和函數對象和不少容器,這些容器的大小從小list到一個有14,000個元素的map都有。
一個程序中的函數對象是處於一個繼承樹中,頂層的函數對象調用低層的函數對象。我大量的使用STL算法for_each() ,find(),find_if(),count()和count_if(),我儘可能減小使用程序內部的函數,而使用STL的算法調用。
STL傾向於自動的把代碼組織成清晰的控制和支持模塊。經過當心使用函數對象並給它們 起有意義的名字,我使它們在個人軟件的控制流中流動。
還有不少關於STL編程要知道的東西,我但願你經過這些例子能夠愉快的工做。
參考數目中的兩本書在web上都有勘誤表,你能夠本身改正它們。
Stroustrup在每一章後面都有個建議欄,特別是對於出學者有用。正本書比早期的版本更加健談。 它也更大了。書店裏還能夠找到其餘幾本關於STL的教科書。去看看,也許你能發現什麼。
The STL Tutorial and Reference Guide, David Musser and Atul Saini. Addison Wesley 1996. 《STL教程和參考手冊》
The C++ Programming Language 3e, Bjarne Stroustrup. Addison Wesley 1997. <===================================================================>