- 配接器(adapters)在 STL組件的靈活組合運用功能上,扮演著軸承、轉換器的角色。Adapter這個概念,事實上是一種設計模式(design pattern)。 ?Design Patterns)) 一書提到23個最普及的設計模式,其中對odopter樣式的定義如下:將 一個class的接口轉換為另一個class的接口,使原本因接口不兼容而不能合作的classes,可以一起運作。
8 . 1 配接器之概觀與分類
- S T L所提供的各種配接器中,改變仿函數(functors)接口者,我們稱為functionadapter,改變容器(containers)接口者,我們稱為container adapter,改變迭代器 fi (iterators)接口者,我們稱為 iterator adapter
8.1.1 應 用 于 容 器 ,container adapters
- S TL提供的兩個容器queue和 s ta c k ,其實都只不過是一種配接器。它們修 飾 deque的接口而成就出另一種容器風貌。這兩個container adapters已于第4章介紹過。
8.1.2 應 用 于 迭 代 器 ,iterator adapters
- S T L 提供了許多應用于迭代器身上的配接器,包 括 insert iterators, reverse iterators, iostream iterators. C++ Standard 規定它們的接口可以藉由 <iterator>獲得,SGI S T L則將它們實際定義于 <stl_iterator.h>
Insert Iterators
- 所 謂 insert iterators, 可以將一般迭代器的賦值(assign)操作轉變為插入 (insert}-操作。這樣的迭代器包括專司尾端插入操作的back_insert_-iterator, 專司頭端插入操作的fro n t_ in se rt_ ite ra to r,以及可從任意位置執行插入操作的 insert_i te ra to ro 由于這三個iterator adapters的使用接口不是十分直觀,給一般用戶帶來困擾,因此,STL更提供三個相應函數: back_inserter()、front_inserter()、 inserter(), 如圖8-1所示,提升使用時的便利性。
Reverse Iterato rs
- 所謂reverse iterators,可以將一般迭代器的行進方向逆轉,使原本應該前進的 operator++ 變成了后退操作,使原本應該后退的 operator-- 變成了前進操作。 這種錯亂的行為不是為了掩人耳目或為了欺敵效果,而是因為這種倒轉筋脈的性質運用在“從尾端開始進行”的算法上,有很大的方便性? 稍后我有一些范例展示
lO S tream Iterato rs
- 所 謂 iostream iterators,可以將迭代器綁定到某個iostream對象身上。綁定 到 istream 對 象 (例 如 std::cin)身上的,稱 為 istream _ iterator,擁有輸入功 能 ; 綁 定 到 ostream 對 象 (例 如 std::cout ) 身 上 的 ’稱為 ostream _iterator,擁有輸出功能。這種迭代器運用于屏幕輸出,非常方便。以它為藍圖,稍加修改,便可適用于任何輸出或輸入裝置上.例如,你可以在透徹了解 iostream Iterators的技術后,完成一個綁定到Internet Explorer cache身上的迭代器1 , 或是完成一個系結到磁盤目錄上的一個迭代器2。
- 請注意,不像稍后即將出場的仿函數配接器(functoradapters)總以仿函數作為參數,予 人 以 “拿某個配接器來修飾某個仿函數”的直觀感受,這里所介紹的迭代器配接器(iterator adapters)很少以迭代器為直接參數% 所謂對迭代器的修 飾,只是一種觀念上的改變(賦值操作變成插入操作啦、前進變成后退啦、綁定到特殊裝置上啦…) 。你可以千變萬化地寫出適合自己所用的任何迭代器? 就這一點而言,為了將S T L 靈活運用于你的日常生活之中,iterator adapters的技術是非常重要的。
#include <iostream>
#include <algorithm>
#include <iterator>
#include <set>
#include <vector>
#include <functional>
#include <deque>template<class T>
struct display{void operator()(const T&x){std::cout << x << ' ';}
};struct even{bool operator()(int x)const{return x%2 ? false : true;}
};int main(int argc,char* argv[]){//將 outite綁定到cout 每次對outite指派一個兀素,就后接一個 ""std::ostream_iterator<int>outite(std::cout," ");int ia[] = {0,1,2,3,4,5};std::deque<int>id(ia,ia+6);//將所有元素拷貝到outite (那么也就是拷貝到cout)std::copy(id.begin(),id.end(),outite); //輸出 0 1 2 3 4 5std::cout << std::endl;// 將 ia []的部分元素拷貝到id 內。使 用 front_insert_iterator<>// 注意,front_insert_iterqtor 會將 assign 操作改為 push_front 操作// vector不支持push_front (),這就是本例不以vector為示范對象的原因。std::copy(ia+1,ia+2,std::front_inserter(id));std::copy(id.begin(),id.end(),outite); //輸出 1 0 1 2 3 4 5std::cout << std::endl;//將 ia []的部分元素拷貝到i d 內。使 用 back_insert_iterator.std::copy(ia+3,ia+4,std::back_inserter(id));std::copy(id.begin(),id.end(),outite); //輸出 1 0 1 2 3 4 5 3std::cout << std::endl;//搜索元素 5 所在的位置std::deque<int>::iterator ite = std::find(id.begin(),id.end(),5);//將 ia[]的部分元素拷貝到i d 內。使 用 insert_iteratorstd::copy(ia+0,ia+3,std::inserter(id,ite));std::copy(id.begin(),id.end(),outite); //輸出 1 0 1 2 3 4 0 1 2 5 3std::cout << std::endl;//將所有的元素逆向拷貝//rbegin ()和 rend ()與 reverse_iterator有關,見稍后源代碼說明std::copy(id.rbegin(),id.rend(),outite);std::cout << std::endl;//以下,將inite綁定到cin.將元素拷貝到inite,直到eos出現std::istream_iterator<int>inite(std::cin),eos;//eos :end-of-streamstd::copy(inite,eos,std::inserter(id,id.begin()));// 由于很難在鍵盤上直接輸入end-of-stream (end-of-file)符號// (而且不同系統的eof符號也不盡相同),因此,為了讓上一行順利執行,// 請單獨取出此段落為一個獨立程序,并準備一個文件,例 如 int.dat,內置 // 32 26 99 (自由格式),并 在 console之下利用piping方式執行該程序,如下:// c :\>type int.dat I thisprog / / 意思是將type int .dat的結果作為thisprog的輸入std::copy(id.begin(),id.end(),outite);return 0;
}
8.1.3 應用于仿函數 , functor adapters
- unctor adapters (亦稱為function adapters) 是所有配接器中數量最龐大的一個族群,其配接靈活度也是前二者所不能及,可以配接、配接、再配接。這些配接操作包括系結(bind)、否 定 (negate), 組 合 (compose)、以及對一般函數或成員函數的修飾(使其成為一個仿函數)。 Standard規定這些配接器的接口可由<functional>獲得,SGI S T L 則將它們實際定義于 <stl_function.h>
- function adapters的價值在于,通過它們之間的綁定、組合、修飾能力,幾乎 可以無限制地創造出各種可能的表達式(expression), 搭配STL算法一起演出
- 例如,我們可能希望找出某個序列中所有不小于12的元素個數.雖然, “不小于”就是 “大于或等于”,我們因此可以選擇STL內建的仿函數greater_equal,但 如果希望完全遵循題目語意(在某些更復雜的情況下,這可能是必要的),堅持找出 “不小于” 12的元素個數,可以這么做:
- ?請注意,所有期望獲得配接能力的組件,本身都必須是可配接的(adaptable) 換句話說,一元仿函數必須繼承自unary_function (7.1.1節 ),二元仿函數必須繼承自binary_function (7.1.2 節 ) ,成員函數必須以mem_fun處理過,一般函數必須以ptr_fun處理過。一個未經ptr_fun處理過的一般函數,雖然也可以函 數指針(pointer to function)的形式傳給STL算法使用 卻無法擁有任何配接能力。
void print(int i){std::cout << i << std::endl;
}class Int{
public:explicit Int(int i) : m_i(i){};//這里有個既存的成員函數(稍后希望于STL體系中被復用)void print1() const {std::cout << '[' << m_i << ']';}private:int m_i;
};int main(int argc,char* argv[]){//將 outite綁定到cout 每次對outite指派一個兀素,就后接一個 ""std::ostream_iterator<int>outite(std::cout," ");int ia[6] = {2,21,12,7,19,23};std::vector<int>iv(ia,ia+6);//以下將所有元素拷貝到 outite. 有數種辦法std::copy(iv.begin(),iv.end(),outite);//1,以函數指針搭配STL算法std::for_each(iv.begin(),iv.end(), print);//2,以修飾過的一般函數搭配STL算法std::for_each(iv.begin(),iv.end(),std::ptr_fun(print));Int t1 (3), t2 (7), t3 (20),t4(14), t5(68);std::vector<Int> Iv;Iv.push_back(t1);Iv.push_back(t2);Iv.push_back(t3);Iv.push_back(t4);Iv.push_back(t5);//3,以下,以修飾過的成員函數搭配STL算法std::for_each(Iv.begin(),Iv.end(),std::mem_fun_ref(&Int::print1));return 0;
}
?8.2 container adapters
8.2.1 stack
stack的底層由deque構成。從以下接口可清楚看出stack與deque的關系:
- ?C++ standard 規定客戶端必須能夠從<stack>中獲得stack的接口,SGI STL 則把所有的實現細節定義于的<stl_stack.h>內,請參考4.5節。class stack封住了所有的deque對外接口,只開放符合stack原則的幾個函數,所以我們說 stack是一個配接器,一個作用于容器之上的配接器。
?8.2.2 queue
- queue的底層由deque構成。從以下接口可清楚看出queue與deque的關系:
- ?規定客戶端必須能夠從 <queue> 中獲得queue的接口,SGI STL 則把所有的實現細節定義于<stl_queue.h>內,請參考4.6節。class queue封住了所有的deque對外接口,只開放符合queue原則的幾個函數,所以我們說 queue是一個配接器,一個作用于容器之上的配接器。
8.3 iterator adapters
- 本章稍早已說過iterator adapters的意義和用法,以下研究其實現細節
8.3.1 insert iterators
- 下面是三種insert iterators的完整實現列表。其中的主要觀念是,每一個 insert iterators內部都維護有一個容器(必須由用戶指定):容器當然有自己的迭代器,于是,當客戶端對insert iterators做賦值(tm i敢)操作時,就在insert iterators中被轉為對該容器的迭代器做插入(insert)操作,也就是說,在 insert iterators的operator?=操作符中調用底層容器的push_front ()或push_back ()或 insert () 操 作 函 數 。
- 至 于 其 它 的 迭 代 器 慣 常 行 為 如 operator++, operator++ (int) ?operator*都被關閉功能,更 沒 有 提 供 operator-- (int)或 operator--或 operator->等功能(因此被類型被定義為output?iterator_tag)
- ?換句話說,insert iterators的前進、后退、取值、成員取用等操作都是沒有意義的,甚至是不允許的。
8.3.2 reverse iterators
- 所謂reverse iterator,就是將迭代器的移動行為倒轉。如果STL算法接受的不 是一般正常的迭代器,而是這種逆向迭代器,它就會以從尾到頭的方向來處理序列中的元素。例如:
- 首先我們看看rbeginO和 rend().任何STL容器都提供有這兩個操作, 我在第4, 5兩章介紹各種容器時,鮮有列出這兩個成員函數,現在舉個例子瞧瞧:
- ?沒有任何例外!只要雙向序列容器提供了 begin(), end(),它 的 rbegin、rend() 就是上面那樣的型式。單向序列容器如slist不可使用reserve iterators. 有些容器如 stack、queue、priority .qu eue 并不提供begin(), end(), 當然也就沒有 rbegin/ rend()
- ?為什么 “正向迭代器”和 “與其相應的逆向迭代器”取出不同的元素呢?這并不是一個潛伏的錯誤,而是一個刻意為之的特征,主要是為了配合迭代器區間的“前閉后開”習 慣 (1.9.5節 ) . 從圖8-3的 rbeginO和 end()關系可以看出, 當迭代器被逆轉方向時,雖然其實體位置(真正的地址)不變,但其邏輯位置(迭代器所代表的元素)改變了(必須如此改變):
- ?唯有這樣,才能保持正向迭代器的一切慣常行為。換句話說,唯有這樣,當我們將一個正向迭代器區間轉換為一個逆向迭代器區間后,不必再有任何額外處理,就可以讓接受這個逆向迭代器區間的算法,以相反的元素次序來處理區間中的每一個元素.例如(以下出現于8.1.2節實例之中):
- 有了這些認知,現在我們來看看reverse_iterator的源代碼
- ?這是一個迭代器配接器(iter己tor adapter), 用來將某個迭代器逆反前進方向
- 使前進為后退,后退為前進
?
?
8.3.3 stream iterators?
- 所 謂 streamfterotors,可以將迭代器綁定到一個stream (數據流)對象身上。 綁 定 到 istrea m對象 (例如 std::cin ) 者,稱 為 istream_iterator ,擁有輸入能力;
- 綁定到ostream 對 象 (例 如 std:: cout) 者,稱為 擁有輸出能力。兩者的用法在8.1.2節的例子中都有示范
- 乍聽之下真神奇。所謂綁定一個i stream object,其實就是在isiream iterator內部維護一個istream member,客戶端對于這個迭代器所做的operator++操 作 ,會 被 導 引 調 用 迭 代 器 內 部 所 含 的 那 個 istream member的輸入操作(operator?) 。這個迭代器是個Input Iterator,不具備operator--o下面的源代碼和注釋說明了一切。
?8,4 function adapters
- 一般而言,對于C++ template語法有了某種程度的了解之后,我們很能夠理解或想象,容器是以class templates完成,算 法 以 function templates完成,仿函數是一種將operator()?重 載 的 class template,
- 迭代器則是一種將 operator++和 operator*等指針習慣常行為重載的class template.然而配接器呢?應用于容 器身上和迭代器身上的配接器,已于本章稍早介紹過,都是一種class template.可 應用于仿函數身上的配接器呢?如 何 能 夠 “事先”對一個函數完成參數的綁定、
執行結果的否定、甚至多方函數的組合?請注意我用“事先” 一詞。我的意思是,最后修飾結果(視為一個表達式,expression) 將被傳給STL算法使用,STL算法 才是真正使用這表達式的主格。而我們都知道,只有在真正使用(調用)某個函數(或仿函數)時,才有可能對參數和執行結果做任何干涉。
8.4.1 對返回值進行邏輯否定:not1,not2
- 以下直接列出源代碼。源代碼中的注釋配合先前的概念解說,應該足以讓你徹底認識這些仿函數配接器。源代碼中常出現的pred 一詞,是 predicate的縮寫, 意指會返回真假值(bool)的表達式。
?
#include <iostream>
#include <algorithm>
#include <iterator>
#include <set>
#include <vector>
#include <functional>
#include <deque>class Shape{
public:virtual void display() = 0;
};class Rect : public Shape{
public:virtual void display() {std::cout << "Rect ";}
};class Circle : public Shape{
public:virtual void display() {std::cout << "Circle ";}
};class Square : public Shape{
public:virtual void display() {std::cout << "Square ";}
};int main(int argc,char* argv[]){std::vector<Shape*>Iv{};Iv.push_back(new Rect);Iv.push_back(new Circle);Iv.push_back(new Square);Iv.push_back(new Circle);Iv.push_back(new Rect);//打印 輸出for (int i = 0; i < Iv.size(); ++i) {(Iv[i])->display();}std::cout << std::endl;std::for_each(Iv.begin(),Iv.end(),std::mem_fun(&Shape::display));std::cout << std::endl;return 0;
}
?
?