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