《C++語言程式設計基礎》學習第十章泛型程式設計與C++標準模板庫
STL簡介:標準模板庫(Standard Template Library,簡稱STL)提供了一些非常常用的資料結構和演算法 標準模板庫(Standard Template Library,簡稱STL)定義了一套概念體系,為泛型程式設計提供了邏輯基礎 STL中的各個類模板、函式模板的引數都是用這個體系中的概念來規定的。 使用STL的模板時,型別引數既可以是C++標準庫中已有的型別,也可以是自定義的型別——只要這些型別是所要求概念的模型。STL的基本元件:容器(container);迭代器(iterator);函式物件(function object);演算法(algorithms)STL的基本元件間的關係
STL的基本元件——容器(container): 容納、包含一組元素的物件。基本容器類模板:順序容器:array(陣列)、vector(向量)、deque(雙端佇列)、forward_list(單鏈表)、list(列表)(有序)關聯容器:set(集合)、multiset(多重集合)、map(對映)、multimap(多重對映)無序關聯容器:
STL的基本元件——迭代器(iterator): 迭代器是泛化的指標,提供了順序訪問容器中每個元素的方法 提供了順序訪問容器中每個元素的方法; 可以使用“++”運算子來獲得指向下一個元素的迭代器; 可以使用“*”運算子訪問一個迭代器所指向的元素,如果元素型別是類或結構體,還可以使用“->”運算子直接訪問該元素的一個成員; 有些迭代器還支援通過“--”運算子獲得指向上一個元素的迭代器; 迭代器是泛化的指標:指標也具有同樣的特性,因此指標本身就是一種迭代器; 使用獨立於STL容器的迭代器,需要包含標頭檔案。
STL的基本元件——函式物件(function object): 一個行為類似函式的物件,對它可以像呼叫函式一樣呼叫。 函式物件是泛化的函式:任何普通的函式和任何過載了“()” 運算子的類的物件都可以作為函式物件使用 使用STL的函式物件,需要包含標頭檔案
STL的基本元件——演算法(algorithms): STL包括70多個演算法,例如:排序演算法,消除演算法,計數演算法,比較演算法,變換演算法,置換演算法和容器管理等 可以廣泛用於不同的物件和內建的資料型別。 使用STL的演算法,需要包含標頭檔案。
#include "pch.h"
#include <iostream>
#include<vector>
#include<iterator>
#include<algorithm>
#include<functional>
using namespace std;
int main(){
const int N = 5;
vector<int> s(N);
for (int i = 0; i < N; i++)
cin >> s[i];
transform(s.begin(), s.end(), ostream_iterator<int>(cout, " "), negate<int>());
cout << endl;
return 0;
}
transform演算法的一種實現:
template <class InputIterator, class OutputIterator, class UnaryFunction>
OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryFunction op) {
for (;first != last; ++first, ++result)
*result = op(*first);
return result;
}
transform演算法順序遍歷first和last兩個迭代器所指向的元素; 將每個元素的值作為函式物件op的引數; 將op的返回值通過迭代器result順序輸出; 遍歷完成後result迭代器指向的是輸出的最後一個元素的下一個位置,transform會將該迭代器返回迭代器 迭代器是演算法和容器的橋樑:迭代器用作訪問容器中的元素;演算法不直接操作容器中的資料,而是通過迭代器間接操作 演算法和容器獨立:增加新的演算法,無需影響容器的實現;增加新的容器,原有的演算法也能適用輸入流迭代器和輸出流迭代器輸入流迭代器: 以輸入流(如cin)為引數構造;可用*(p++)獲得下一個輸入的元素
istream_iterator<T>
輸出流迭代器: 構造時需要提供輸出流(如cout);可用(*p++) = x將x輸出到輸出流
ostream_iterator<T>
二者都屬於介面卡:介面卡是用來為已有物件提供新的介面的物件;輸入流介面卡和輸出流介面卡為流物件提供了迭代器的介面
#include "pch.h"
#include <iostream>
#include<algorithm>
#include<iterator>
using namespace std;
template<class T>
T square(T x) {
return x * x;
}
int main(){
//從標準輸入讀入若干個實數,分別將它們的平方輸出
//template<class T>
transform(istream_iterator<double>(cin), istream_iterator<double>(),
ostream_iterator<double>(cout, "\t"), square<double>);
cout << endl;
return 0;
}
迭代器的分類
迭代器支援的操作 迭代器是泛化的指標,提供了類似指標的操作(諸如++、*、->運算子) 輸入迭代器:可以用來從序列中讀取資料,如輸入流迭代器 輸出迭代器:允許向序列中寫入資料,如輸出流迭代器 前向迭代器:既是輸入迭代器又是輸出迭代器,並且可以對序列進行單向的遍歷 雙向迭代器:與前向迭代器相似,但是在兩個方向上都可以對資料遍歷 隨機訪問迭代器:也是雙向迭代器,但能夠在序列中的任意兩個位置之間進行跳轉,如指標、使用vector的begin()、end()函式得到的迭代器
迭代器的區間: 兩個迭代器表示一個區間:[p1, p2) STL演算法常以迭代器的區間作為輸入,傳遞輸入資料 合法的區間:p1經過n次(n > 0)自增(++)操作後滿足p1 == p2 區間包含p1,但不包含p2
//將來自輸入迭代器的n個T型別的數值排序,將結果通過輸出迭代器result輸出
template<class T,class InputIterator,class OutputIterator>
void mySort(InputIterator first, InputIterator last, OutputIterator result) {
//通過輸入迭代器將輸入資料存入向量容器s中
vector<T> s;
for (; first != last; ++first)
s.push_back(*first);
//對s進行排序,sort函式的引數必須是隨機訪問迭代器
sort(s.begin(), s.end());
copy(s.begin(), s.end(), result);//將s序列通過輸出迭代器輸出
}
int main() {
//將s陣列的內容排序後輸出
double a[5] = { 1.2,2.4,0.9,3.3,3.2 };
mySort<double>(a, a + 5, ostream_iterator<double>(cout, " "));
cout << endl;
//從標準輸入讀入若干個整數,將排序後的結果輸出
mySort<int>(istream_iterator<int>(cin), istream_iterator<int>(),
ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
執行的結果:
0.9 1.2 2.4 3.2 3.3
2 -4 5 9 -1 3 6 -5
-5 -4 -1 2 3 5 6 9
迭代器的輔助函式 advance(p, n):對p執行n次自增操作 distance(first, last):計算兩個迭代器first和last的距離,即對first執行多少次“++”操作後能夠使得first == last
容器的基本功能與分類: 容器類是容納、包含一組元素或元素集合的物件;基於容器中元素的組織方式:順序容器、關聯容器;按照與容器所關聯的迭代器型別劃分:可逆容器隨機訪問容器順序容器:array(陣列)、vector(向量)、deque(雙端佇列)、forward_list(單鏈表)、list(列表)(有序)關聯容器:set(集合)、multiset(多重集合)、map(對映)、multimap(多重對映)無序關聯容器:unorderedset (無序集合)、unorderedmultiset(無序多重集合);unorderedmap(無序對映)、unordermultimap(無序多重對映)容器的分類
容器的通用功能 容器的通用功能:用預設建構函式構造空容器;支援關係運算符:==、!=、<、<=、>、>=; begin()、end():獲得容器首、尾迭代器;clear():將容器清空;empty():判斷容器是否為空;size():得到容器元素個數;s1.swap(s2):將s1和s2兩容器內容交換 相關資料型別(S表示容器型別):S::iterator:指向容器元素的迭代器型別;S::const_iterator:常迭代器型別對可逆容器的訪問: STL為每個可逆容器都提供了逆向迭代器,逆向迭代器可以通過下面的成員函式得到:rbegin() :指向容器尾的逆向迭代器;rend():指向容器首的逆向迭代器 逆向迭代器的型別名的表示方式如下:S::reverse_iterator:逆向迭代器型別;S::constreverseiterator:逆向常迭代器型別隨機訪問容器:隨機訪問容器支援對容器的元素進行隨機訪問:s[n]:獲得容器s的第n個元素順序容器的特性: 順序容器:向量、雙端佇列、列表、單向連結串列、陣列向量(Vector) 特點:一個可以擴充套件的動態陣列;隨機訪問、在尾部插入或刪除元素快;在中間或頭部插入或刪除元素慢 向量的容量:容量(capacity):實際分配空間的大小;s.capacity() :返回當前容量;s.reserve(n):若容量小於n,則對s進行擴充套件,使其容量至少為n雙端佇列(deque) 特點:在兩端插入或刪除元素快;隨機訪問較快,但比向量容器慢;在中間插入或刪除元素慢 先按照從大到小順序輸出奇數,再按照從小到大順序輸出偶數。
#include "pch.h"
#include <iostream>
#include<deque>
#include<vector>
#include<algorithm>
#include<iterator>
using namespace std;
int main(){
istream_iterator<int> i1(cin), i2;//建立一對輸入流迭代器
vector<int> s1(i1, i2);//通過輸入流迭代器從標準輸入流中輸入資料
sort(s1.begin(), s1.end());//將輸入的整數排序
deque<int> s2;
//以下迴圈遍歷s1
for (vector<int>::iterator iter = s1.begin(); iter != s1.end(); ++iter) {
if (*iter % 2 == 0)//偶數放到s2尾部
s2.push_back(*iter);
else
s2.push_front(*iter);
}
//將s2的結果輸出
copy(s2.begin(), s2.end(), ostream_iterator<int>(cout, " "));
cout << endl;
return 0;
}
列表(list) 特點:在任意位置插入和刪除元素都很快;不支援隨機訪問 接合(splice)操作:s1.splice(p, s2, q1, q2):將s2中[q1, q2)移動到s1中p所指向元素之前
#include "pch.h"
#include <iostream>
#include<string>
#include<list>
#include<iterator>
using namespace std;
int main(){
string names1[] = { "Alice","Helen","Lucy","Susan" };
string names2[] = { "Bob","David","Levin","Mike" };
//用names1陣列的內容構造列表s1
list<string>s1(names1, names1 + 4);
//用names2陣列的內容構造列表s2
list<string>s2(names2, names2 + 4);
//將s1的第一個元素放到s2的最後
//等價於s2.splice(s2.end(), s1, s1.begin(),++s1.begin());
s2.splice(s2.end(), s1, s1.begin()); //s2.end()指向s2最後元素的後一個
list<string>::iterator iter1 = s1.begin();//iter1指向s1首
advance(iter1, 2);//iter1前進2個元素,它將指向s1第3個元素
list<string>::iterator iter2 = s2.begin();//iter2指向s2首
++iter2;//iter2前進1個元素,它將指向s2第2個元素
list<string>::iterator iter3 = iter2;//用iter2初始化iter3
advance(iter3, 2);//iter3前進2個元素,它將指向s2的第4個元素
//將[iter2,iter3)範圍內的節點接到s1中iter1指向的節點前
s1.splice(iter1, s2, iter2, iter3);
//分別將s1和s2輸出
copy(s1.begin(), s1.end(), ostream_iterator<string>(cout, " "));
cout << endl;
copy(s2.begin(), s2.end(), ostream_iterator<string>(cout, " "));
cout << endl;
return 0;
}
順序容器的插入迭代器: 用於向容器頭部、尾部或中間指定位置插入元素的迭代器 包括前插迭代器(frontinserter)、後插迭代器(backinsrter)和任意位置插入迭代器(inserter)
list<int> s;
back_inserter iter(s);
*(iter++) = 5; //通過iter把5插入s末尾
順序容器的介面卡: 以順序容器為基礎構建一些常用資料結構,是對順序容器的封裝 棧(stack):最先壓入的元素最後被彈出 佇列(queue):最先壓入的元素最先被彈出 優先順序佇列(priority_queue):最“大”的元素最先被彈出棧和佇列模板 棧模板
template <class T, class Sequence = deque<T> > class stack;
佇列模板
template <class T, class FrontInsertionSequence = deque<T> > class queue;
棧可以用任何一種順序容器作為基礎容器,而佇列只允許用前插順序容器(雙端佇列或列表)棧和佇列共同支援的操作: s1 op s2 op可以是==、!=、<、<=、>、>=之一,它會對兩個容器介面卡之間的元素按字典序進行比較 s.size() 返回s的元素個數 s.empty() 返回s是否為空 s.push(t) 將元素t壓入到s中 s.pop() 將一個元素從s中彈出,對於棧來說,每次彈出的是最後被壓入的元素,而對於佇列,每次被彈出的是最先被壓入的元素 不支援迭代器,因為它們不允許對任意元素進行訪問棧和佇列不同的操作: 棧的操作:s.top() 返回棧頂元素的引用 佇列操作:s.front() 獲得隊頭元素的引用;s.back() 獲得隊尾元素的引用
優先順序佇列:優先順序佇列也像棧和佇列一樣支援元素的壓入和彈出,但元素彈出的順序與元素的大小有關,每次彈出的總是容器中最“大”的一個元素。template <class T, class Sequence = vector<T> > class priority_queue;
優先順序佇列的基礎容器必須是支援隨機訪問的順序容器。
支援棧和佇列的size、empty、push、pop幾個成員函式,用法與棧和佇列相同。
優先順序佇列並不支援比較操作。
與棧類似,優先順序佇列提供一個top函式,可以獲得下一個即將被彈出元素(即最“大”的元素)的引用。
#include "pch.h"
#include <iostream>
#include<queue>
#include<time.h>
#include<stdlib.h>
using namespace std;
const int SPLIT_TIME_MIN = 500;//細胞分裂最短時間
const int SPLIT_TIME_MAX = 2000;//細胞分裂最長時間
class Cell;
priority_queue<Cell> cellQueue;
class Cell { //細胞類
private:
static int count; //細胞總數
int id; //當前細胞編號
int time; //細胞分裂時間
public:
Cell(int birth) :id(count++) {//birth為細胞誕生時間
//初始化,確定細胞分裂時間
time = birth + (rand() % (SPLIT_TIME_MAX - SPLIT_TIME_MIN)) + SPLIT_TIME_MIN;
}
int getId() const { return id; } //得到細胞編號
int getSplitTime() const { return time; } //得到細胞分裂時間
bool operator <(const Cell& s) const{ //定義“<”
return time > s.time;
}
void split() { //細胞分裂
Cell child1(time), child2(time); //建立兩個子細胞
cout << time << "s: Cell #" << id << " splits to #" << child1.getId()
<< " and #" << child2.getId() << endl;
cellQueue.push(child1);//將子細胞壓入優先順序佇列
cellQueue.push(child2);//將子細胞壓入優先順序佇列
}
};
int Cell::count = 0;
int main(){
srand(static_cast<unsigned>(time(0)));
int t;//模擬時間長度
cout << "Simulation time: ";
cin >> t;
cellQueue.push(Cell(0));//將細胞壓入優先順序佇列
while (cellQueue.top().getSplitTime() <= t) {
Cell x = cellQueue.top();
x.split();//模擬下個細胞的分裂
cellQueue.pop();//將剛剛分裂的細胞彈出
}
return 0;
}