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

post0發表於2007-08-11
STL 簡介,標準模板庫(轉)[@more@]

這篇文章是關於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在我的Linux上編譯這個測試程式,例如:

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 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中, 這樣它就會在這個錯誤訊息以前列印它了。

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

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 << "There were no messages " << endl;

}

#

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 << *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的通用演算法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 << StringToPrint << endl;

}

#

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 << "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

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

用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 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

#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 "

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

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

使用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

#include

#include

#

int main (void) {

list Fruit;

list::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

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

使用STL通用演算法find_if()在list中搜尋物件

這是find()的一個更強大的版本。這個例子演示了find_if(),它接收一個函式物件的引數作為引數, 並使用它來做更復雜的評價物件是否和給出的查詢條件相付。

假設我們的list中有一些按年代排列的包含了事件和日期的記錄。我們希望找出發生在1997年的事件。

/*

|| How to find things in an STL list MkII

*/

#include

#include

#include

#

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 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::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通用演算法search在list中找一個序列

一些字元在STL容器中很好處理,讓我們看一看一個難處理的字元序列。我們將定義一個list來放字元。

list Characters;

現在我們有了一個字元序列,它不用任何幫助就知道然後管理記憶體。它知道它是從哪裡開始、到哪裡結束。 它非常有用。我不知道我是否說過以null結尾的字元陣列。

讓我們加入一些我們喜歡的字元到這個list中:

Characters.push_back('');

Characters.push_back('');

Characters.push_back('1');

Characters.push_back('2');

我們將得到多少個空字元呢?

int NumberOfNullCharacters(0);

count(Characters.begin(), Characters.end(), '', NumberOfNullCharacters);

cout << "We have " << NumberOfNullCharacters << endl;

讓我們找字元'1'

list::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

#include

#include

#

int main ( void {

#

list TargetCharacters;

list ListOfCharacters;

#

TargetCharacters.push_back('');

TargetCharacters.push_back('');

#

ListOfCharacters.push_back('1');

ListOfCharacters.push_back('2');

ListOfCharacters.push_back('');

ListOfCharacters.push_back('');

#

list::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的成員函式sort()排序一個list。

要排序一個list,我們要用list的成員函式sort(),而不是通用演算法sort()。所有我們用過的演算法都是 通用演算法。然而,在STL中有時容器支援它自己對一個特殊演算法的實現,這通常是為了提高效能。

在這個例子中,list容器有它自己的sort演算法,這是因為通用演算法僅能為那些提供隨機存取裡面元素 的容器排序,而由於list是作為一個連線的連結串列實現的,它不支援對它裡面的元素隨機存取。所以就需要一個特殊的 sort()成員函式來排序list。

由於各種原因,容器在效能需要較高或有特殊效果需求的場合支援外部函式(extra functions), 這透過利用建構函式的結構特性可以作到。

/*

|| How to sort an STL list

*/

#include

#include

#include

#

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

#

int main (void) {

list Staff;

list::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的成員函式插入元素到list中

list的成員函式push_front()和push_back()分別把元素加入到list的前面和後面。你可以使用insert() 把物件插入到list中的任何地方。

insert()可以加入一個物件,一個物件的若干份複製,或者一個範圍以內的物件。這裡是一些 插入物件到list中的例子:

/*

|| Using insert to insert elements into a list.

*/

#include

#

int main (void) {

list 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:

list Fred;

你也可以象這樣定義一個list,並同時初始化它的元素:

// define a list of 10 elements and initialise them all to 0

list Fred(10, 0);

// list now contains 0,0,0,0,0,0,0,0,0,0

或者你可以定義一個list並用另一個STL容器的一個範圍來初始化它,這個STL容器不一定是一個list, 僅僅需要是元素型別相同的的容器就可以。

vector Harry;

Harry.push_back(1);

Harry.push_back(2);

#

// define a list and initialise it with the elements in Harry

list Bill(Harry.begin(), Harry.end());

// Bill now contains 1,2

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

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

list成員函式pop_front()刪掉list中的第一個元素,pop_back()刪掉最後一個元素。 函式erase()刪掉由一個iterator指出的元素。還有另一個erase()函式可以刪掉一個範圍的元素。

/*

|| Erasing objects from a list

*/

#include

#

int main (void) {

list 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中刪除元素。

list的成員函式remove()用來從list中刪除元素。

/*

|| Using the list member function remove to remove elements

*/

#include

#include

#include

#

PrintIt (const string& StringToPrint) {

cout << StringToPrint << endl;

}

#

int main (void) {

list 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

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

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

通用演算法remove()使用和list的成員函式不同的方式工作。一般情況下不改變容器的大小。

/*

|| Using the generic remove algorithm to remove list elements

*/

#include

#include

#include

#

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

#

int main (void) {

list Birds;

list::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()來劃分一個list

我們將完成一個稍微有點複雜的例子。它演示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

#include

#include

#

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 CmdLineParameters; // the command line parameters

list::iterator StartOfFiles; // start of filenames

list Flags; // list of flags

list 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::iterator, 或 list< char>::iterator, 或 list::iterator.

iterator有很好的定義繼承性。它們非常有用。某些iterator僅支援對一個容器只讀,某些 僅支援寫,還有一些僅能向前指,有一些是雙向的。有一些iterator支援對一個容器的隨機存取。

STL演算法需要某個iterator作為“動力” 如果一個容器不提供iterator作為“動力”,那麼這個演算法將無法編譯。例如, list容器僅提供雙向的 iterator。通常的sort()演算法需要隨機存取的iterator。這就是為什麼我們需要一個特別的list成員函式 sort()。

要合適的實際使用STL,你需要仔細學習各種不同的iterator。你需要知道每種容器都支援那類iterator。 你還需要知道演算法需要那種iterator,你當然也需要懂得你可以有那種iterator。

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

在field中使用STL

去年,我曾用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.

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

相關文章