天天看點

第九章 泛型程式設計

在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;

};

繼續閱讀