1. 程式人生 > >《C++語言程式設計基礎》學習第十章泛型程式設計與C++標準模板庫

《C++語言程式設計基礎》學習第十章泛型程式設計與C++標準模板庫

 STL簡介:標準模板庫(Standard Template Library,簡稱STL)提供了一些非常常用的資料結構和演算法 標準模板庫(Standard Template Library,簡稱STL)定義了一套概念體系,為泛型程式設計提供了邏輯基礎 STL中的各個類模板、函式模板的引數都是用這個體系中的概念來規定的。 使用STL的模板時,型別引數既可以是C++標準庫中已有的型別,也可以是自定義的型別——只要這些型別是所要求概念的模型。STL的基本元件:容器(container);迭代器(iterator);函式物件(function object);演算法(algorithms)STL的基本元件間的關係

Iterators(迭代器)是演算法和容器的橋樑。將迭代器作為演算法的引數、通過迭代器來訪問容器而不是把容器直接作為演算法的引數。 將函式物件作為演算法的引數而不是將函式所執行的運算作為演算法的一部分。 使用STL中提供的或自定義的迭代器和函式物件,配合STL的演算法,可以組合出各種各樣的功能。

 STL的基本元件——容器(container): 容納、包含一組元素的物件。基本容器類模板:順序容器:array(陣列)、vector(向量)、deque(雙端佇列)、forward_list(單鏈表)、list(列表)(有序)關聯容器:set(集合)、multiset(多重集合)、map(對映)、multimap(多重對映)​​​​​​無序關聯容器:

unorderedset (無序集合)、unorderedmultiset(無序多重集合)unorderedmap(無序對映)、unordermultimap(無序多重對映)容器介面卡:stack(棧)、queue(佇列)、priority_queue(優先佇列)使用容器,需要包含對應的標頭檔案

 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演算法順序遍歷firstlast兩個迭代器所指向的元素; 將每個元素的值作為函式物件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;
}