1.什么是泛型編程
前面我們介紹的vector,list,map都是一種數據結構容器,
容器本身的存儲結構不同,各容器中存在的數據類型也可以不同。
但我們在訪問這些容器中數據時,擁有相同的方式。
這種方式就叫做“泛型編程”,顧名思義,不同的類型采用相同的方式來操作。
2.泛型編程的原理。
我們先看下面的兩個分別訪問數組和鏈表中元素的示例。
示例1,數組類型遍歷
void show(double* arr,int n) { for(int i=0;i<n;i++) cout<<arr[i]<<" "; }
示例2,鏈表類型遍歷
struct Node { double item; Node * p_next; }; void show(Node* head) { for(Node* start=head;start!=0;start=start->p_next) cout<<start.item<<" "; }
我們可以看到,數組和鏈表的訪問方式是完全不同的。
那么,如何使用相同的方式去訪問呢?
這兩種數據結構的共同點是,它們都是一個順序存儲數據的容器,
所以我們可為每一種容器中定義一個相應的指針類p,在泛型編程中,叫做“迭代器類(iterator)”
該指針類中需要重載兩個操作符,
1)*p,訪問數據元素內容
2)p++,訪問下一個數據元素
示例1,數組容器(double *本身具有*p,p++操作符):
typedef double* iterator; void show(iterator head,int n) { int i=0; for(iterator start=head;i<n;++start) { cout<<*start<<" "; i++ } }
示例2,鏈表容器:
struct Node { double item; Node * p_next; }; class iterator { Node *pt; public: double operator*() { return pt->item; } iterator& operator++() { pt=pt->p_next; return *this; } }; void show(iterator head) { for(iterator start=head;start!=0;++start) { cout<<*start<<" "; } }
我們看到這兩種容器的show()方法已基本相同,唯一的區別是結束判斷。
在數組中,根據數組長度來判斷結尾。
在鏈表中,根據最后一個元素指向的下一個元素指針為空來判斷。
鏈表比數組多出來一個指向null的指針。
在泛型編程中,使用了“超尾”的概率來解決這個問題,
“超尾”是指在容器的最后一個元素后面,還有一個額外的元素,該元素表示結束。
這樣數組和鏈表都有這個超尾指針。我們統一使用這個超尾指針來判斷結尾。
3.泛型編程中迭代器的使用
C++為每個容器類(vector,list,deque等)定義了相應的迭代器類型,
其begin()返回指向第一個元素的迭代器,end()返回指向超尾元素(額外添加的元素)的迭代器。
如下例所示:
vector<double>::iterator pr; for(pr=scores.begin();pr!=scores.end();pr++) cout<<*pr<<endl;
在C++11中,簡化了迭代器類型的定義,使用auto自動類型
for(auto pr=scores.begin();pr!=scores.end();pr++) cout<<*pr<<endl;
4.建議使用的遍歷方式
很多語言,如C#,並沒有使用迭代器iterator,而是使用for,foreach等來遍歷數據元素。
所以,C++中最好也避免直接使用迭代器,而盡可能使用for_each()。
C++11中,新增了for循環,如下例所示:
for(auto x:scores) cout<<x<<endl;
參考資料:《C++ Primer.Plus》 pp.685-688