標準模板庫(STL)介紹【轉貼】

標準模板庫(STL)介紹

大概是一月份在winter的blog看到他翻譯的標準模板(STL)介紹(上),當時看了前面的五六段,感覺不少地方怪怪的,於是留了“...發現這個翻譯實在糟糕”的回覆,最近重又看到 winter的這篇文章,重新對比着winter貼出的英文和其譯文,發現硬傷的確不少,如past-the-end的翻譯,或許是winter的一時疏忽吧。由此對譯文並不滿意,於是決定自己譯一遍,當然看winter的譯文在先,所以必有不少部分基於winter的譯文,在此表示感謝,還有介紹了這樣一篇不錯的STL好文(至少對初學者而言)。

文中的代碼用Vim+InkPot(Vim Themes)+TOhtml(Vim命令)轉換得到。

原文 http://linuxgazette.net/issue34/field.html
版權信息:
Copyright ? 1998, Scott Field
Published in Issue 34 of Linux Gazette, November 1998

標準模板庫(STL)介紹
Introduction to STL, Standard Template Library

By Scott Field


本文介紹了C++語言一個新的擴展,標準模板庫,也即STL。

最初打算寫篇關於STL的文章時,我得承認當時有些低估了這個主題的深度和廣度。畢竟STL涉及了衆多內容,而市面上也有許多詳細描述STL的書。因此我重新審視和思考了自己最初的想法。爲什麼我還要寫篇這樣的文章,我又能提供些什麼?哪些是有用的?有必要再來一篇STL文章嗎?

當我翻開Musser和Saini的書時,我看到編程時間在自己面前分解(programming time dissolving in front of me),我看到黑夜消逝去而軟件項目又重回目標,我看到了可維護的代碼。現在已經過去了一年,我用STL編寫的軟件維護起來仍非常容易。令人驚恐啊,缺了我,別人可以維護的一樣好!(注:作者用了shock horror,大概是想突出STL寫的代碼之可維護性。)

然而,我還依稀記得一開始面對那些技術術語時有多困難。直到買了Musser&Saini一書後,一切才清晰明朗起來,不過在那之前着實苦苦掙扎了一番,我渴望着能找到一些好的例子。

此外,我開始學習STL時,把STL作爲C++一部分來描述的第三版Stroustrup還未出版。

因此我想,爲STL新手們寫篇關於STL在實際編程中的使用或許有些用處。要是手頭有些好的例子,我總能學得更快,尤其是對於類似STL這樣的新事物。

另外,STL應該易於使用,因此理論上我們應能馬上開始使用STL。

什麼是STL?STL表示Standard Template Library,即標準模板庫。相對這個史上最令人興奮的工具之一,這個名字也可能是史上最單調的術語之一。STL主要由一簇容器——list, verctor,set,map等等和一簇算法及其它組件組成。這“一簇容器和算法”可是許多世上最聰明的人多年探索的結晶。

STL的目的在於標準化經常使用的組件,這樣一來人們就不必重新發明輪子了,可以直接使用現成的標準STL組件。現在STL已是C++的一部分,因此不用再到處蒐集,安裝,它已經內建在你的編譯器中。STL list是較爲簡單的容器之一,我想從它入手應該能很好的說明STL的實際用法。如果理解了list相關的那些概念,其它容器也就不在話下了。此外,正如我們將看到的,即使是一個簡單的list容器,所涉及的內容已多的嚇人。

本文描述了:如何定義和初始化list,如何對其元素進行計數,怎樣在list中查找、刪除元素,及其它非常有用的操作。爲完成這些操作,我們將論及兩種不同的算法:作用於多種容器的STL泛型算法和只作用於list容器的list成員函數。

爲消除不必要的疑惑,這兒先對三種主要的STL組件作一個扼要介紹。STL容器持有對象,包括內建對象(built in object)和類對象(class object),並且保證這些對象的安全,此外還定義了標準接口,通過它我們可以操作這些對象。放在蛋託(egg container)裏的雞蛋不會滾到廚桌上,它們很安全。STL容器(STL container)裏的對象也是如此,它們也一樣安全無虞。我知道這比喻夠老土,不過蠻貼切。

