天天看點

C++函數模闆與模闆函數

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 ) {

有一種方法可替代為每個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 ) {

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 的值會被一個編譯時刻已知的常量值代替。函數定義或聲明跟在模闆參數表後除了模闆參數是類型訓示符或常量值外函數模闆的定義看起來與非模闆函數的定義相同

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;

Type min( Type a, Type b )

// tmp 類型為模闆參數 Type

// 不是全局 typedef

Type tmp = a < b ? a : b;

return tmp;

②  在函數模闆定義中聲明的對象或類型不能與模闆參數同名

// 錯誤: 重新聲明模闆參數 Type

③  模闆類型參數名可以被用來指定函數模闆的傳回位

// 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 在不同模闆之間重複使用

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 是個類型名因而使整個表達式是一個指針聲明我們應如下修改

typename Parm::name * p; // ok: 指針聲明

關鍵字typename 也可以被用在模闆參數表中以訓示一個模闆參數是一個類型

⑦ 如同非模闆函數一樣函數模闆也可以被聲明為inline 或extern 應該把訓示符放在模闆參數表後面而不是在關鍵字template 前面

// ok: 關鍵字跟在模闆參數表之後

template <typename Type>

你們的評論、回報,及對你們有所用,是我整理材料和博文寫作的最大的鼓勵和唯一動力。歡迎讨論和關注!

沒有整理與歸納的知識,一文不值!高度概括與梳理的知識,才是自己真正的知識與技能。

永遠不要讓自己的自由、好奇、充滿創造力的想法被現實的架構所束縛,讓創造力自由成長吧!

多花時間,關心他(她)人,正如别人所關心你的。理想的騰飛與實作,沒有别人的支援與幫助,是萬萬不能的。

繼續閱讀