將一個敘述完整的算法轉化為程序代碼,不是什麼難事。然而,如何將算法獨立與其所處理的數據結構之外,不受數據結構的羁絆呢?換個說法,如何將我們所寫的程序算法適用於任何(或者大部分)未知的數據結構(比如array,vector,list等)呢?
關鍵在於,只要把操作對象的型別加以抽象化,把操作對象的標示法和區間目標的移動行為抽象化,整個算法也就在一個抽象層面上工作了。整個過程稱為算法的泛型化(generalized),簡稱泛化。
以簡單的循序查找為例,編寫find()函數,在array中尋找特定值。面對整數array,寫出如下程序:
int* find(int* arrayHead, int arraySize, int value)
{
int i=0;
for (; i<arraySize; ++i)
{
if (arrayHead[i] == value)
{
break;
}
}
return &(arrayHead[i]);
}
該函數在某個區間內查找value。返回的是一個指針,指向它所找到的第一個元素;如果沒有找到,就返回最後一個元素的下一位置(地址)。
“最後一個元素的下一位置”稱為end。為什麼不返回null?因為,end指針可以對其他種類的容器帶來泛型效果,這是null所無法達到的。事實上一個指向array元素的指針,不但可以指向array內部任何位置,也可以指向array尾端以外的任何位置。只不過當指針指向array尾端以外的位置時,它只能用來與其他array指針相比較,不能提領(dereference)其值。
const int arraySize = 7;
int ia[arraySize] = {0, 1, 2, 3, 4, 5, 6};
int* end = ia + arraySize;
int * ip = find(ia, sizeof(ia)/sizeof(int), 4);
if (ip == end)
{
cout<<"4 not found"<<endl;
}
else
{
cout<<"4 found. "<<*ip<<endl;
}
上述find()函數寫法暴露了太多的實現細節(例如arraySize),為了讓find()適用於所有類型的容器,其操作應該更抽象化些。讓find()接受兩個指針作為參數,標示一個操作區間:
int* find(int* begin, int*end, int value)
{
while(begin !=end && *begin != value)
++begin;
return begin;
}
由於find()函數之內並無任何操作是針對特定整數array而發的,所以我們可以把它改成一個template:
template<typename T>
T* find(T* begin, T* end, const T& value)
{
// 注意,以下用到了operator!=, operator*, operator++
while (begin != end && *begin != value)
++begin;
// 注意,以下返回操作用會引發copy行為
return begin;
}
注意數值傳遞由pass by value改為pass by reference const, 因為value的型別可為任意,對象一大,傳遞成本便會提升。
在上述代碼中,傳入的指針必須支持以下四種操作行為:
上述操作符可以被重載(overload),find()函數就可以從原聲(native)指針的思想框框中跳脫出來。我們可以設計一個class,擁有原生指針的行為,這就是迭代器(iterator):
template<typename Iterator, typename T>
Iterator find(Iterator begin, Iterator end, const T& value)
{
while(begin != end && *begin != value)
++begin;
return begin;
}
這便是完全泛型化的find()函數。
來自:STL源碼剖析
STL源碼剖析簡體中文完整版(高清晰掃描帶目錄)PDF 下載見 http://www.linuxidc.com/Linux/2016-04/129761.htm