STL算法是我們能應用到容器內對象上的標準算法。這些算法的執行性能卓著,能對容器內對象進行排序、刪除、計數、比較,以及查找某個特定對象,把對象合併到另一個容器內,還能進行很多其它很有用的操作。

STL迭代器類似於指向容器內對象的指針,STL算法就是藉助迭代器在容器上進行操作的。迭代器爲算法設定了界限,依據的是容器的範圍和其它途徑,比如有些迭代器只允許算法讀取元素,有些則允許算法寫元素,而另外一些則讀寫皆可。迭代器還決定了在容器內操作的方向。

調用容器的成員函數begin()可以取得指向該容器內第一個元素的iterator,而調用容器的end()函數則可得到最後元素的下一個位置(在此停止處理)(past the end)。

STL的全部內容差不多也就是,容器、算法和允許算法作用於容器內元素上的迭代器。這些算法以可測量的、標準的方法操作對象,並藉助迭代器掌握容器的確切範圍。一旦做到這點,這些算法就決不會“亡命天涯”(run off the edge 越出邊界)。還有其它一些組件能增強這些核心組件類型的功能,比如函數對象。我們也會論及一些函數對象相關的例子。眼下,開始咱們的STL list之旅吧。



定義一個list

如下方法可以定義一個STL list:




main () {
list<string> Milkshakes;
}

就這樣,我們已經定義了一個list。夠簡單的吧?通過list Milkshakes這一句,我們先是實例化(instantiated)了模板類list,隨後創建了該型別的(注:即實例化後的模板類)對象。不過先別拘泥於這些,現階段你只需知道這樣就定義了一個字符串list(a list of strings)。你還需要包含提供了STL list類的頭文件。我在自己的Linux系統上用GCC 2.7.2編譯了這些測試程序,如下:


g++ test1.cpp -otest1

