在C++裡,不考慮具體資料類型的程式設計模式叫做泛型程式設計,泛型也是一種資料類型,隻不過它是一種用來代替所有類型的“通用類型”。
泛型程式設計通過函數模闆和類模闆來實作泛型程式設計。
9.1 函數模闆
9.1.1 函數模闆的基本使用
當我們想寫個Swap()交換函數時,通常這樣寫:
imagepng
但是這個函數僅僅隻能支援int類型,如果我們想實作交換double,float,string等等時,就還需要從新去構造Swap()重載函數,這樣不但重複勞動,容易出錯,而且還帶來很大的維護和調試工作量。更糟的是,還會增加可執行檔案的大小。
我們可以通過函數模闆解決這個問題:
imagepng
Swap 泛型寫法中的 T 不是一個具體的資料類型,而是泛指任意的資料類型。
函數模闆其實是一個具有相同行為的函數家族
函數模闆的文法規則如下
template 關鍵字用于聲明開始進行泛型程式設計
typename 關鍵字用于聲明泛指類型
imagepng
函數模闆的應用
自動類型推導調用
具體類型顯示調用
imagepng
9.1.2 深入了解函數模闆
1、為什麼函數模闆能夠執行不同的類型參數?
其實編譯器對函數模闆進行了兩次編譯
第一次編譯時,首先去檢查函數模闆本身有沒有文法錯誤
第二次編譯時,會去找調用函數模闆的代碼,然後通過代碼的真正參數,來生成真正的函數。
是以函數模闆,其實隻是一個模具,當我們調用它時,編譯器就會給我們生成真正的函數.
2、如何驗證呢?
編譯程式生成.o檔案,然後通過nm指令檢視符号表
g++ -c 函數模闆.cpp nm 函數模闆.o
imagepng
從符号表中可以非常直覺的看到生成了兩個不同的符号。
需要注意的是:函數模闆是不允許隐式類型轉換的,調用時類型必須嚴格比對
也就說如下代碼是非法的:
int a;
float b;
Swap(a, b);
3、函數模闆的特點
數模闆還可以定義任意多個不同的類型參數,但是對于多參數函數模闆:
編譯器是無法自動推導傳回值類型的
可以從左向右部分指定類型參數
#include
using namespace std;
template <typename T1, typename T2, typename T3>
T1 add(T2 a, T3 b)
{
T1 ret;
ret = static_cast(a + b);
return ret;
}
void main()
{
int c = 12;
float d = 23.4;
//cout << add(c, d) << endl; //error,無法自動推導函數傳回值
cout << add(c, d) << endl; //傳回值在第一個類型參數中指定
cout << add<int, int, float>(c, d) << endl;
}
4、函數模闆的重載
函數模闆跟普通函數一樣,也可以被重載
C++編譯器優先考慮普通函數
如果函數模闆可以産生一個更好的比對,那麼就選擇函數模闆
也可以通過空模闆實參清單<>限定編譯器隻比對函數模闆
#include
using namespace std;
template `
void fun(T a)
{
cout << “void fun(T1 a)” << endl;
}
template <typename T1, typename T2>
void fun(T1 a, T2 b)
{
cout << “void fun(T1 a, T2 b)” << endl;
}
//函數模闆的重載
void fun(int a, float b)
{
cout << “void fun(int a, float b)” << endl;
}
void main()
{
int a = 0;
float b = 0.0;
fun(a);
fun(a, b); //普通函數void fun(int a, float b)已經能完美比對,于是調用普通函數
fun(b, a); //這個調用,函數模闆有更好的比對,于是調用函數模闆
fun<>(a, b); //限定隻使用函數模闆
}
5、總結
函數模闆是泛型程式設計在C++中的應用方式之一
函數模闆能夠根據實參對參數類型進行推導
函數模闆支援顯示的指定參數類型
函數模闆是C++中重要的代碼複用方式
函數模闆通過具體類型産生不同的函數
函數模闆可以定義任意多個不同的類型參數
函數模闆中的傳回值類型必須顯示指定
函數模闆可以像普通函數一樣重載
在任何一個函數模闆前都必須 使用template <typename 泛型,…>聲明開始泛型程式設計
9.2 類模闆
9.2.1 類模闆的定義
還記得我們上次實作的Array類嗎?在Array類中我們隻能操作int類型的資料,如果需要操作char,float類型的資料我們該如何處理呢?難道我們再重新實作一個類嗎?當然不用,我們可以使用類模闆來實作。
在實際工作中,有時,有兩個或多個類,其功能是相同的,僅僅是資料類型不同,我們可以使用類模闆來實作。
template<class 模闆參數表>
class 類名{
// 類定義......
};
imagepng
imagepng
這樣我們就定義了一個簡單的類模闆,其中的T代表任意的類型,可以出現在類模闆中的任意地方,與函數模闆不同的是,使用類模闆構造對象時必須顯示的指定資料類型,編譯器無法自動推導,例如test<int>t;需要顯示的指定資料類型。
編譯器對類模闆的處理方式和函數模闆相同
編譯器從類模闆通過具體類型産生不同的類
編譯器在聲明的地方對類模闆代碼本身進行編譯
編譯器在使用的地方對參數替換後的代碼進行編譯
由于類模闆的編譯機制不同 , 是以不能像普通類一樣分開實作後在使用時隻包含頭檔案,在工程實踐上 , 一般會把類模闆的定義直接放到頭檔案中!!
隻有被調用的類模闆成員函數才會被編譯器生成可執行代碼!!!
imagepng
9.2.2 類模闆的特化
上面的類模闆好像已經實作了add加法運算.但是卻不能支援指針類型。其實,類模闆也可以像函數重載一樣, 類模闆通過特化的方式可以實作特殊情況.
類模闆特化:
表示可以存在多個相同的類名,但是模闆類型都不一緻(和函數重載的參數類似)
特化類型有完全特化和部分特化兩種類型
完全特化表示顯示指定類型參數,模闆聲明隻需寫成template<>,并在類名右側指定參數,比如:
#include
using namespace std;
template < typename T1,typename T2 > //聲明的模闆參數個數為2個
class Operator //正常的類模闆
{
public:
Operator()
{
cout << “Operator” << endl;
}
void add(T1 a, T2 b)
{
cout<<a+b<<endl;
}
};
template <> //不需要指定模闆類型,因為是完全特化的類模闆
class Operator<int, int> //指定類型參數,必須為2個參數,和正常類模闆參數個數一緻
{
public:
Operator()
{
cout << “Operator<int , int>” << endl;
}
void add(int a, int b)
{
cout<<a+b<<endl;
}
};
int main()
{
//比對完全特化類模闆:class Operator< int,int>
Operator<int,int> Op1;
//比對正常的類模闆
Operator<int,float> Op2;
return 0;
}
部分特化表示通過特定規則限制類型參數,模闆聲明和類相似,并在類名右側指定參數,比如:
#include
using namespace std;
template < typename T1,typename T2 > //聲明的模闆參數個數為2個
class Operator //正常的類模闆
{
public:
void add(T1 a, T2 b)
{
cout<<a+b<<endl;
}
};
template < typename T > //有指定模闆類型以及指定參數,是以是部分特化的類模闆
class Operator< T* ,T*> //指定類型參數,必須為2個參數,和正常類模闆參數個數一緻
{
public:
void add(T* a, T* b)
{
cout<<*a+*b<<endl;
}
};
int main()
{
Operator<int*,int*> Op1; //比對部分特化: class Operator< T* ,T*>
Operator<int,float> Op2; //比對正常的類模闆: class Operator
return 0;
}
編譯時,會根據對象定義的類模闆類型,首先去比對完全特化,再來比對部分特化,最後比對正常的類模闆
#include
using namespace std;
template < typename T1,typename T2 >
class Operator //正常的類模闆
{
public:
void add(T1 a, T2 b)
{
cout<<“add(T1 a, T2 b)”<<endl;
cout<<a+b<<endl;
}
};
template < typename T >
class Operator<T,T> //特化的類模闆,當兩個參數都一樣,調用這個
{
public:
void add(T a, T b)
{
cout<<“add(T a, T b)”<<endl;
cout<<a+b<<endl;
}
};
template < typename T1,typename T2 >
class Operator<T1*,T2*> //部分特化的類模闆,當兩個參數都是指針,調用這個
{
public:
void add(T1* a, T2* b)
{
cout<<“add(T1* a, T2* b)”<<endl;
cout<<*a+*b<<endl;
}
};
template < >
class Operator<void*,void*> //完全特化的類模闆,當兩個參數都是void*,調用這個
{
public:
void add(void* a, void* b)
{
cout<<“add(void* a, void* b)”<<endl;
cout<<“add void* Error”<<endl; //void*無法進行加法
}
};
int main()
{
int *p1 = new int(1);
float *p2 = new float(1.25);
Operator<int,float> Op1; //比對正常的類模闆:class Operator
Op1.add(1,1.5);
Operator<int,int> Op2; //比對部分特化的類模闆:class Operator<T,T>
Op2.add(1,4);
Operator<int*,float*> Op3; //比對部分特化的類模闆:class Operator<T1*,T2*>
Op3.add(p1,p2);
Operator<void*,void*> Op4; //比對完全特化的類模闆:class Operator<void*,void*>
Op4.add(NULL,NULL);
delete p1;
delete p2;
return 0;
}
9.2.3 繼承中類模闆的使用
9.2.3.1 父類是一般類,子類是模闆類
class A {
public:
A(int temp = 0) {
this->temp = temp;
}
~A(){}
private:
int temp;
};
template
class B :public A{
public:
B(T t = 0) :A(666) {
this->t = t;
}
~B(){}
private:
T t;
};
9.2.3.2 子類是一般類,父類是模闆類
template
class A {
public:
A(T t = 0) {
this->t = t;
}
~A(){}
private:
T t;
};
class B:public A {
public:
//也可以不顯示指定,直接A(666)
B(int temp = 0):A(666) {
this->temp = temp;
}
~B() {}
private:
int temp;
};
9.2.3.3 子類和父類都是模闆類
#include
using namespace std;
//1、類模闆繼承類模闆
template <typename T1, typename T2>
class A
{
T1 x;
T2 y;
};
template <typename T1, typename T2>
class B : public A<T2, T1>
{
T1 x1;
T2 y2;
};