1.函數指針——指針函數
函數指針的重點是指針。表示的是一個指針,它指向的是一個函數,例子:
int (*pf)();
指針函數的重點是函數。表示的是一個函數,它的返回值是指針。例子:
int* fun();
2.數組指針——指針數組
數組指針的重點是指針。表示的是一個指針,它指向的是一個數組,例子:
int (*pa)[8];
指針數組的重點是數組。表示的是一個數組,它包含的元素是指針。例子;
int* ap[8];
3.類模板——模板類(class template——template class)
類模板的重點是模板。表示的是一個模板,專門用於產生類的模子。例子:
template <typename T>
class Vector
{
…
};
使用這個Vector模板就可以產生很多的class(類),Vector <int> 、Vector <char> 、Vector < Vector <int> > 、Vector <Shape*> ……。
模板類的重點是類。表示的是由一個模板生成而來的類。例子:
上面的Vector <int> 、Vector <char> 、……全是模板類。
這兩個詞很容易混淆,我看到很多文章都將其用錯,甚至一些英文文章也是這樣。將他們區分開是很重要的,你也就可以理解為什麼在定義模板的頭文件.h時,模板的成員函數實現也必須寫在頭文件.h中,而不能像普通的類(class)那樣,class的聲明(declaration)寫在.h文件中,class的定義(definition)寫在.cpp文件中。
array是一個模板,array<int, 50>是一個模板實例 - 一個類型。從array創建array<int, 50>的過程就是實例化過程。實例化要素體現在main.cpp文件中。如果按照傳統方式,編譯器在array.h文件中看到了模板的聲明,但沒有模板的定義,這樣編譯器就不能創建類型array<int, 50>。但這時並不出錯,因為編譯器認為模板定義在其它文件中,就把問題留給鏈接程序處理。
現在,編譯array.cpp時會發生什麼問題呢?編譯器可以解析模板定義並檢查語法,但不能生成成員函數的代碼。它無法生成代碼,因為要生成代碼,需要知道模板參數,即需要一個類型,而不是模板本身。
這樣,鏈接程序在main.cpp 或 array.cpp中都找不到array<int, 50>的定義,於是報出無定義成員的錯誤。
關於一個缺省模板參數的例子:
template <typename T = int>
class Array
{
…
};
第一次我定義這個模板並使用它的時候,是這樣用的:
Array books;//我認為有缺省模板參數,這就相當於Array <int> books
上面的用法是錯誤的,編譯不會通過,原因是Array不是一個類。正確的用法是Array <> books;
這裡Array <> 就是一個用於缺省模板參數的類模板所生成的一個具體類。
4.函數模板——模板函數(function template——template function)
函數模板的重點是模板。表示的是一個模板,專門用來生產函數。例子:
template <typename T>
void fun(T a)
{
…
}
在運用的時候,可以顯式(explicitly)生產模板函數,fun <int> 、fun <double> 、fun <Shape*> ……。
也可以在使用的過程中由編譯器進行模板參數推導,幫你隱式(implicitly)生成。
fun(6);//隱式生成fun <int>
fun(8.9);//隱式生成fun <double>
fun(‘a’);// 隱式生成fun <char>
Shape* ps = new Cirlcle;
fun(ps);//隱式生成fun <Shape*>
模板函數的重點是函數。表示的是由一個模板生成而來的函數。例子:
上面顯式(explicitly)或者隱式(implicitly)生成的fun <int> 、fun <Shape*> ……都是模板函數。
模板本身的使用是很受限制的,一般來說,它們就只是一個產生類和函數的模子。除此之外,運用的領域非常少了,所以不可能有什麼模板指針存在的,即指向模板的指針,這是因為在C++中,模板就是一個代碼的代碼生產工具,在最終的代碼中,根本就沒有模板本身存在,只有模板具現出來的具體類和具體函數的代碼存在。
提醒:在本文的幾個術語中,語言的重心在後面,前面的詞是作為形容詞使用的。
2 函數模板的異常處理
函數模板中的模板形參可實例化為各種類型,但當實例化模板形參的各模板實參之間不完全一致時,就可能發生錯誤,如:
template<typename T> void min(T &x, T &y) { return (x<y)?x:y; } void func(int i, char j) { min(i, i); min(j, j); min(i, j); min(j, i); }
例子中的後兩個調用是錯誤的,出現錯誤的原因是,在調用時,編譯器按最先遇到的實參的類型隱含地生成一個模板函數,並用它對所有模板函數進行一致性檢查,例如對語句
min(i, j);
先遇到的實參i是整型的,編譯器就將模板形參解釋為整型,此後出現的模板實參j不能解釋為整型而產生錯誤,模板函數是沒有隱含的類型轉換功能的。解決此種異常的方法有兩種:
⑴采用強制類型轉換,如將語句min(i, j);改寫為min(i,int( j));
⑵用非模板函數重載函數模板
方法有兩種:
① 借用函數模板的函數體
此時只聲明非模板函數的原型,它的函數體借用函數模板的函數體。如改寫上面的例子如下:
template<typename T> void min(T &x, T &y) { return (x<y)?x:y; } int min(int,int); void func(int i, char j) { min(i, i); min(j, j); min(i, j); min(j, i); }
執行該程序就不會出錯了,因為重載函數支持數據間的隱式類型轉換。
② 重新定義函數體
就像一般的重載函數一樣,重新定義一個完整的非模板函數,它所帶的參數可以隨意。
C++中,函數模板與同名的非模板函數重載時,應遵循下列調用原則:
• 尋找一個參數完全匹配的函數,若找到就調用它。若參數完全匹配的函數多於一個,則這個調用是一個錯誤的調用。
• 尋找一個函數模板,若找到就將其實例化生成一個匹配的模板函數並調用它。
• 若上面兩條都失敗,則使用函數重載的方法,通過類型轉換產生參數匹配,若找到就調用它。
•若上面三條都失敗,還沒有找都匹配的函數,則這個調用是一個錯誤的調用。
3.函數模板與類模板有什麼區別?
答:函數模板的實例化是由編譯程序在處理函數調用時自動完成的,而類模板的實例化必須由程序員在程序中顯式地指定。
即函數模板允許隱式調用和顯式調用而類模板只能顯示調用