注意頭文件iostream.h已被包含在上面其中一個STL頭文件內,這就是有些例子不見其蹤影的原因。(注:不過良好的編程習慣應該是明確的包含需要的頭文件,而不應該依賴其它頭文件包含了所需的頭文件;畢竟不是所有的編譯器實現的都一樣。此外應該使用標準C++的頭文件,而不是,在最後一句#include之後,應加上using namespace std;,以下例子同)

好了,我們已經有了一個list,可以開始用它來持有東西了。我們將向這個list添加一些字符串。有個很重要的概念,即這個list的值型別(value type),value type指的是該list所持有的對象之型別。本例中,這個list的value type是string(字符串),因爲該list持有strings(字符串)。



用list成員函數push_back和push_front插入元素到list中




main () {
list<string> Milkshakes;
Milkshakes.push_back();
Milkshakes.push_back();
Milkshakes.push_front();
Milkshakes.push_front();
}
現在我們得到一個含有四個字符串的list。list成員函數push_back()把一個對象放置到list的尾部,而list成員函數 push_front()則把對象放置在list的頭部。我常用push_back()插入一些出錯消息到list中,然後用push_front()插入一個標題到list中,以便在出錯消息前先打印該標題。



list成員函數empty()

獲知一個list是否爲空很有用。如果一個list爲空,則list成員函數empty()返回true。Empty是個看似簡單的概念。我常以如下方式使用之。在一個程序中,我會從頭到尾的使用push_back()把出錯消息放到list中。隨後通過調用empty()我便能知道該程序是否有報錯。如果我爲指示性信息(informational messages)、警告和嚴重錯誤三者分別定義一個list,那麼我只用empty()就能輕而易舉的知道出現了哪種錯誤。

我可以在程序整個運行過程中填充這些lists,並在輸出這些lists之前,加個標題讓它們變得漂亮些,或將它們排序歸類。

下面就是我的做法:









main () {




return_code;

list<string> InfoMessages;
list&lt:string> WarningMessages;


InfoMessages.push_back();

WarningMessages.push_back();


return_code = OK;

(!InfoMessages.empty()) {
InfoMessages.push_front();

return_code = INFO;
}

(!WarningMessages.empty()) {
WarningMessages.push_front();

return_code = WARNING;
}


(InfoMessages.empty() && WarningMessages.empty()) {
cout << << endl;
}

return_code;
}




用for循環處理list中的元素

我們總想要迭代遍歷list以便,例如打印list中的所有對象,來查看在list上施加了各種不同操作後的實際效果。要一個元素一個元素的迭代遍歷list,可按如下方法做:








main () {
list<string> Milkshakes;
list<string>::iterator MilkshakeIterator;

Milkshakes.push_back();
Milkshakes.push_back();
Milkshakes.push_front();
Milkshakes.push_front();


Milkshakes.push_front();
Milkshakes.push_back();
(MilkshakeIterator=Milkshakes.begin();
MilkshakeIterator!=Milkshakes.end();
++MilkshakeIterator) {

cout << *MilkshakeIterator << endl;
}
}

本例程中,我們定義了一個iterator,MilkshakeIterator,並將其指向list的第一個元素。只要調用 Milkshakes.begin()即可,該函數返回指向list頭的iterator。然後拿MilkshakeIterator和list的 Milkshakes.end()返回值進行比較,當這兩個值相等時退出循環。

容器的成員函數end()返回一個iterator,它指向容器最後元素的下一個位置。到達此位置後,我們便停止處理,並且不能提領(dereference)由容器的end()函數返回的iterator。只要記住,這個位置表示我們已經處於容器最後元素的下一個位置,應該停止處理元素。所有STL容器都遵循這一點。

在上面的例子裏,每次進入for循環之後,我們就提領這個iterator取得其指向的字符串,並將其打印輸出。

在STL編程中,每個算法都會用到一個或多個iterators,我們可以用它們訪問容器內的對象。將iterator指向需要訪問的對象,然後提領這個iterator,我們便可以訪問這個指定對象。

list容器不支持給list iterator加個數就能跳到容器內的另一個對象處,記住這一點。也就是說,我們無法讓Milkshakes.begin()+2指向list中的第三個對象,因爲STL list內部是以雙向鏈表(a double linked list)結構實現的,而雙向鏈表並不支持隨機存取。不過STL的vector和deque容器支持隨機存取。

上面的例程會打印輸出list的全部內容。任何讀過該例程的人都能立即明白其工作流程,它使用了標準iterators和一個標準list容器。這個例程裏頭並沒有多少程序員個人的東西,或者自個兒打造的list實現,不過是標準C++而已。這可是一大進步。即使STL的這一簡單用法就可令我們的軟件更加標準。



用STL泛型算法for_each處理list中的元素

即使用了STL list和iterator,爲了迭代遍歷一個容器,我們還是得對iterator賦初值、檢測並加一(注:指for(...)中的部分)。而STL泛型算法for_each則能讓我們免於這些雜務。










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

main () {
list<string> FruitAndVegetables;
FruitAndVegetables.push_back();
FruitAndVegetables.push_back();
FruitAndVegetables.push_back();
FruitAndVegetables.push_front();
FruitAndVegetables.push_front();

for_each (FruitAndVegetables.begin(), FruitAndVegetables.end(), PrintIt);
}
這個例程中,我們使用了STL泛型算法for_each()來迭代遍歷一個iterator區間(range),並對每個對象調用函數PrintIt ()。我們不再需要給任何iterator賦初值、檢測或加一,for_each()讓我們的代碼具備了很好的模塊化。我們要給對象施加的操作已經很好的打包到一個函數裏,並且除掉了那個循環,現在我們的代碼變得更清晰了。

for_each算法引入了一個新概念,iterator區間(an iterator range),它由起始iterator和結束iterator(end iterator)界定。起始iterator確定從何處開始處理,而end iterator表示何處應停止處理,但並不包含在該區間內。(注:是一個半閉半開的區間,[ ... ))



用STL泛型算法count()對list內元素進行計數

STL泛型算法count()和count_if()對容器內對象的出現(occurrences of objects)進行計數。和for_each()一樣,count()和count_if()算法也要給定一個iterator range。

讓我們來算一下學生考試成績list中最好成績的數量,該list的value type是int。








main () {
list<> Scores;

Scores.push_back(); Scores.push_back();
Scores.push_back(); Scores.push_back();
Scores.push_back(); Scores.push_back();

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

cout << << NumberOf100Scores << << endl;
}
這裏的count()算法算的是等於某一個特定值的對象個數。在上面的例子中,該算法拿list內每個整型對象和100進行比較,每次只要容器對象等於100,變量NumberOf100Scores便加一。該例程的輸出如下:


There were 2 scores of 100



用STL泛型算法count_if()對list內元素進行計數

和count()相比,count_if()是一個有趣的多的版本。它引入了一個新的STL組件,函數對象(function object)。count_if()的參數之一是函數對象。函數對象是一個至少定義了operator()的類。一些STL算法接受函數對象作爲其參數,並對每個正被處理容器對象調用傳入函數對象的operator()。

專門爲和STL算法配合使用的函數對象已指定自己的函數調用操作符返回true或flase,函數對象也因此被稱作判定函數(predicate function)。來個例子就能解釋這一點。count_if()用傳入的函數對象來進行比count()更爲複雜的評估——某個對象是否應被計數。下面的例子將對牙刷的銷售量進行計數。我們假定銷售記錄包含一個4字符的產品代碼和該產品的描述。









string ToothbrushCode();

IsAToothbrush {
:
() ( string& SalesRecord ) {
SalesRecord.substr(,)==ToothbrushCode;
}
};

main () {
list<string> SalesRecords;

SalesRecords.push_back();
SalesRecords.push_back();
SalesRecords.push_back();
SalesRecords.push_back();
SalesRecords.push_back();

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

cout <<
<< NumberOfToothbrushes
<< << endl;
}

該程序的輸出是:
There were   toothbrushes matching code  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 range,它確定了list或其它容器的哪一部分要進行處理。照常第一個iterator指定從何處開始處理,第二個iterator指定在何處停止處理,並且不用處理後者所確定的位置。

下面的例子說明了find()是如何工作的:








main () {
list<string> Fruit;
list<string>::iterator FruitIterator;

Fruit.push_back();
Fruit.push_back();
Fruit.push_back();

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

(FruitIterator == Fruit.end()) {
cout << << endl;
}
{
cout << *FruitIterator << endl;
}
}

該例程的輸出是:


Pineapple

如果find()沒有查找到指定的對象,則返回最後元素的下一位置(past the end)iterator Fruit.end();否則返回一個iterator,指向查找到的list對象。



用STL泛型算法find_if()在list中查找對象

還有一個比find()功能更強大的版本,find_if()。下面的例子即演示了find_if(),該算法以函數對象作爲其參數之一,並用它來進行更爲複雜的評估——某個對象是否“已被找到”。

假設我們手頭有一個記錄list,這些記錄由事件和按年月日存儲的日期組成。我們想查找1997年發生的第一件事。









EventIsIn1997 {
:
() (string& EventRecord) {

EventRecord.substr(,)==;
}
};

main () {
list<string> Events;


Events.push_back();
Events.push_back();
Events.push_back();
Events.push_back();
Events.push_back();

list<string>::iterator EventIterator =
find_if (Events.begin(), Events.end(), EventIsIn1997());





(EventIterator==Events.end()) {
cout << << endl;
}
{
cout << *EventIterator << endl;
}
}


該例程的輸出是:

10 January 1997 Client agrees to job




用STL泛型算法search在list中查找序列

在STL容器中有些字符處理起來比較容易,不過下面我們來看看一個處理起來較困難的字符序列。下面定義一個持有字符的STL list:


list<char> Characters;


現在我們已經得到一個堅石般的字符序列,它自個兒知道如何管理自己的內存,它也知道起始和結束的確切位置。這個字符序列很有用處,我沒用這話誇過以null結尾的字符數組吧。

好,再向這個list添加一些我們喜歡的字符:

  Characters.push_back();
Characters.push_back();
Characters.push_back();
Characters.push_back();


我們得到了幾個null字符呢?

   NumberOfNullCharacters();
count(Characters.begin(), Characters.end(), , NumberOfNullCharacters);
cout << << NumberOfNullCharacters << endl;


再來查找一下字符'1':

  list<>::iterator Iter;
Iter = find(Characters.begin(), Characters.end(), );
cout << << *Iter << endl;


這個例子意在說明STL容器允許你以更標準的方式處理null字符。接下來用STL search算法在容器中搜索兩個nulls。

如你猜想的那樣,STL泛型算法search()在容器中搜索,只不過搜索的目標是一個元素序列,而非find()和find_if()那樣搜索的是單個元素。









main ( ) {

list<> TargetCharacters;
list<> ListOfCharacters;

TargetCharacters.push_back();
TargetCharacters.push_back();

ListOfCharacters.push_back();
ListOfCharacters.push_back();
ListOfCharacters.push_back();
ListOfCharacters.push_back();

list<>::iterator PositionOfNulls =
search(ListOfCharacters.begin(), ListOfCharacters.end(),
TargetCharacters.begin(), TargetCharacters.end());

(PositionOfNulls!=ListOfCharacters.end())
cout << << endl;
}


該例程的輸出是:


We found the nulls

search算法在一個序列中查找另一個序列首次出現的位置。這個例子裏,我們在ListOfCharacters中搜尋TargetCharacters第一次出現的位置,TargetCharacters是一個包含兩個null字符的list。

search的參數包括:兩個指定了搜索區間(a range to search)的iterators,還有兩個指定了搜索的目標區間(a range to search for)的iterators。由此可知,我們是在ListOfCharacters的整個區間裏查找TargetCharacters list的整個區間。

如果找到TargetCharacters,search會返回一個iterator,指向ListOfCharacters中和其匹配的序列的第一個字符。如果找不到,search返回最後元素的後一個位置ListOfCharacters.end()。



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

要排序一個list,我們使用list的成員函數sort()而非泛型算法sort()。到目前爲止我們用的所用算法都是泛型算法。不過STL中,有些情況下出於需要或更好的性能,容器也會自己提供特定算法的具體實現。

本例中的list容器有自己的sort算法,因爲泛型sort算法只能對可隨機訪問其內部元素的容器進行排序。由於list容器以鏈接表的結構實現,故不能隨機存取list中的元素。需要提供能對鏈接表排序的專門sort()成員函數。

你會發現這種情況對STL而言司空見慣。由於各種各樣的原因,容器會提供特別的附加函數,或是因爲效率的需要,或是因爲利用容器結構的一些特性能夠獲得不同尋常的性能。








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

main () {
list<string> Staff;
list<string>::iterator PeopleIterator;

Staff.push_back();
Staff.push_back();
Staff.push_back();
Staff.push_back();
Staff.push_back();

cout << << endl;
for_each(Staff.begin(), Staff.end(), PrintIt );

Staff.sort();

cout << << 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成員函數insert()插入元素到list中

list成員函數push_front()和push_back()分別在list的頭部和尾部添加元素,你也可以用insert()在list的任何位置添加對象。

insert()可以添加一個對象、一個對象的多個拷貝,或者一個對象區間(a range of objects)。下面是插入對象到list中的一些例子:






main () {
list<> list1;




( i = ; i < ; ++i) list1.push_back(i);





list1.insert(list1.begin(), -1);





list1.insert(list1.end(), );





IntArray[] = {,};
list1.insert(list1.end(), &IntArray[], &IntArray[]);





}

注意insert()函數是在你指定的iterator所指位置處添加一個或多個元素。插入的元素會出現在list中指定的iterator所在位置的元素前。



List構造函數

此前我們都是按如下方式定義一個list:

  list<> Fred;
你也可以這樣定義一個list,同時初始化其元素:

  
list<> Fred(, );
或者,你也可以定義一個list,然後用取自另一個STL容器的區間來初始化該list,這個STL容器不用非得是個list,只要有相同的value type。

  vector<> Harry;
Harry.push_back();
Harry.push_back();


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



用list成員函數刪除list中的元素

list成員函數pop_front()刪除list中的第一個元素,pop_back()刪除的是最後一個元素。成員函數erase()刪除某個iterator所指的元素。還有一個erase()函數能刪除一個區間的元素。






main () {
list<> list1;





( i = ; i < ; ++i) list1.push_back(i);

list1.pop_front();

list1.pop_back();

list1.erase(list1.begin());

list1.erase(list1.begin(), list1.end());

cout << << list1.size() << << endl;
}

輸出如下:


list contains 0 elements



用list成員函數remove()刪除list中的元素

list成員函數remove()可以刪除list中的對象:









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

main () {
list<string> Birds;

Birds.push_back();
Birds.push_back();
Birds.push_back();
Birds.push_back();
Birds.push_back();

cout << << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);

Birds.remove();

cout << << 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成員函數remove()的工作方式有所不同。remove的泛型版本不會改變容器的大小。









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

main () {
list<string> Birds;
list<string>::iterator NewEnd;

Birds.push_back();
Birds.push_back();
Birds.push_back();
Birds.push_back();
Birds.push_back();

cout << << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);

NewEnd = remove(Birds.begin(), Birds.end(), );

cout << endl << << endl;
for_each(Birds.begin(), NewEnd, PrintIt);

cout << endl << << endl;
for_each(Birds.begin(), Birds.end(), PrintIt);
}
輸出如下:

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()算法返回一個iterator指向該list的新結尾(new end)。從起始處到新結尾(但不包括這個新結尾)的區間包含了施加remove算法之後剩下的元素。然後你可以用list成員函數erase來刪除從新結尾到舊結尾(old end)之間的區間。



用STL泛型算法stable_partition()和list成員函數splice()分割一個list

我們將以一個稍微比較複雜的例子來結束這篇文章。這個例子演示說明STL泛型算法stable_partition()和list成員函數splice ()的一個變種。注意函數對象的用法,還有代碼中並沒有出現循環。控制是通過一系列簡單的語句完成的,這些語句調用了STL算法。

stable_partition()是個很有趣的函數,它重新排列list內元素使得那些滿足一定條件的元素排在其它元素之前。同時它保持兩組元素的相對順序。來個例子就能一清二楚了。

splice把另一個list的元素連接到一個list裏,同時刪除源list中的元素(注:即前面的另一個list)

這個例子裏我們要從命令行接收一些標誌(flags)和四個文件名,這些文件名必須以一定順序出現。藉助stable_partition()我們可以和文件名相關的、放在任何位置的標誌,然後把它們組合在一起,同時無需關心文件名參數的順序。

歸功於現成易用的計數和查找算法,我們能夠按需調用這些算法以確定程序裏哪些標誌已被設定。我發現用容器來管理少量類似的動態數據變量非常方便。










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

IsAFlag {
:
() (string& PossibleFlag) {
PossibleFlag.substr(,)==;
}
};

IsAFileName {
:
() (string& StringToCheck) {
!IsAFlag()(StringToCheck);
}
};

IsHelpFlag {
:
() (string& PossibleHelpFlag) {
PossibleHelpFlag==;
}
};

main ( argc, *argv[]) {

list<string> CmdLineParameters;
list<string>::iterator StartOfFiles;
list<string> Flags;
list<string> FileNames;

( i = ; i < argc; ++i) CmdLineParameters.push_back(argv[i]);

CmdLineParameters.pop_front();


NumberOfFiles();
count_if(CmdLineParameters.begin(), CmdLineParameters.end(),
IsAFileName(), NumberOfFiles);

cout <<
<< (NumberOfFiles == ? : )
<<
<< NumberOfFiles
<< << endl;


StartOfFiles =
stable_partition(CmdLineParameters.begin(), CmdLineParameters.end(),
IsAFlag());

cout << << endl;
for_each(CmdLineParameters.begin(), CmdLineParameters.end(), PrintIt);


Flags.splice(Flags.begin(), CmdLineParameters,
CmdLineParameters.begin(), StartOfFiles);

(!Flags.empty()) {
cout << << endl;
for_each(Flags.begin(), Flags.end(), PrintIt);
}
{
cout << << endl;
}


FileNames.splice(FileNames.begin(), CmdLineParameters,
CmdLineParameters.begin(), CmdLineParameters.end());

(!FileNames.empty()) {
cout << << endl;
for_each(FileNames.begin(), FileNames.end(), PrintIt);
}
{
cout << << endl;
}


(find_if(Flags.begin(), Flags.end(), IsHelpFlag())!=Flags.end()) {
cout << << endl;
}



}


假設命令行輸入如下:


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算法將iterators作爲其參數,還有iterator ranges,有時是一個區間,有時則是兩個。STL容器提供了iterators,那正是我們使用list<int>:: iterator,或list<char>::iterator,或list<string>::iterator的緣由。

Iterators有一個定義良好的層次結構,它們具有各不相同的“權力”(powers)。有些iterators提供對容器的只讀訪問,有些則只寫;有些只能向前迭代,有些則是雙向的;有些iterators提供對容器的隨機訪問。

STL算法要求一個特定“權力”的iterator。如果容器不提供那種權力的iterator,那麼這個算法便無法通過編譯。例如,list容器只提供了雙向(bidirectional)iterators,而泛型sort()算法要求可隨機訪問的iterators。這就是我們需要專門的list成員函數sort()的原因。

要真正正確的使用STL,你需要認真學習各種不同的iterators。你需要熟知哪些類型的iterators是由哪些容器提供的,然後需要熟知那些算法要求何種類型的iterators。當然,你得需要理解手頭能有何種類型的iterators。



在開發中(field)使用STL

過去一年裏我已經用STL編寫了數個商業C++程序,期間STL的確讓我省事不少,而且幾乎消除了邏輯錯誤。

最大的程序大概有5000行,而最驚人的應該是它的速度。這個程序能在大約20秒內讀取並詳盡處理一個1-2Mb大的事務文件。它在Linux下用GCC 2.7.2開發,現運行在HP-UX機器上,使用了超過50個函數對象以及大量容器,其大小不等,如小小的list,最大的map則有超過14,000個元素。

這個程序的函數對象形成了一個層次結構,上層函數對象調用低層函數對象。我廣泛的使用了STL算法for_each(),find(),find_if(),count()和count_if()。我幾乎把程序的所有內部結構簡化至STL算法的調用。

STL有助於自動把代碼組織成清晰的控制和支持兩個模塊。通過細緻的編寫函數對象並給它們取上有意義的名字後,我便設法把它們擱置到一邊,然後集中精力處理軟件中的控制流程。

關於STL編程還有很多東西要學,我希望你樂於學習這些例子。

參考文獻裏的兩本書在網上都有最新的勘誤表,你可以自己校正它們。

Stroustrup一書在每章後面都有建議欄,這些建議都很棒,尤其值得初學者一看。整本書也比先前版本更加通俗易懂些,當然也變得更厚。書店裏還有許多其它論述STL的書,自己去找找看,並祝好運!:)



參考文獻

The STL Tutorial and Reference Guide, David Musser and Atul Saini. Addison Wesley 1996.

The C++ Programming Language 3e, Bjarne Stroustrup. Addison Wesley 1997.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章