首先来看一个简单的例子,假设我们想找整形数组中第一个等于某个给定值的元素,代码很简单,如下:
const int* find(const int* array, int n, int x) { const int* p = array; for(int i=0; i<n; i++) { if(*p == x) return p; p++; } return 0; }
我们想让这个算法变得尽量的通用,首先肯定要让其去除对特定类型int的依赖,我们采用模板对这个算法进行元素类型的泛化,变化后的代码如下:
template <class T> T* find(T* array, int n, const T& x) { T* p = array; for(int i=0; i<n; i++) { if(*p == x) return p; p++; } return 0; }注:这里不需要用const,因为如果我们用一个指向const类型的指针来调用find的话,T会把const作为它类型的一部分。又因为我们应用这个算法的类型T有可能是类类型,也许这个类类型根本不允许复制一个对象,也许这个类型复制的代价会很大,基于这些考虑我们算法函数的第三个参数改为使用const引用
我们想让这个算法变得更通用,那么我们应该去除find算法中一定要给出总共元素的个数n的限制,试想如果我们要对链表来使用这个算法,那我们必须得先给出链表的长度,这是不是太麻烦了?
为了通用性,我们又修改这个算法,代码如下:
template <class T> T* find(T* start, T* beyond, const T& x) { T* p = array; while(p != beyond && *p !=x) ++p; return p; }
template <class T> T* find(S s, S b, const T& x) { while (s!=b && *s!=x) ++s; return s; }
struct Node { int value; Node* next; }很容易我们可以得到查找链表特定元素的算法,如下:
Node* ListFind(Node* s, Node* b=0, const int& x) { while( s!=b && *s!=x ) s = s->next; return s; }显然我们的ListFind函数与我们最终的find算法函数非常类似,不要奇怪,因为他们解决的是同样一个问题,只是针对不同的数据结构,所以算法形式的一致是不足为奇的(这就是抽象的魅力所在!)
要对链表应用通用的find函数,我们还需要定义一个辅助类,这个辅助类要建立链表与这个算法之间的联系,并且这个辅助类要能够支持!= * ++,为了能更好的理解,我们可以先设想这个辅助类应该是实现STL容器中的迭代器的功能
我们将这个辅助类命名为Nodep,具体定义如下:
class Nodep { public: Nodep(Node* p = 0):pt(p) {}; int& operator*() { return pt->value; } void operator++() { pt = pt->next; } friend bool operator!=(const Nodep&, const Nodep&); operator Node*() { return pt; } //类型转换符 private: Node* pt; }; bool operator!=(const Nodep& p, const Nodep& q) { return p.pt != q.pt; }
#include <iostream> using namespace std; template <class S, class T> S find(S s, S b, const T& x) // 泛型算法 { while (s!=b && *s!=x) ++s; return s; } struct Node //链表节点定义 { int value; Node* next; }; typedef Node* List; class Nodep //辅助类,辅助链表使用通用泛型算法 { public: Nodep(Node* p = 0):pt(p) {}; int& operator*() { return pt->value; } void operator++() { pt = pt->next; } friend bool operator!=(const Nodep&, const Nodep&); operator Node*() { return pt; } //类型转换符 private: Node* pt; }; bool operator!=(const Nodep& p, const Nodep& q) { return p.pt != q.pt; } List CreateList(int* p) //建立链表 { List head = new Node(); Node* tail = head; head->next = NULL; while(*p) { Node* t = new Node(); t->value = *p; t->next = NULL; tail->next = t; tail = t; p++; } return head; } void main() { int a[] = {1,2,3,4,0}; List l= CreateList(a); Nodep start(l); Nodep end(0); Nodep n = find(start,end,4); Node* p = n; cout << p->value; }
总结:
记住,利用模板写出的函数,不仅可以适用于不同的参数类型,还可以使用于不同的数据结构
也许有人会疑问,既然为了让特定的数据结构使用通用算法,我们需要定义一个辅助类,为什么直接使用我们的普通算法(比如ListFind)呢?要解开这个疑惑,请假设有m个数据结构和n个算法,如果我们不使用泛型,则数据结构和算法之间的映射有m*n个,也就是说针对每个数据结构的每个算法我们都需要去定义,而如果使用泛型,则我们只需要定义m个辅助类和n个算法,O(m*n)与O(m+n),那个更好呢?