天天看點

STL基礎知識

STL的一個重要特點是資料結構和算法的分離。盡管這是個簡單的概念,但這種分離确實使得STL變得非常通用。例如,由于STL的sort()函數是完全通用的,你可以用它來操作幾乎任何資料集合,包括連結清單,容器和數組。

要點

STL算法作為模闆函數提供。為了和其他元件相差別,在本書中STL算法以後接一對圓括弧的方式表示,例如sort()。

STL另一個重要特性是它不是面向對象的。為了具有足夠通用性,STL主要依賴于模闆而不是封裝,繼承和虛函數(多态性)——OOP的三個要素。你在STL中找不到任何明顯的類繼承關系。這好像是一種倒退,但這正好是使得STL的元件具有廣泛通用性的底層特征。另外,由于STL是基于模闆,内聯函數的使用使得生成的代碼短小高效。

提示

確定在編譯使用了STL的程式中至少要使用-O優化來保證内聯擴充。

STL元件

STL提供了大量的模闆類和函數,可以在OOP和正常程式設計中使用。所有的STL的大約50個算法都是完全通用的,而且不依賴于任何特定的資料類型。下面的小節說明了三個基本的STL元件:

1) 疊代器提供了通路容器中對象的方法。例如,可以使用一對疊代器指定list或vector中的一定範圍的對象。疊代器就如同一個指針。事實上,C++的指針也是一種疊代器。但是,疊代器也可以是那些定義了operator*()以及其他類似于指針的操作符地方法的類對象。

2) 容器是一種資料結構,如list,vector,和deques ,以模闆類的方法提供。為了通路容器中的資料,可以使用由容器類輸出的疊代器。

3) 算法是用來操作容器中的資料的模闆函數。例如,STL用sort()來對一個vector中的資料進行排序,用find()來搜尋一個list中的對象。函數本身與他們操作的資料的結構和類型無關,是以他們可以在從簡單數組到高度複雜容器的任何資料結構上使用。

頭檔案

為了避免和其他頭檔案沖突, STL的頭檔案不再使用正常的.h擴充。為了包含标準的string類,疊代器和算法,用下面的訓示符:

#i nclude <string>

#i nclude <iterator>

#i nclude <algorithm>

如果你檢視STL的頭檔案,你可以看到象iterator.h和stl_iterator.h這樣的頭檔案。由于這些名字在各種STL實作之間都可能不同,你應該避免使用這些名字來引用這些頭檔案。為了確定可移植性,使用相應的沒有.h字尾的檔案名。表1列出了最常使用的各種容器類的頭檔案。該表并不完整,對于其他頭檔案,我将在本章和後面的兩章中介紹。

表 1. STL頭檔案和容器類

#i nclude Container Class
<deque> deque
<list> list
<map> map, multimap
<queue> queue, priority_queue
<set> set, multiset
<stack> stack
<vector> vector, vector<bool>

名字空間

你的編譯器可能不能識别名字空間。名字空間就好像一個信封,将标志符封裝在另一個名字中。标志符隻在名字空間中存在,因而避免了和其他标志符沖突。例如,可能有其他庫和程式子產品定義了sort()函數,為了避免和STL地sort()算法沖突,STL的sort()以及其他标志符都封裝在名字空間std中。STL的sort()算法編譯為std::sort(),進而避免了名字沖突。

盡管你的編譯器可能沒有實作名字空間,你仍然可以使用他們。為了使用STL,可以将下面的訓示符插入到你的源代碼檔案中,典型地是在所有的#i nclude訓示符的後面:

using namespace std;

疊代器

疊代器提供對一個容器中的對象的通路方法,并且定義了容器中對象的範圍。疊代器就如同一個指針。事實上,C++的指針也是一種疊代器。但是,疊代器不僅僅是指針,是以你不能認為他們一定具有位址值。例如,一個數組索引,也可以認為是一種疊代器。

疊代器有各種不同的建立方法。程式可能把疊代器作為一個變量建立。一個STL容器類可能為了使用一個特定類型的資料而建立一個疊代器。作為指針,必須能夠使用*操作符類擷取資料。你還可以使用其他數學操作符如++。典型的,++操作符用來遞增疊代器,以通路容器中的下一個對象。如果疊代器到達了容器中的最後一個元素的後面,則疊代器變成past-the-end值。使用一個past-the-end值得指針來通路對象是非法的,就好像使用NULL或為初始化的指針一樣。

STL不保證可以從另一個疊代器來抵達一個疊代器。例如,當對一個集合中的對象排序時,如果你在不同的結構中指定了兩個疊代器,第二個疊代器無法從第一個疊代器抵達,此時程式注定要失敗。這是STL靈活性的一個代價。STL不保證檢測毫無道理的錯誤。

疊代器的類型

對于STL資料結構和算法,你可以使用五種疊代器。下面簡要說明了這五種類型:

