STL 簡介,標準模板庫[1] (轉)

amyz發表於2007-08-16
STL 簡介,標準模板庫[1] (轉)[@more@]STL 簡介,標準模板庫(轉貼)


STL 簡介,標準模板庫 

作者:tt Field

  這篇文章是關於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++的一部分,因此不用額外什麼。它被內建在你的之內。因為STL的list是一個簡單的容器,所以我打算從它開始介紹STL如何使用。如果你懂得了這個概念,其他的就都沒有問題了。另外,list容器是相當簡單的,我們會看到這一點。 

  這篇文章中我們將會看到如何定義和初始化一個list,計算它的元素的數量,從一個list裡查詢元素,刪除元素,和一些其他的操作。要作到這些,我們將會討論兩個不同的演算法,STL通用演算法都是可以操作不止一個容器的,而list的成員是list容器專有的操作。 

 這是三類主要的STL元件的簡明綱要。STL容器可以儲存,內建物件和類物件。它們會的儲存物件,並定義我們能夠操作的這個物件的介面。放在蛋架上的雞蛋不會滾到桌上。它們很安全。因此,在STL容器中的物件也很安全。我知道這個比喻聽起來很老土,但是它很正確。 

  STL演算法是標準演算法,我們可以把它們應用在那些容器中的物件上。這些演算法都有很著名的特性。它們可以給物件排序,刪除它們,給它們記數,比較,找出特殊的物件,把它們合併到另一個容器中,以及執行其他有用的操作。 

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

  你可以透過容器的成員函式begin()來得到一個指向一個容器起始位置的iterator。你可以呼叫一個容器的 end() 函式來得到過去的最後一個值(就是處理停在那的那個值)。 

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



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

定義一個list
我們可以象這樣來定義一個STL的list: 
 
#include 
#include 
int main (void) {
 list Milkshakes;
}
  這就行了,你已經定義了一個list。簡單嗎?list Milkshakes這句是你宣告瞭list模板類 的一個例項,然後就是例項化這個類的一個物件。但是我們別急著做這個。在這一步其實你只需要知道你定義了 一個字串的list。你需要包含提供STL list類的頭。我用gcc 2.7.2在我的上編譯這個測試程式,例如: 
g++ test1.cpp -otest1
  注意iostream.h這個標頭檔案已經被STL的標頭檔案放棄了。這就是為什麼這個例子中沒有它的原因。 
  現在我們有了一個list,我們可以看實使用它來裝東西了。我們將把一個字串加到這個list裡。有一個非常 重要的東西叫做list的值型別。值型別就是list中的物件的型別。在這個例子中,這個list的值型別就是字串,string , 這是因為這個list用來放字串。 



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

I使用list的成員函式push_back和push_front插入一個元素到list中
 
#include 
#include 
#
int main (void) {
 list 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  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中, 這樣它就會在這個錯誤訊息以前列印它了。 


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

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

這是我的意思: 

 
/*
|| Using a list to track and report program messages and status 
*/
#include 
#include 
#include 
#
int main (void) {
 #define OK 0 
 #define INFO 1
 #define WARNING 2
#
 int return_code;
#
 list 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 < }
#
 return return_code;
}

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

用for迴圈來處理list中的元素
 我們想要遍歷一個list,比如列印一箇中的所有物件來看看list上不同操作的結果。要一個元素一個元素的遍歷一個list, 我們可以這樣做: 
/*
|| How to print the contents of a simple STL list. Whew! 
*/
#include 
#include 
#include 
#
int main (void) {
list Milkshakes;
list::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 < } 
}
 這個程式定義了一個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的通用演算法for_each來處理list中的元素
 使用STL list和 iterator,我們要初始化、比較和給iterator增量來遍歷這個容器。STL通用的for_each 演算法能夠減輕我們的工作。 
 
/*
|| How to print a simple STL list MkII
*/
#include 
#include 
#include 
#include 
#
PrintIt (string& StringToPrint) {
 cout <}
#
int main (void) {
 list 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 
#include 
#
int main (void) {
 list 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 <}
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

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

用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 
#include 
#include 
#
const string ToothbrushCode("0003");
#
class IsAToothbrush {
public: 
bool operator() ( string& SalesRecord ) {
 return SalesRecord.substr(0,4)==ToothbrushCode;
 } 
};
#
int main (void) {
 list SalesRecords;
#
 SalesRecords.push_back("0001 p");
 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 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 
#include 
#include 
#include 
#
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 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 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通用演算法。 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-962493/,如需轉載,請註明出處,否則將追究法律責任。

相關文章