STL泛型编程之迭代器
前言
學習《C++ Primer Plus》中泛型編程的一些概念,感覺需要對這部分的知識理解的深一點(都是書上的內容,只不過經常搞不清,所以就抄了個筆記)。STL提供了一組表示容器、迭代器、函數對象和算法的模板。容器是一個與數組類似的單元,可以存儲若干個值。STL容器是同質的,即存儲的值的類型相同;算法是完成特定任務的“處方”;迭代器能夠用來遍歷容器的對象,與能夠遍歷數組的指針類似,是廣義指針;函數對象是類似于函數的對象,可以是類對象或函數指針(包括函數名,因為函數名被用作指針)。
?
泛型編程
面向對象編程關注的是編程的數據方面,而泛型編程關注的是算法。它們之間的共同點是抽象和創建可重用代碼,但它們的理念絕然不同。
泛型編程旨在編寫獨立于數據類型的代碼。在C++中,完成通用程序的工具是模板。當然,模板使得能夠按泛型定義函數或類,而STL通過通用算法更進一步。模板讓這一切成為可能,但必須對元素進行仔細地設計。為解模板和設計師如何協同工作的,來看一看需要迭代器的原因。
理解迭代器是理解STL的關鍵所在。模板使得算法獨立于存儲的數據類型,而迭代器使算法獨立于使用的容器類型。因此,它們都是STL通用方法的重要組成部分。
為了理解為何需要迭代器,我們來看如何為兩種不同數據表示實現find函數,然后看如何推廣這種方法。首先看一個在double數組中搜索特定值的函數,可以這樣編寫該函數:
double* find_ar(double *ar, int n, const double &val) {for(int i = 0; i < n; i++){if(ar[i] == val)return &ar[i];}return 0; }如果函數在數組中找到這樣的值,則返回該值在數組中的位地址,否則返回一個空指針。該函數使用下標來遍歷數組。可以用模板將這種算法推廣到包含==運算符、任意類型的數組。盡管如此,這種算法仍然與一種特定的數據結構(數組)關聯在一起。
?
下面來看搜索鏈表的情況。鏈表有鏈接在一起的Node結構組成:
struct Node {double item;Node *p_next; };假設有一個指向鏈表第一個節點的指針,每個節點的p_next指針都指向下一個節點,鏈表最后一個節點的p_next指針被設置為0,則可以這樣編寫find_ll()函數:
Node* find_ll(Node *head, const double &val) {Node *start;for(start = head; start != 0; start = start->p_next){if(start->item == val)return start;}return 0; }同樣,也可以使用模板將這種算法推廣到支持==運算符的任何數據類型的鏈表。然而,這種算法也是與特定的數據結構(鏈表)相關聯在一起。
從實現的細節上看,這兩個find函數的算法時不同的:一個使用數組索引來遍歷元素,另一個則將start重置為start->p_next。但從廣義上講,這兩種算法是相同的:將值依次與容器中的每個值進行比較,直到找到匹配的為止。
?
泛型編程旨在使用同一個find函數來處理數組、鏈表或任何其他的容器。即函數不僅獨立于容器中存儲的數據類型,而且獨立于容器本身的數據結構。模板提供了存儲在容器中的數據類型的通用表示,因此還需要遍歷容器中的值的通用表示,迭代器正是這樣的通用表示。
要實現find函數,迭代器應具備的特征如下:
(1)應能夠對迭代器執行解除引用的操作,以便能訪問它引用的值。即如果p是一個迭代器,則應該對*p進行定義。
(2)應能夠將一個迭代器賦給另一個。即如果p和q都是迭代器,則應對表達式p = q進行定義。
(3)應能夠將一個迭代器與另一個進行比較,看它們是否相等。即如果p和q都是迭代器,則應該對p == q和p != q進行定義。
(4)應能夠使用迭代器遍歷容器中的所有元素,這可以通過為迭代器p定義++p和p++來實現。
迭代器也可以完成其他的操作,但有上述功能就足夠了,至少對于find函數是如此。實際上,STL按功能的強弱定義了多種級別的迭代器。
?
順便說一句,常規指針就能滿足迭代器的要求,因此,可以這樣重新編寫find_arr()函數:
typedef double *iterator; iterator find_ar(iterator ar, int n, const double &val) {for(int i = 0; i < n; i++, ar++){if(*ar == val)return ar;}return 0; }然后可以修改函數參數,使之接受兩個指示區間的指針參數,其中的一個指向數組的起始位置,另一個指向數組的超尾;同時函數可以通過返回尾指針,來指出沒有找到要找的值。下面的find_arr()版本完成了這些修改:
typedef double *iterator; iterator find_ar(iterator begin, iterator end, const double &val) {iterator ar;for(ar =begin; ar != end; i++, ar++){if(*ar == val)return ar;}return end; }對于find_ll()函數,可以定義一個迭代器類,其中定義了運算符*和++:
struct Node {double item;Node *p_next; };class iterator {Node *pt; public:iterator():pt(0) {}iterator(Node *pn):pt(pn) {}double operator*() {return pt->item;}iterator &operator++(){pt = pt->p_next;return *this;}iterator operator++(int){iterator tmp = *this;pt = pt->p_next;return tmp;} };這里重點不是如何定義iterator類,而是有了這樣的類后,第二個find函數就可以這樣編寫:
iterator find_ll(iterator head, const double &val) {iterator start;for(start = head; start != 0; ++start){if(*start == val)return start;}return 0; }這和find_ar()幾乎相同,差別在于如何到達最后一個值。find_ar()函數使用超尾迭代器,而find_ll()使用存儲在最后一個節點中的空值。除了這種差別外,這兩個函數完全相同。例如,可以要求鏈表的最后一個元素后面還有一個額外的元素,即讓數組和鏈表都有超尾元素,并在迭代器達超尾位置時結束搜索。這樣,find_ar()和find_ll()監測數據尾的方式將相同,從而成為相同的算法。注意,增加超尾元素后,對迭代器的要求變成了對容器的要求。
?
STL遵循上面介紹的方法。首先,每個容器類(vector、llist、deque等)定義了相應的迭代器類型。對于其中的某個類,迭代器可能是指針;而對于另一個類,則可能是對象。不管實現方式如何,迭代器都將提供所需的操作,如*和++(有些類需要的操作可能比其他類多)。其次,每個容器類都有一個超尾標記,當迭代器遞增到超越容器的最后一個值后,這個值將被賦給迭代器。每個容器類都有begin()和end()方法,它們分別返回一個指向容器的第一個元素和超尾位置的迭代器。每個容器類都使用++操作,讓迭代器從指向第一個元素逐步指向超尾位置,從而遍歷容器中的每一個元素。
使用容器類時,無需知道其迭代器是如何實現的,也無需知道超尾是如何實現的,而只需知道它有迭代器,其begin()返回一個指向第一個元素的迭代器,end()返回一個指向超尾位置的迭代器即可。例如,假設要打印vector<double>對象中的值,則可以這樣做:
vector<double>::iterator pr; for(pr = scores.begin(); pr != scores.end(); pr++) {cout << *pr <<endl; }其中,下面的代碼行將pr的類型聲明為vector<double>類的迭代器:
vector<double>class: vector<double>::iterator pr;如果要使用list<double>類模板來存儲分數,則代碼如下:
list<double>::iterator pr; for(pr = scores.begin(); pr!= scores.end(); pr++) {cout << *pr <<endl; }唯一不同的是pr的類型。因此,STL通過為每個類定義適當的迭代器,并以統一的風格設計類,能夠對內部表示絕然不同的容器,編寫相同的代碼。使用C++11新增的自動類型推斷可進一步簡化:對于矢量或列表,都可使用如下代碼:
for(auto pr = scores.begin(); pr != scores.end(); pr++) {cout << *pr <<endl; }實際上,作為一種編程風格,最好避免直接使用迭代器,而應盡可能使用STL函數(如for_each())來處理細節。也可以使用C++11新增的基于范圍的for循環:
for(auto x : scores) cout << x << endl;來總結一下STL方法。首先是處理容器的算法,應盡可能用通用的術語來表達算法,使之獨立于數據類型和數據類型。為使通用算法能夠適用于具體情況,應定義能夠滿足算法需求的迭代器,并把要求加到容器設計上。即基于算法的要求,設計基本迭代器的特征和容器特征。
總結
以上是生活随笔為你收集整理的STL泛型编程之迭代器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 要得到,就要先付出
- 下一篇: C++ getline() 和 get(