· Input iterators 提供對資料的隻讀通路。

· Output iterators 提供對資料的隻寫通路

· Forward iterators 提供讀寫操作,并能向前推進疊代器。

· Bidirectional iterators提供讀寫操作,并能向前和向後操作。

· Random access iterators提供讀寫操作,并能在資料中随機移動。

盡管各種不同的STL實作細節方面有所不同,還是可以将上面的疊代器想象為一種類繼承關系。從這個意義上說,下面的疊代器繼承自上面的疊代器。由于這種繼承關系,你可以将一個Forward疊代器作為一個output或input疊代器使用。同樣,如果一個算法要求是一個bidirectional 疊代器,那麼隻能使用該種類型和随機通路疊代器。

指針疊代器

正如下面的小程式顯示的,一個指針也是一種疊代器。該程式同樣顯示了STL的一個主要特性——它不隻是能夠用于它自己的類類型,而且也能用于任何C或C++類型。​​Listing 1​​, iterdemo.cpp, 顯示了如何把指針作為疊代器用于STL的find()算法來搜尋普通的數組。

表 1. iterdemo.cpp

#i nclude <iostream.h>      
#i nclude <algorithm>      
using namespace std;      
#define SIZE 100      
int iarray[SIZE];      
int main()      
{      
iarray[20] = 50;      
int* ip = find(iarray, iarray + SIZE, 50);      
if (ip == iarray + SIZE)      
cout << "50 not found in array" << endl;      
else      
cout << *ip << " found in array" << endl;      
return 0;      
}      

在引用了I/O流庫和STL算法頭檔案(注意沒有.h字尾),該程式告訴編譯器使用std名字空間。使用std名字空間的這行是可選的,因為可以删除該行對于這麼一個小程式來說不會導緻名字沖突。

程式中定義了尺寸為SIZE的全局數組。由于是全局變量,是以運作時數組自動初始化為零。下面的語句将在索引20位置處地元素設定為50,并使用find()算法來搜尋值50:

iarray[20] = 50;      
int* ip = find(iarray, iarray + SIZE, 50);      

find()函數接受三個參數。頭兩個定義了搜尋的範圍。由于C和C++數組等同于指針,表達式iarray指向數組的第一個元素。而第二個參數iarray + SIZE等同于past-the-end 值,也就是數組中最後一個元素的後面位置。第三個參數是待定位的值,也就是50。find()函數傳回和前兩個參數相同類型的疊代器,這兒是一個指向整數的指針ip。

必須記住STL使用模闆。是以,STL函數自動根據它們使用的資料類型來構造。

為了判斷find()是否成功,例子中測試ip和 past-the-end 值是否相等:

if (ip == iarray + SIZE) ...      

如果表達式為真,則表示在搜尋的範圍内沒有指定的值。否則就是指向一個合法對象的指針,這時可以用下面的語句顯示::

cout << *ip << " found in array" << endl;      

測試函數傳回值和NULL是否相等是不正确的。不要象下面這樣使用:

int* ip = find(iarray, iarray + SIZE, 50);      
if (ip != NULL) ... // ??? incorrect      

當使用STL函數時,隻能測試ip是否和past-the-end 值是否相等。盡管在本例中ip是一個C++指針,其用法也必須符合STL疊代器的規則。

容器疊代器

盡管C++指針也是疊代器,但用的更多的是容器疊代器。容器疊代器用法和iterdemo.cpp一樣,但和将疊代器申明為指針變量不同的是,你可以使用容器類方法來擷取疊代器對象。兩個典型的容器類方法是begin()和end()。它們在大多數容器中表示整個容器範圍。其他一些容器還使用rbegin()和rend()方法提供反向疊代器,以按反向順序指定對象範圍。

下面的程式建立了一個矢量容器(STL的和數組等價的對象),并使用疊代器在其中搜尋。該程式和前一章中的程式相同。

Listing 2. vectdemo.cpp

#i nclude <iostream.h>      
#i nclude <algorithm>      
#i nclude <vector>      
using namespace std;      
vector<int> intVector(100);      
void main()      
{      
intVector[20] = 50;      
vector<int>::iterator intIter =      
find(intVector.begin(), intVector.end(), 50);      
if (intIter != intVector.end())      
cout << "Vector contains value " << *intIter << endl;      
else      
cout << "Vector does not contain 50" << endl;      
}      

注意用下面的方法顯示搜尋到的資料:

cout << "Vector contains value " << *intIter << endl;      

常量疊代器

和指針一樣,你可以給一個疊代器指派。例如,首先申明一個疊代器:

vector<int>::iterator first;      

該語句建立了一個vector<int>類的疊代器。下面的語句将該疊代器設定到intVector的第一個對象,并将它指向的對象值設定為123::

first = intVector.begin();      
*first = 123;      

