C++:函式模板(理解)
目錄
泛型程式設計
泛型程式設計最初提出時的動機很簡單直接:發明一種語言機制,能夠幫助實現一個通用的標準容器庫。所謂通用的標準容器庫,就是要能夠做到,比如用一個List類存放所有可能型別的物件這樣的事;泛型程式設計讓你編寫完全一般化並可重複使用的演算法,其效率與針對某特定資料型別而設計的演算法相同。
泛型即是指具有在多種資料型別上皆可操作的含義,與模板有些相似。STL巨大,而且可以擴充,它包含很多計算機基本演算法和資料結構,而且將演算法與資料結構完全分離,其中演算法是泛型的,不與任何特定資料結構或物件型別系在一起。
函式過載的缺點:
- 過載的函式僅僅只是型別不同,程式碼的複用率比較低
- 程式碼的維護性比較低,一個出錯可能所有的過載均出錯
- 如果函式的返回值型別不同,不行形參過載
- 只要有新的型別出現,就需要增加對應的函式
所以c++中存在這樣一個模具,通過這個不同的型別生成不同的具體程式碼。
泛型程式設計:編寫與型別無關的通用程式碼,是程式碼複用的一種手段,模板是泛型程式設計的基礎
函式模板
1:函式模板概念
函式模板代表一個函式族,該函式模板與型別無關,使用時被引數化,根據實參型別產生函式的特定型別版本
2:模板格式:
template<typename T1,typename T1,....>
返回值型別 函式名 (引數列表){ }
template<typename T> void Swap(T& left,T& right) { T temp = left; left = right; right = temp; }
注意:typename是用來定義模板引數關鍵字,也可以使用class,建議儘量使用typename
3函式模板原理
模板是一個藍圖,它本身不是一個類或者函式,編譯器用模板產生指定的類或者函式的特定型別版本,這個過程稱之為例項化!
編譯器需要根據傳入的實參型別,來推演生成對應型別的函式應用
4:函式模板的例項化與類模板例項化
不同型別的引數使用函式模板時,稱之為例項化。模板引數例項化分為兩種:隱式例項化和顯示例項化
1:隱式例項化
模板:定義:函式模板隱式例項化指的是在發生函式呼叫的時候,如果沒有發現相匹配的函式存在,編譯器就會尋找同名函式模板,如果可以成功進行引數型別推演,就對函式模板進行例項化。
類:類模板隱式例項化指的是在使用模板類時才將模板例項化
2:顯示例項化
對於函式模板而言,不管是否發生函式呼叫,都可以通過顯示例項化宣告將函式模板例項化,格式為:
template 函式返回型別 函式模板名<實際型別列表>(函式引數列表)
template class 類模板名<實際型別列表>
函式模板:對於函式模板而言,不管是否發生函式呼叫,都可以通過顯示例項化宣告將函式模板例項化
類模板:對於類模板而言,不管是否生成一個模板類的物件,都可以直接通過顯示例項化宣告將類模板例項化
如果兩個引數型別不同,T推演的型別只有一個,編譯器無法處理而報錯Swap<int>(a,b)
5:函式模板的匹配原則
函式的所有過載版本的宣告都應該位於被呼叫函式之前
int Max(const int& left ,const int& right);
template<class T>
T Max(const T& left, const T& right)
{
return (left>right)? left:right;
}
template<class T>
T Max(const T& left, const T& mid, const T& right)
- 一個非模板函式可以和一個同名的函式模板同時存在,而且該函式模板還可以被例項化為這個非模板函式
- 對於非模板函式和同名函式模板,如果其他條件都相同,在呼叫時會優先呼叫非模板函式而不會從模板生出一個例項(如果模板可以產生一個具有更好匹配的函式,那麼選擇模板)
- 顯示指定一個空的模板實參列表,該語法告訴編譯器只有模板才能來匹配這個呼叫,而且所有模板引數都應該根據實參演繹出來
- 函式模板不允許自動型別轉換,但普通函式可以進行自動型別轉換
類模板
1類模板的定義格式
template
class 類模板名
{...};
//動態順序表
template<typename T>
class Vector
{
public:
Vector;
~Vector()
{
delete[] _data;
}
private:
int _size;
int _capacity;
T* _data;
};
//注意:類模板中函式放在類外進行定義時,需要加模板引數列表
template<typename T>
Vector<T>::Vector()
:_size(0)
, _capacity(10)
, _data(new T[_capacity])
{}
void test1()
{
Vector<int>s1;
Vector<double>s2;
}
2類模板的例項化
類模板例項化與函式模板例項化不同,類模板例項化需要在模板名字後跟<>,然後將例項化的型別放在<>中即可,模板類名字不是真正的類
而例項化的結構才是真正的類
Vector<int>s1;
Vector<double>s2;
3非模板型別引數
非模板型別形參是模板內部定義的常量,在需要常量表達式的時候,可以使用非模板型別引數
//靜態順序表
template<typename T,szie_t N = 10>
class Array
{
pubilc:
Array()
: _size(0)
{}
private:
T _array[N];
int _size;
};
void Test()
{
Array<int>s1;
Array<double>s2;
}
注意:浮點數和類物件(自定義型別)不能做非模板型別引數
4類模板的特化
模板對於大部分的型別都可以直接處理,但有些型別處理可能會出現問題,對於不能直接處理的型別,
需要對其進行特殊化處理,因此在進行特化時,需要先給出一個模板類,然後對其進行某些型別的特殊化處理。
類模板的特化分為:全特化和偏特化
以此為例:
template<typename T1,typename T2>
class Data
{
pubilc:
Data
()
{};
private:
T1 _d1;
T2 _d2;
};
1:全特化
全特化是將模板引數列表中的所有型別引數均具體化
//全特化
template<int ,int>
class Data
{
pubilc:
Data
()
{};
private:
T1 _d1;
T2 _d2;
};
將來如果將Data類模板的兩個引數全部例項化為int時,編譯器會優先選擇特化版本,而不會去使用基礎的類模板再例項化
2偏特化
偏特化是將類模板中的部分引數具體化稱之為部分特化或者區域性特化,或者是針對模板引數更進一步的條件限制所設計
的一個特化版本
//偏特化
template<typename T ,int>
class Data
{
pubilc:
Data
()
{};
private:
T1 _d1;
int _d2;
};
讓模板引數限制更加嚴格!
只要是第二個引數為int型都會使用上述模板
5類模板特化之型別萃取
型別萃取使用模板技術來萃取型別(包含自定義型別和內建型別)的某些特性,用以判斷
該型別進行特殊的處理用來提高效率或者其他
問題:如何實現一個通用拷貝?
方式一:
template<class T>
void Copy(T* dst, const T* src, szie_t size)
{
memcpy(dst,src,sizeof(T)*size);
}
上述程式碼雖然對於任意型別的空間都可以拷貝,但是如果拷貝自定義型別物件就有可能出錯,因為自定義型別物件有可能會涉及深拷貝
而memcpy屬於淺拷貝,如果涉及到深拷貝,就只能用賦值
方式二:
template<class T>
void Copy(T* dst,T* src,size_t size)
{
for(size_t i = 0;i < size;++i)
{
dst[i] = src[i] ;
}
}
用迴圈賦值的方式雖然可以,但是程式碼效率比較低,而c和c++最大的優勢就是效率高,
能否當遇見內建型別就用memcpy來拷貝,遇見自定義型別就用迴圈賦值方式來做
方式三:
template<class T>
void Copy(T* dst, const T* src, size_t size,bool IsPODType)
{
if (IsPODType)
{
memcpy(dst, src, sizeof(T)*size);
}
else
{
for (size_t i = 0;i < size;++i)
{
dst[i] = src[i];
}
}
}
POD(Plain Old Data)(平凡型別):
POD資料型別主要用來解決C++與C之間資料型別的相容性,以實現C++和C函式的互動
簡單理解為:C++ 中與 C相容的型別,可以按照 C 的方式處理
通過多增加一個引數,就可以將兩種拷貝的優勢體現結合起來。但缺陷是:使用者需要根據所拷貝
元素的型別去傳遞第三引數,那出錯的可能性就增加,那能否讓函式自動去識別所拷貝型別是
內建型別還是自定義型別呢?
方式四:
因為內建型別的個數是確定的,可以將所有的內建型別聚合在一起,如果能夠將所拷貝物件的型別確定下來
在內建型別中查詢是否存在即可確定所拷貝的型別是否為內建型別
// 此處只是舉例,只列出個別型別
bool IsPODType(const char* strType)
{
const char* arrType[] = {"char", "short", "int", "long", "long long",
"float", "double","long double"};
for(size_t i = 0; i < sizeof(array)/sizeof(array)/sizeof(array[0]); ++i)
{
if(0 == strcmp(strType, arrType[i]))
return true;
}
return false;
}
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
if(IsPODType(typeid(T).name()))
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
通過typeid所確認的拷貝物件的實際型別,然後在內建型別集合中列舉其是否出現過,既可以確認所拷貝的元素型別
為內建型別或者為自定義型別,但缺陷是:列舉需要將所有的型別遍歷一遍,每次比較字串,效率低
方式五:
為了將內建型別與自定義型別區分開,給出以下兩個類分別代表內建型別與自定義型別。
// 代表內建型別
struct TrueType
{
static bool Get()
{
return true ;
}
};
// 代表自定義型別
struct FalseType
{
static bool Get()
{
return false ;
}
};
給出以下類模板,將來使用者可以按照任意型別例項化該類模板
template<class T>
struct TypeTraits
{
typedef FalseType IsPODType;
};
對上述的類模板進行以下方式的例項化:
template<>
struct TypeTraits<char>
{
typedef TrueType IsPODType;
};
template<>
struct TypeTraits<short>
{
typedef TrueType IsPODType;
};
。。。。。等等
通過對TypeTraits類模板重寫改寫方式四中的Copy函式模板,來確認所拷貝物件的實際型別。
/*
T為int:int例項化TypeTraits模板類對應的TypeTraits<int>已經特化過,程式執行時就會使用已經特化過的
TypeTraits<int>,該類中的IsPODType剛好為類TrueType,而TrueType中Get函式返回true,內建型別走
memcpy拷貝
string例項化TypeTraits模板類對應的TypeTraits<string>沒有特化過,程式執行時使用TypeTraits類模
板,該類模板中的IsPODType剛好為類TrueType,而TrueType中Get函式返回true,內建型別走memcpy拷貝
*/
template<class T>
void Copy(T* dst, const T* src, size_t size)
{
if(TypeTraits<T>::IsPODType::Get())
memcpy(dst, src, sizeof(T)*size);
else
{
for(size_t i = 0; i < size; ++i)
dst[i] = src[i];
}
}
6模板分離編譯