1.函數模板的聲明和模板函數的生成
1.1 函數模板的聲明
函數模板可以用來創建一個通用的函數,以支持多種不同的形參,避免重載函數的函數體重復設計。它的最大特點是把函數使用的數據類型作為參數。
函數模板的聲明形式為:
template<typename 數據類型參數標識符>
<返回類型><函數名>(參數表)
{
函數體
}
其中,template是 定義模板函數的關鍵字;template后面的尖括號不能省略;typename(或class)是聲明數據類型參數標識符的關鍵字,用以說明它后面的標 識符是數據類型標識符。這樣,在以后定義的這個函數中,凡希望根據實參數據類型來確定數據類型的變量,都可以用數據類型參數標識符來說明,從而使這個變量 可以適應不同的數據類型。例如:
template<typename T>
T fuc(T x, int y)
{
T x;
//……
}
如果主調函數中有以下語句:
double d;
int a;
fuc(d,a);
則系統將用實參d的數據類型double去代替函數模板中的T生成函數:
double fuc(double x,int y)
{
double x;
//……
}
函數模板只是聲明了一個函數的描述即模板,不是一個可以直接執行的函數,只有根據實際情況用實參的數據類型代替類型參數標識符之后,才能產生真正的函數。
關鍵字typename也可以使用關鍵字class,這時數據類型參數標識符就可以使用所有的C++數據類型。
1.2 模板函數的生成
函數模板的數據類型參數標識符實際上是一個類型形參,在使用函數模板時,要將這個形參實例化為確定的數據類型。將類型形參實例化的參數稱為模板實參,用模板實參實例化的函數稱為模板函數。模板函數的生成就是將函數模板的類型形參實例化的過程。例如:
使用中應注意的幾個問題:
⑴ 函數模板允許使用多個類型參數,但在template定義部分的每個形參前必須有關鍵字typename或class,即:
template<class 數據類型參數標識符1,…,class 數據類型參數標識符n>
<返回類型><函數名>(參數表)
{
函數體
}
⑵ 在template語句與函數模板定義語句<返回類型>之間不允許有別的語句。如下面的聲明是錯誤的:
template<class T>
int I;
T min(T x,T y)
{
函數體
}
⑶ 模板函數類似於重載函數,但兩者有很大區別:函數重載時,每個函數體內可以執行不同的動作,但同一個函數模板實例化后的模板函數都必須執行相同的動作。
2 函數模板
經常有碰到函數模塊的應用,很多書上也只是略有小講一下,今天又狂碰到函數模塊,無奈特地找來C++編程經典<<C++ Primer>>翻閱一遍,終於有所全面了解.....
2.1 問題
強類型語言要求我們為所有希望比較的類型都實現一個實例
int min( int a, int b ) {
return a < b ? a : b;
}
double min( double a, double b ) {
return a < b ? a : b;
}
有一種方法可替代為每個min()實例都顯式定義一個函數的方法這種方法很有吸引力但是也很危險.那就是用預處理器的宏擴展設施例如 : #define min(a,b) ((a) < (b) ? (a) : (b))
在復雜調用的情況下,它的行為是不可預期的,這是因為它的兩個參數值都被計算兩次. 一次是在a 和b 的測試中另一次是在宏的返回值被計算期間.
#include <iostream>
#define min(a,b) ((a) < (b) ? (a) : (b))
const int size = 10;
int ia[size];
int main() {
int elem_cnt = 0;
int *p = &ia[0];
// 計數數組元素的個數
while ( min(p++,&ia[size]) != &ia[size] )
++elem_cnt;
cout << "elem_cnt : " << elem_cnt
<< "\texpecting: " << size << endl;
return 0;
}
執行該程序的結果是下面不正確的計算結果: elem_cnt : 5 expecting: 10
min()的宏擴展在這種情況下會失敗因為應用在指針實參p 上的后置遞增操作隨每次擴展而被應用了兩次
2.2 解決辦法
函數模板提供了一種機制通過它我們可以保留函數定義和函數調用的語義在一個程序位置上封裝了一段代碼確保在函數調用之前實參只被計算一次.
函數模板提供一個種用來自動生成各種類型函數實例的算法程序員對於函數接口參數和返回類型中的全部或者部分類型進行參數化(parameterize)而函數體保持不變.
下面是min()的函數模板定義
template <class Type>
Type min( Type a, Type b ) {
return a < b ? a : b;
}
2.3 具體操作
關鍵字template 總是放在模板的定義與聲明的最前面關鍵字后面是用逗號分隔的模板參數表(template parameter list)它用尖括號<> 一個小於號和一個大於號括起來該列表是模板參數表不能為空模板參數可以是一個模板類型參數(template typeparameter)它代表了一種類型也可以是一個模板非類型參數(template nontype parameter)它代表了一個常量表達式模板類型參數由關鍵字class 或typename 后加一個標識符構成在函數的模板參數表中這兩個關鍵字的意義相同。
模板非類型參數由一個普通的參數聲明構成模板非類型參數表示該參數名代表了一個潛在的值而該值代表了模板定義中的一個常量例如size 是一個模板非類型參數它代表arr 指向的數組的長度
template <class Type, int size>
Type min( Type (&arr) [size] );
當函數模板min()被實例化時size 的值會被一個編譯時刻已知的常量值代替。函數定義或聲明跟在模板參數表后除了模板參數是類型指示符或常量值外函數模板的定義看起來與非模板函數的定義相同
template <class Type, int size>
Type min( const Type (&r_array)[size] )
{
/* 找到數組中元素最小值的參數化函數 */
Type min_val = r_array[0];
for ( int i = 1; i < size; ++i )
if ( r_array[i] < min_val )
min_val = r_array[i];
return min_val;
}
在程序的運行過程中Type 會被各種內置類型和用戶定義的類型所代替而size 會被各種常量值所取代這些常量值是由實際使用的min()決定的記住一個函數的兩種用法是調用它和取它的地址
當 一個名字被聲明為模板參數之后它就可以被使用了一直到模板聲明或定義結束為止模板類型參數被用作一個類型指示符可以出現在模板定義的余下部分它的使用方式 與內置或用戶定義的類型完全一樣比如用來聲明變量和強制類型轉換模扳非類型參數被用作一個常量值可以出現在模板定義的余下部分它可以用在要求常量的地方或 許是在數組聲明中指定數組的大小或作為枚舉常量的初始值
2.4 幾點注意
① 如果在全局域中聲明了與模板參數同名的對象函數或類型則該全局名將被隱藏在下面的例子中tmp 的類型不是double 是模板參數Type
typedef double Type;
template <class Type>
Type min( Type a, Type b )
{
// tmp 類型為模板參數 Type
// 不是全局 typedef
Type tmp = a < b ? a : b;
return tmp;
}
② 在函數模板定義中聲明的對象或類型不能與模板參數同名
template <class Type>
Type min( Type a, Type b )
{
// 錯誤: 重新聲明模板參數 Type
typedef double Type;
Type tmp = a < b ? a : b;
return tmp;
}
③ 模板類型參數名可以被用來指定函數模板的返回位
// ok: T1 表示 min() 的返回類型
// T2 和 T3 表示參數類型
template <class T1, class T2, class T3>
T1 min( T2, T3 );
④ 模板參數名在同一模板參數表中只能被使用一次,但是模板參數名可以在多個函數模板聲明或定義之間被重復使用
// 錯誤: 模板參數名 Type 的非法重復使用
template <class Type, class Type>
Type min( Type, Type );
// ok: 名字 Type 在不同模板之間重復使用
template <class Type>
Type min( Type, Type );
template <class Type>
Type max( Type, Type );
⑤ 如果一個函數模板有一個以上的模板類型參數則每個模板類型參數前面都必須有關鍵字class 或typename
// ok: 關鍵字 typename 和 class 可以混用
template <typename T, class U>
T minus( T*, U );
// 錯誤: 必須是 <typename T, class U> 或 <typename T, typename U>
template <typename T, U>
T sum( T*, U );
⑥ 為了分析模板定義編譯器必須能夠區分出是類型以及不是類型的表達式對於編譯器來說它並不總是能夠區分出模板定義中的哪些表達式是類型例如如果編譯器在模板定義中遇到表達式Parm::name 且Parm 這個模板類型參數代表了一個類那么name 引用的是Parm 的一個類型成員嗎.
template <class Parm, class U>
Parm minus( Parm* array, U value )
{
Parm::name * p; // 這是一個指針聲明還是乘法乘法
}
編譯器不知道name 是否為一個類型因為它只有在模板被實例化之后才能找到Parm 表示的類的定義為了讓編譯器能夠分析模板定義用戶必須指示編譯器哪些表達式是類型表達式告訴編譯器一個表達式是類型表達式的機制是在表達式前加上關鍵字typename 例如如果我們想讓函數模板minus()的表達式Parm::name 是個類型名因而使整個表達式是一個指針聲明我們應如下修改
template <class Parm, class U>
Parm minus( Parm* array, U value )
{
typename Parm::name * p; // ok: 指針聲明
}
關鍵字typename 也可以被用在模板參數表中以指示一個模板參數是一個類型
⑦ 如同非模板函數一樣函數模板也可以被聲明為inline 或extern 應該把指示符放在模板參數表后面而不是在關鍵字template 前面
// ok: 關鍵字跟在模板參數表之后
template <typename Type>
inline
Type min( Type, Type );