Unix/Linux C++應用開發-C++標準模板庫
作為C++標準庫相當重要的一部分,STL庫提供一系列元件操作。它主要可以分為容器、迭代器、基本演算法、函式物件以及記憶體分配器和配接器六個部分。整個STL庫的程式碼都採用模板函式以及模板類的方式實現,具有高度的通用性。對於傳統的應用程式來講,模板庫支援並且倡導一種新的程式設計風格,即稱為泛型程式設計思想,以通用的模板方式來編寫應用程式中的資料結構與演算法。
C++標準STL庫中封裝實現了常見資料結構,並以容器的方式提供給使用者使用。STL容器主要包含vector向量、deque佇列、list連結串列、set集合與map對映等。每個類別容器相應通過一個模板類對外提供操作介面。通過提供的容器類型別,開發者可以自定義封裝實現相應的資料結構類的物件。通過定義時傳入真正的資料型別來例項化產生對應容器類的物件例項,並且在外部通過這些類提供的方法介面來實現應用程式中需求處理功能。
16.2.1 STL容器介紹
STL模板庫容器大致可以分為兩類:一類是以線性組織方式儲存型別相同的物件的序列式容器,另一類是使用樹結構的關聯式容器。
1.序列式容器
序列式容器在STL中主要包含常見的三種:向量vector、連結串列list和雙端佇列deque。
q 向量vector為一種順序儲存同類型元素的資料結構。它是一種陣列方式的思路實現,並且可以隨機訪問的序列。
q 連結串列list是一種實現雙向連結串列資料結構的容器。它只支援順序訪問序列中的元素。該容器在刪除、插入相應元素時,效率較高。另外,list可以與vector一樣在需要時動態改變其大小。
q 雙端佇列deque
2.關聯式容器
關聯式容器則是採用了key-value鍵值的方式儲存和訪問容器中的元素。關聯式容器採用樹結構組織資料,支援快速、隨機並且高效的檢索其中的元素。關聯式容器主要包含set、multiset、map以及multimap容器。
q set容器支援隨機存取,並且其鍵與對應的資料元素為同一個值。該容器中所有的元素必須是唯一的,不能包含重複的元素。
q multiset容器則在set基礎上可以包含重複的元素。同樣,該容器的鍵與對應的元素也是為同一值。
q map
q multimap提供單個關鍵詞可以對應多個數據的資料結構操作,供實際應用中選擇使用。
順序式容器與關聯式容器在底層實現,通過不同的資料結構型別來區分。針對提供的不同的操作介面,使用者並不需要了解具體容器底層實現,可以直接通過對外公佈的介面訪問對應的資料元素。而容器的設計實現則是主要基於陣列、連結串列以及二叉樹基本資料結構原型的底層實現。開發者通常只需要瞭解容器的基本功能以及應用場合,就能夠在實際應用中選擇合適的容器來處理相應的資料。
16.2.2 vector向量
vector向量容器的資料結構的原型為陣列型別。只不過該容器通過封裝實現vector模板類實現動態陣列結構,並提供基於該順序儲存結構的操作介面供開發者呼叫。vector容器在使用時,必須包含相應的標頭檔案(#include<vector>)。標頭檔案提供了相應的操作介面宣告。一旦包含之後,就可以定義vector物件例項儲存並操作相應型別的資料元素。
由於vector為模板類,所以在實際定義vector物件時,需要指定該模板型別引數。而相應的模板引數可以是內建資料型別,也可以是自定義的結構體、類等型別。例如,以下程式碼根據傳入的實際型別來確定該vector例項化儲存相應的型別元素。
#include<vector>
std::vector;
vector<string>stringValue; //第一個定義string標準字串型別的vector物件stringValue
vector<int> intValue; //第二個定義int整型型別的vector物件intValue
vector<float> floatValue(5,6.0); //第三個定義float浮點型別的vector物件floatValue
vector<MyString>MyStringValue(5); //第四個定義自定義字串操作MyString型別的vector物件MyStringValue
上述程式碼中,標準的庫都採用std名字空間,使用相應的標準庫的中型別操作。一個應用程式需要使用標準名字空間內的API,需要先宣告在該名稱空間下。std::vector表明使用的是std名字空間下的該類。通常情況下,建議使用全域性方式定義std名字空間。即使用using namespace std;語句來公開表示以下應用程式中使用的標準庫操作都來自於該名字空間。
例項中主要根據具體的引數型別,採用vector類提供的建構函式來構造例項化物件。根據vector類提供的對外建構函式介面,可以採用不同的建構函式來構造該容器類物件例項。
q 第一個stringValue物件為定義string型別的空vector。該容器是個模板實現,其中存放的元素必須為相應傳入模板例項化的引數型別,即上述語句主要定義空的可以存放string型別元素的vector向量。
q 第二個則同樣定義一個空vector物件,該物件中存放著整型元素。
q 第三個例項物件的定義是根據vector類提供的另一個建構函式介面而來。該建構函式結構介面物件定義語法為vector<Type> objectName(size,value),即定義vector物件例項同時指定初始化元素的大小以及對應存放的值。例項三中實際定義了vector物件floatValue,該向量存放float浮點型資料元素,並且在構造時指定其大小為5,同時每個元素都採用資料值6.0來初始化。
q 第四個例項中則直接通過自定義字串型別MyString作為例項化引數型別,定義指定大小為5個元素的向量MyStringValue。
16.2.3 vector基本操作演示
vector向量在模板庫中既然以類方式實現,那麼除了提供對應的建構函式用於定義具體vector物件例項外,還提供了相當豐富的對外操作vector向量的公開介面方法。這些方法主要用於資料元素的操作,如表16.1所示。
表16.1 向量vector介面方法說明
介面方法名稱 |
基本功能說明 |
vectorObject.assign(start,end) |
將迭代器start與end之間的資料元素賦值給vectorObject |
vectorObject.assign(n, elem) |
將n個elem元素賦給其呼叫的vectorObject |
vectorObject.at(index) |
返回向量vectorObject中指定索引index所指的資料元素 |
vectorObject.back() |
返回向量vectorObject中最後一個數據元素 |
vectorObject.begin() |
返回指向向量vectorObject中第一個元素的迭代器 |
vectorObject.capacity() |
返回向量vectorObject中元素的個數 |
vectorObject.clear() |
清空向量vectorObject中的資料元素 |
vectorObject.empty() |
判斷向量vectorObject是否為空,為空則返回true值 |
vectorObject.end() |
返回指向向量vectorObject最後一個數據元素的迭代器 |
vectorObject.erase(loc) |
刪除當前向量loc位置資料元素,返回下一個資料元素位置 |
vectorObject.erase(start,end) |
刪除從start到end區間的元素,包含start不包含end,並返回下一個資料元素位置 |
vectorObject.front() |
返回向量中第一個資料元素 |
vectorObject.insert(pos,elem) |
當前向量中pos位置插入元素elem,並返回新資料的位置 |
vectorObject.insert(pos,n,elem) |
當前向量中pos位置插入n個elem資料元素,不返回任何值 |
vectorObject.insert(pos,start,end) |
當前向量中pos位置處插入區間在start到end之間的資料元素,包含start但不包含end,無任何返回值 |
vectorObject.max_size() |
返回當前向量中最大資料量,即向量的最大長度 |
vectorObject.pop_back() |
刪除當前向量最後一個數據元素 |
vectorObject.push_back(elem) |
當前向量尾部新增一個數據元素 |
vectorObject.rbegin() |
返回一個逆向佇列的第一個資料元素 |
vectorObject.rend() |
返回一個逆向佇列的最後一個數據元素 |
VectorObject.reserve() |
設定當前向量合適的最小容量 |
vectorObject.resize(num) |
重新設定當前向量的長度 |
vectorObject.size() |
返回當前容器中實際資料元素個數 |
v1.swap(v2) |
呼喚向量v1與v2的資料元素 |
operator[] |
過載下標操作符,用於訪問指定向量中的元素 |
下面將會通過一個完整的例項,呼叫vector相應的介面,操作vector儲存的資料。程式碼編輯如下所示。
1.準備例項
開啟UE工具,建立新的空檔案並且另存為chapter1601.cpp。該程式碼檔案隨後會同makefile檔案一起通過FTP工具傳輸至Linux伺服器端,客戶端通過scrt工具訪問操作。程式程式碼檔案編輯如下所示。
/**
* 例項chapter1601
* 原始檔chapter1601.cpp
* vector基本操作例項
*/
#include <iostream>
#include <vector>
using namespace std;
int main()
{
//第一部分程式碼
vector<int> intValue; //定義int整型型別的vector物件intValue
vector<int>::iterator iter; //定義int整型型別的迭代器物件iter
vector<int>::iterator tempIter; //定義int整型型別的迭代器物件tempiter
for(int i = 0;i < 10;i++) //定義for迴圈控制結構,從變數i值為0開始,迴圈10次
{
intValue.push_back(i); //控制結構內實現將i變數遞增值逐個放入容器intValue中
}
cout<<"intValue size:"<<intValue.size()<<endl; //通過vector提供的size()方法計算該容器內元素個數
//第二部分程式碼
cout<<"intValueelement:";
for(int i = 0;i < intValue.size();i++) //for迴圈內,通過vector的at方法逐個訪問輸出元素
{
cout<<intValue.at(i)<<" ";
}
cout<<endl;
//第三部分程式碼
intValue.assign(5,6); //通過vector的assign方法來實現容器內元素值的替換
cout<<"intValue element:";
for(int i = 0;i < intValue.size();i++) //通過for迴圈控制結果迴圈列印輸出容器內元素值
{
cout<<intValue.at(i)<<" ";
}
cout<<endl;
//第四部分程式碼
iter = intValue.begin(); //通過begin()方法將迭代器指向容器intValue第一個元素
intValue.insert(iter,100); //通過vector的insert方法將迭代器指向的元素處插入值100
intValue.insert(iter,3,200); //通過vector的insert方法將迭代器指向的元素處插入3 //個200的值
cout<<"intValue element:";
for(iter = intValue.begin();iter != intValue.end();iter++) //迴圈控制輸出容器元素值
{
cout<<*iter<<" ";
}
cout<<endl;
//第五部分程式碼
cout<<"intValue element:"<<endl; //提示下面將會進行一些操作後列印輸出容器內容
int size = intValue.size(); //通過vector提供的size()方法獲取到該容器內長度
for(int i = 0;i < size;i++) //通過for迴圈遍歷該容器元素
{
iter = intValue.begin(); //首先將迭代器指向容器intValue第一個元素
intValue.erase(iter); //通過vector提供的erase方法刪除指向的第一個元素
for(tempIter = intValue.begin();tempIter !=intValue.end();tempIter++)//通過//臨時的迭代器訪問輸出此 //時容器內的元素值
{
cout<<*tempIter<<"";
}
cout<<endl;
}
intValue.clear(); //通過vector提供的clear()方法清空容器intValue內元素
if(intValue.empty()) //通過if結構,結合vector提供的empty()方法判斷容器是否為空
{
cout<<"The vector intValue isempty!"<<endl; //為空則輸出提示資訊
}
else
{
cout<<"haveelements!"<<endl; //不為空則輸出提示資訊
}
return 0;
}
程式中主要使用了vector容器的插入、刪除資料操作。具體程式執行講解見後面程式剖析。
2.編輯makefile
Linux平臺下需要編譯的原始檔為chapter1601.cpp,相關makefile工程檔案編譯命令編輯如下所示。
OBJECTS=chapter1601.o
CC=g++
chapter1601: $(OBJECTS)
$(CC)$(OBJECTS) -g -o chapter1601
clean:
rm-f chapter1601 core $(OBJECTS)
submit:
cp-f -r chapter1601 ../bin
3.編譯執行程式
當前shell下執行make命令,生成可執行程式檔案,隨後通過make submit命令提交程式檔案至本例項bin目錄,通過cd命令定位至bin目錄,執行該程式,執行結果如下所示。
[[email protected] src]$ make
g++ -c-o chapter1601.o chapter1601.cpp
g++ chapter1601.o -g -o chapter1601
[developer @localhost src]$ make submit
cp -f -r chapter1601 ../bin
[developer @localhost src]$ cd ../bin
[developer @localhost bin]$ ./chapter1601
intValue size:10
intValue element:0 1 2 3 4 5 6 7 8 9
intValue element:6 6 6 6 6
intValue element:200 200 200 100 6 6 6 6 6
intValue element:
200 200 100 6 6 6 6 6
200 100 6 6 6 6 6
100 6 6 6 6 6
6 6 6 6 6
6 6 6 6
6 6 6
6 6
6
The vector intValue is empty!
4.剖析程式
上述例項主要演示了標準模板庫vector容器提供的基本操作應用。應用程式中首先定義一個整型引數型別的空向量vector,該向量例項化為處理整型元素的容器。隨後定義兩個作用於vector相對應的迭代器,迭代器iter、tempIter作用於向量intValue上,類似指標,通常可以用於遍歷處理容器中的資料元素。下面將程式分為五個部分來進行剖析講解。
第一個部分應用例項中使用的第一個方法介面使為push_back(elem)。該方法主要用於向空向量的尾部新增資料元素。程式中採用for迴圈控制結構,迴圈地在向量intValue中放入10個元素。其存放的為迴圈遞增的i值。空向量intValue中存放10個元素後,使用該物件呼叫其方法成員size求取該向量中元素個數,並在螢幕上列印輸出。從程式執行結果可以看出,該向量中包含10個數據元素。
第二個部分主要演示向量容器at方法的使用。根據for迴圈控制結構,遞增式訪問向量容器中的資料元素。以當前向量長度size返回值為for迴圈判斷值,依次遞增變數i。通過傳入實參i值呼叫at方法,返回對應位置的資料元素並列印輸出。此時,該容器中存放了剛剛放入的遞增值,位置從0~9,共10個元素。
第三個主要演示方法介面assign的使用。根據前面的介面說明,傳入兩實參分別為5和6。5表示需要替換的元素的個數,6表示相應的值。但是需要注意的是,該替換是整個所有元素的替換,即將當前向量中的元素全部採用5個6來替換,其餘元素都不復存在了。隨後的at()方法迴圈遍歷列印元素說明了這點。替換後的向量中只剩下了5個元素值為6的資料。
第四個主要演示了insert方法介面(在指定位置插入元素)的使用,首先將迭代器iter指向當前vector的首元素,隨後使用當前向量物件呼叫insert方法,根據迭代器iter所指的位置插入實參100。由於當前迭代器指向位置為向量的首元素,所以在向量中首元素前插入資料100。此時,向量中存放的資料即為(100,6,6,6,6,6)。第二個insert方法介面的呼叫則採用過載實現的另外一個插入操作介面。該介面主要根據迭代器所指的位置,插入指定數目的指定元素。應用程式中主要在向量的首部插入3個元素為200的資料,此時向量中的資料為(200,200,200,100,6,6,6,6,6)。隨後的部分通過for迴圈控制,通過cout流物件輸出程式碼中採用迭代器遍歷向量方法列印輸出並驗證了該向量中此時的資料元素。
第五個演示了erase方法介面(刪除向量中指定位置)的使用。通過兩個for迴圈控制結構,依次遞增的刪除向量中元素的操作。根據向量中的資料長度首先採用erase方法呼叫刪除向量第一個元素,隨後第二重for迴圈則列印輸出剩下的向量資料元素。隨後呼叫了clear方法清空了當前向量,然後根據empty方法成員呼叫判斷,當前向量是否為空,由於前面已經將該向量清空,此時empty方法返回true,隨後輸出列印對應的提示資訊。
vector向量提供了眾多的操作方法介面,本書由於篇幅限制無法一一演示使用方法。初學者可以根據上述演示的過程,通過標準庫提供的介面基本說明,在應用程式中嘗試著使用這些介面。
16.2.4 deque佇列
deque容器為雙端可操作佇列,非常類似於向量vector資料結構。僅僅是雙端佇列可以在容器的兩端插入以及刪除對應的資料元素,並且提供的操作效率較高。雙端佇列deque同樣也提供了隨機訪問功能,使用者也可以根據需要動態地改變佇列的大小。
應用程式中要使用雙端佇列deque容器,同樣需要包含相應的標頭檔案(#include<deque>)。同樣模板類deque提供了多個建構函式過載實現形式,允許根據需要,定義不同的雙端佇列的物件例項。下面通過幾個例項定義,幫助讀者瞭解deque物件構造情況。
#include <deque> //佇列包含的標頭檔案
std::deque; //佇列名字空間宣告
deque<int> intValue; //定義整型型別佇列物件intValue
deque<string>stringValue(100); //定義string標準字串型別佇列物件stringValue
deque<double>doubleValue(10,200.0); //定義double型別佇列物件doubleValue
deque<float>floatValue1(floatValue2); //定義float型別佇列物件floatValue1
deque<MyString> MyStringValue(first,last); //定義自定義MyString型別佇列物件MyStringValue
第一個例項定義中通過整型引數例項化佇列物件,定義了雙端佇列物件例項intValue。此時雙端佇列為空,可以儲存整型型別的資料元素。
第二個例項定義中則採用string字串型別例項化deque容器。通過在定義時傳入實參來指定容器的大小,這裡指定該容器大小為100。
第三個例項中使用deque容器構造原型(deque<type> dequename(size,elem))構造雙端佇列物件例項。其中,size為容器的長度,elem則為所有元素賦初始值。這裡,定義了double型雙端佇列容器doubleValue,容量大小為10,每個元素初始化為200.0。
第四個物件例項構造則是採用內部拷貝構造的方式,將根據現有的deque佇列物件例項建立一個新的物件例項。例項中以float型別例項化佇列,並以現有的同類型floatValue2物件拷貝構造實現。
最後一個例項定義則以自定義字串型別例項化deque,定義物件例項以迭代器first到last區間建立有限制範圍的雙端佇列MyStringValue。
16.2.5 deque基本操作演示
deque佇列容器與向量容器實現結構類似,甚至在提供的介面方法上也基本相同,僅僅有一小部分的區別。讀者可以通過表16.2,瞭解雙端佇列deque對外公開提供的操作介面基本情況,以便在應用程式中正確地操作使用。
表格16.2 雙端佇列deuqe公開方法介面說明
介面方法名稱 |
基本功能說明 |
d.assign(n,elem) |
n個elem元素取代當前佇列容器中元素 |
d.assign(first,end) |
迭代器first到end區間的元素取代當前佇列中元素 |
d.at(n) |
訪問當前佇列n位置處的元素,並返回該元素 |
d.back() |
返回當前佇列尾部元素 |
d.begin() |
返回當前佇列第一個元素的迭代器 |
d.clear() |
清空當前佇列中所有元素 |
d.empty() |
判斷當前佇列是否為空,如果為空則返回true |
d.end() |
返回當前佇列中最後一個元素的迭代器 |
d.erase(first,end) |
刪除當前佇列迭代器first到end所指區間的元資料 |
d.erase(iter) |
刪除當前佇列迭代器iter所指位置元素 |
d.front() |
返回當前佇列起始位置元素 |
d.insert(iter,elem) |
當前佇列迭代器iter位置處插入元素elem |
d.insert(iter,first,end) |
將迭代器first到end區間的元素插入到當前佇列iter所指位置 |
d.insert(iter,num,elem) |
將num個elem元素插入到當前佇列iter所指位置處 |
d.max_size() |
返回當前佇列容器當前最大容量 |
d.pop_back() |
刪除當前佇列中最後一個元素 |
d.pop_front() |
刪除當前佇列中第一個元素 |
d.push_back(elem) |
當前佇列的尾部新增元素elem |
d.push_front(elem) |
當前佇列的頭部新增元素elem |
d.rbegin() |
返回當前佇列反向的指向首元素的迭代器 |
d.resize(num,elem) |
將當前佇列大小調整為num,並且使用元素elem初始化容器 |
d.size() |
返回當前佇列的元素個數 |
d.swap(deque) |
交換當前佇列與deque佇列的內容 |
下面通過一個完整的雙端佇列介面操作的例項,幫助讀者瞭解雙端佇列deque介面使用的基本情況。
1.準備例項
開啟UE工具,建立新的空檔案並且另存為chapter1602.cpp。該程式碼檔案隨後會同makefile檔案一起通過FTP工具傳輸至Linux伺服器端,客戶端通過scrt工具訪問操作。程式程式碼檔案編輯如下所示。
/**
* 例項chapter1602
* 原始檔chapter1602.cpp
* deque容器基本操作例項
*/
#include <iostream>
#include <deque>
using namespace std;
int main()
{
//第一部分程式碼
deque<char> charValue; //定義char型別佇列物件charValue
deque<char> charValue1; //定義char型別佇列物件charValue1
deque<char>::iterator iter; //定義char型別佇列迭代器iter
deque<char>::iterator tempIter; //定義char型別佇列迭代器tempIter
for(int i = 0;i < 10;i++) //for迴圈從變數i值為0迴圈10次
{
charValue.push_front(i+65); //通過佇列提供push_front方法從佇列前面插入元素,值為i+65
charValue1.push_front(i+65); //通過佇列提供push_front方法從佇列前面插入元素,值為i+65
}
cout<<"charValue elements:"; //列印輸出佇列中元素值
for(int i = 0;i < charValue.size();i++) //通過for迴圈,遍歷佇列長度
{
cout<<charValue.at(i)<<" "; //通過佇列的at()方法輸出內部元素值
}
cout<<endl;
//第二部分程式碼
charValue.assign(4,'A'); //通過佇列assign方法來替換內部元素,採用4個A替換所有元素
cout<<"charValue elements:";
for(int i = 0;i < charValue.size();i++) //列印輸出替換後的佇列中元素值
{
cout<<charValue[i]<<" ";
}
cout<<endl;
//第三部分程式碼
iter = charValue.begin(); //通過begin()方法將迭代器iter指向佇列容器charValue首個元素
charValue.insert(iter,'B'); //通過insert()方法將字元B插入iter指向的位置
charValue.insert(iter,4,'C'); //通過insert()方法將4個字元C插入iter指向的位置
cout<<"charValue elements:"; //列印輸出插入操作後的佇列容器中的元素值
for(iter = charValue.begin();iter != charValue.end();iter++)
{
cout<<*iter<<" ";
}
cout<<endl;
//第四部分程式碼
cout<<"charVa