1. 程式人生 > >C++泛型程式設計

C++泛型程式設計

面向物件程式設計關注資料,泛型程式設計關注演算法。

模板和迭代器:都是STL通用方法的組成部分,模板讓演算法獨立於資料型別,迭代器讓演算法獨立於容器型別。例如,對於在陣列和在連結串列中查詢特定值節點的find函式,模板提供了儲存在容器中的資料型別的通用表示,還需要提供遍歷容器中值的通用表示,這就是迭代器,理解迭代器的使用,類比模板。

迭代器需要注意的概念:

1.超尾

STL設計中所有區間都是形如[a, b)的,因此遍歷一個完整的容器,最後是一個超尾,即指向最後一個元素後面的地址。對於陣列這來說,最後一個元素後面的地址是可以獲取到的,可以直接用來判斷是否到達超尾,對於連結串列,可以使用NULL充當超尾。

2.p++和++p

例如,對一個連結串列的迭代器,需要設計一個迭代器類,為這個類過載++,如下:

iterator & operator++()
{
    p = p->next;
    return * this;
}

iterator operator++(int)
{
    iterator tmp = *this;
    p = p->next;
    return tmp;
}

這裡注意兩個問題,一個是返回值,++p先++然後使用p,因此直接返回*this即可,返回值是引用;對於p++,先使用p然後++,因此使用一個臨時的迭代器tmp儲存當前*this,最後返回的是區域性變數tmp,不能使用引用,必須是值。另一個問題是為了區分兩種版本,p++使用了一個int型別的引數,這個引數僅僅用來提示編譯器,因此不需要提供形參名。

對於p++的理解:p是連結串列節點,這個節點是迭代器類的private資料。使用迭代器的時候,建立一個迭代器物件,這個物件中的私有資料部分p指向一個連結串列節點,這個時候說這個迭代器當前訪問的節點是p,迭代器能遍歷連結串列,意思就是p一直在移動,每次指向一個連結串列節點,相當於迭代器一直在移動。要訪問迭代器當前結點的值,需要定義一個operator*方法對p解引用 。

使用迭代器的時候,不需要知道內部是怎麼實現的。一般不顯式使用迭代器,而是使用STL函式,比如需要遍歷的時候,使用for_each函式。C++11和ruby等指令碼語言中使用如下方式遍歷:

int scores[2] = {1, 2};
for (x : scores)
    cout << x << endl;

迭代器和容器主導權:容器需要適應迭代器的要求。

STL定義5種迭代器,層級關係如下,其中下層具備上層的所有功能,還有自己額外的功能

輸入和輸出是對程式來說的,對於上面的find函式,使用輸入迭代器即可,含義是從容器中讀取值,作為輸入傳給程式

1.輸入迭代器

只讀、單通行、只能遞增。意思是,每次遍歷和上一次遍歷的順序不保證一致,迭代器遞增後,先前值不一定可以解引用。find可以使用輸入迭代器,因為只需要一次遍歷

2.輸出迭代器

只寫、單通行、只能遞增。只寫的例子是,cout到螢幕

3.正向迭代器

讀寫、多次同行。保證每次遍歷按照相同的順序,對正向迭代器遞增後,若儲存了前面的值,可以解引用,可以加const變成只讀

4.雙向迭代器

支援遞減

5.隨機訪問迭代器

支援隨機訪問和用於元素比較的關係運算符

概念、模型、改進:對應面向物件程式設計中的類、物件、繼承,如下:

陣列和STL容器:陣列不是STL容器,但是對於STL來說,迭代器是STL演算法的入口,指標是迭代器,因此STL演算法可以使用指標來對基於指標的非STL容器進行操作。

STL演算法:所謂演算法,即一系列解決問題的清晰指令,對於STL來說,find(),sort(),copy()這些函式就是演算法。迭代器是STL演算法入口的意思是,這些演算法操作的是迭代器,也就是說只能通過迭代器才能使用這些演算法

STL預定義迭代器:

1.ostream_iterator類迭代器

是輸出流的迭代器,可以用於cout或者fout,格式如下:

ostream_iterator<int, char>outer(cout, "");

2.istream_iterator類迭代器

是輸入流的迭代器,使用的例子如下:

copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(),
        dice.begin());

使用cin作為建構函式引數表示使用cin管理的輸入流,無引數表示讀取失敗,例如讀取到了檔案結尾或者其他問題

3.reverse_iterator類迭代器

對其遞增將導致遞減,不直接遞減的原因是為了使用現有的STL演算法,比如copy,在前兩個引數的區間之間是遞增的,使用如下:

copy(dice.rbegin(), dice.rend(), osteram_iterator<int, char>(cout, ""));

4.back_insert_iterator,front_insert_iterator,insert_iterator

dice.begin()這樣的迭代器不支援自動調整記憶體大小,會覆蓋,這3種迭代器可以,原因是,這些插入迭代器可以使用vector類的push_back等方法,而這些方法內部有動態記憶體管理機制。使用時將容器型別作為模板引數,將容器物件作為建構函式入參,原因是,需要知道容器型別,這樣才能呼叫vector::push_back等方法。如下:

dice.begin()       <===>      back_insert_iterator<vector<int> >back_iter(dice)

意思是,這兩種都是得到一個關於dice的迭代器。注意copy是一個演算法,調整容器記憶體是根據迭代器種類決定的,而不是copy

for_each演算法格式:

#include <algorithm>

void output(const int x)
{
    cout << x << endl;
}

for_each(dice.begin(), dice.end(), output);

容器概念、容器型別

7種序列容器:deque、list、queue、priority_queue、stack、vector、forward_list(C++11),序列要求元素線性排序。容器提供了一些方法應該使用,比如a[i]和a.at[i],都是返回第i個元素,at會對i越界的情況進行檢查並丟擲異常

vector:動態陣列,支援隨機訪問,動態改變大小,尾部操作O(1)時間,頭部操作O(N)時間

deque:雙端佇列,支援隨機訪問,頭尾操作都是O(1)

list:雙鏈表,提供兩個方法insert和splice,前者複製副本,後者將原始區間移動

forward_list:C++11中提供,單鏈表

queue:佇列,標準的先進先出佇列,是一個介面卡,底層使用deque

priority_queue:優先佇列,是介面卡,底層使用vector,預設是最大的元素放在隊首,可以自定義函式物件修改規則

stack:棧,是介面卡,底層是vector

C++11中的array不是STL容器,因為長度是固定的,但是和普通陣列一樣,一些copy,for_each演算法可以對其進行操作

關聯容器:鍵值對,通常用樹實現,有4種:set、multiset、map、multimap,set是集合,沒有鍵和值的概念,map是鍵值對

無序關聯容器:關聯容器是排序的,使用樹,查詢效率不高,無序關聯容器使用hash,提升查詢效率,c++11提供4種無序關聯容器:unordered_set,unordered_multiset,unordered_map,unordered_multimap