這種指派對于大多數容器類都是允許的,除了隻讀變量。為了防止錯誤指派,可以申明疊代器為:

const vector<int>::iterator result;      
result = find(intVector.begin(), intVector.end(), value);      
if (result != intVector.end())      
*result = 123; // ???      

警告

另一種防止資料被改變得方法是将容器申明為const類型。

『呀!在VC中測試出錯,正确的含義是result成為常量而不是它指向的對象不允許改變,如同int *const p;看來這作者自己也不懂』

使用疊代器程式設計

你已經見到了疊代器的一些例子,現在我們将關注每種特定的疊代器如何使用。由于使用疊代器需要關于STL容器類和算法的知識,在閱讀了後面的兩章後你可能需要重新複習一下本章内容。

輸入疊代器

輸入疊代器是最普通的類型。輸入疊代器至少能夠使用==和!=測試是否相等;使用*來通路資料;使用++操作來遞推疊代器到下一個元素或到達past-the-end 值。

為了了解疊代器和STL函數是如何使用它們的,現在來看一下find()模闆函數的定義:

template <class InputIterator, class T>      
InputIterator find(      
InputIterator first, InputIterator last, const T& value) {      
while (first != last && *first != value) ++first;      
return first;      
}      

注意

在find()算法中,注意如果first和last指向不同的容器,該算法可能陷入死循環。

輸出疊代器

輸出疊代器預設隻寫,通常用于将資料從一個位置拷貝到另一個位置。由于輸出疊代器無法讀取對象,是以你不會在任何搜尋和其他算法中使用它。要想讀取一個拷貝的值,必須使用另一個輸入疊代器(或它的繼承疊代器)。

Listing 3. outiter.cpp

#i nclude <iostream.h>      
#i nclude <algorithm>   // Need copy()      
#i nclude <vector>      // Need vector      
using namespace std;      
double darray[10] =      
{1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9};      
vector<double> vdouble(10);      
int main()      
{      
vector<double>::iterator outputIterator = vdouble.begin();      
copy(darray, darray + 10, outputIterator);      
while (outputIterator != vdouble.end()) {      
cout << *outputIterator << endl;      
outputIterator++;      
}      
return 0;      
}      

當使用copy()算法的時候,你必須確定目标容器有足夠大的空間,或者容器本身是自動擴充的。

前推疊代器

前推疊代器能夠讀寫資料值,并能夠向前推進到下一個值。但是沒法遞減。replace()算法顯示了前推疊代器的使用方法。

template <class ForwardIterator, class T>      
void replace (ForwardIterator first,      
ForwardIterator last,      
const T& old_value,      
const T& new_value);      

使用replace()将[first,last]範圍内的所有值為old_value的對象替換為new_value。:

replace(vdouble.begin(), vdouble.end(), 1.5, 3.14159);      

雙向疊代器

雙向疊代器要求能夠增減。如reverse()算法要求兩個雙向疊代器作為參數:

template <class BidirectionalIterator>      
void reverse (BidirectionalIterator first,      
BidirectionalIterator last);      

使用reverse()函數來對容器進行逆向排序:

reverse(vdouble.begin(), vdouble.end());      

随機通路疊代器

随機通路疊代器能夠以任意順序通路資料,并能用于讀寫資料(不是const的C++指針也是随機通路疊代器)。STL的排序和搜尋函數使用随機通路疊代器。随機通路疊代器可以使用關系操作符作比較。

random_shuffle() 函數随機打亂原先的順序。申明為:

template <class RandomAccessIterator>      
void random_shuffle (RandomAccessIterator first,      
RandomAccessIterator last);      

使用方法:

random_shuffle(vdouble.begin(), vdouble.end());      

疊代器技術

要學會使用疊代器和容器以及算法,需要學習下面的新技術。

流和疊代器

本書的很多例子程式使用I/O流語句來讀寫資料。例如:

int value;      
cout << "Enter value: ";      
cin >> value;      
cout << "You entered " << value << endl;      

對于疊代器,有另一種方法使用流和标準函數。了解的要點是将輸入/輸出流作為容器看待。是以,任何接受疊代器參數的算法都可以和流一起工作。

#i nclude <iostream.h>      
#i nclude <stdlib.h>    // Need random(), srandom()      
#i nclude <time.h>      // Need time()      
#i nclude <algorithm>   // Need sort(), copy()      
#i nclude <vector>      // Need vector      
using namespace std;      
void Display(vector<int>& v, const char* s);      
int main()      
{      
// Seed the random number generator      
srandom( time(NULL) );      
// Construct vector and fill with random integer values      
vector<int> collection(10);      
for (int i = 0; i < 10; i++)      
collection[i] = random() % 10000;;      
// Display, sort, and redisplay      
Display(collection, "Before sorting");      
下一篇: html parser