菜鳥學習歷程【32】函式模板與類模板
函式模板
所謂函式模板,實際上是建立一個通用函式,其函式型別和形參型別不具體指定,用一個虛擬的型別來代表。這個通用函式就稱為函式模板。凡是函式體相同的函式都可以用這個模板來代替,不必定義多個函式,只需在模板中定義一次即可。在呼叫函式時系統會根據實參的型別來取代模板中的虛擬型別,從而實現了不同函式的功能。
模板:將 演算法 與 資料型別 相分離;
定義:
template <typename T>
void mySwap(T &a, T &b)
{
T tmp = a;
a = b;
b = tmp;
}
對於上面的函式體,我想大家很熟悉,就是一個交換兩個數的值的過程,但有所不同的是 並沒有明確指明 形參的型別
那麼,像這樣的函式,我們成為 函式模板。
使用規則
1.普通函式呼叫時支援型別的隱式轉換,函式模板呼叫時不支援型別的隱式轉換;
例如:
#include <iostream>
using namespace std;
// 普通函式 在被呼叫時 如果 實參型別 與 形參型別 不一致時 會自動轉換
void print(int a, int b)
{
cout << "a = " << a << ", b = " << b << endl;
}
int main()
{
int a = 10, b = 20;
char c = 'A';
print(a, b); // a = 10, b = 20
print(a, c); // a = 10, b = 65
return 0;
}
#include <iostream>
using namespace std;
// 函式模板 不支援 引數型別的隱式轉換 ,呼叫時 沒有明確 指明哪一種型別 函式模板 不會進行轉換
// 如果呼叫時 明確指明使用 哪一種資料型別 那麼函式模板 會進行資料型別轉化
template<typename T>
void print(T a, T b)
{
cout << "a = " << a << ", b = " << b << endl;
}
int main()
{
int a = 10, b = 20;
char c = 'A';
print(a, b);
print(a, c); // 編譯報錯:沒有找到與引數列表 匹配的函式模板“print”例項
//print<int>(a, c); // 編譯通過,通過<int>告知編譯器 去構造一個 int 型的模板函式 ,那麼在使用時 系統會自動轉換
return 0;
}
2.函式模板和普通函式可以共存
1)當 函式模板 和 普通函式 都可以使用時,呼叫 普通函式。如果想呼叫 函式模板 ,可以用空引數列表 <> 進行說明;例如: print<>(a, b);
2)當 函式模板 提供一個更優的選擇,優先呼叫 函式模板;
// 普通函式
void print(char c, int a)
{
cout<<"a = "<< a <<", c = " << c << endl;
}
// 函式模板
template<typename T>
void print(T c, T a)
{
cout<<"a = "<< a <<", c = " << c << endl;
}
int main()
{
int a = 10, b = 20;
// 普通函式也可以列印 a 與 b 的值,只是會進行隱式的型別轉換
// 由於 函式模板 的存在,對於 a 與 b 兩個相同型別的 資料,編譯器更願意去 構造一個 int 型別的模板函式 供使用
print(a, b);
return 0;
}
3.函式模板 可以過載
template <typename T>
void print(T a, T b, T c)
{
cout<<"a = "<< a <<", b = "<< b <<", c = "<< c <<endl;
}
template<typename T>
void print(T a, T b)
{
cout << "a = " << a << ", b = " << b << endl;
}
函式模板機制
編譯器會對函式模板進行兩次編譯:
1.在宣告的地方對模板程式碼本身進行編譯;
2.在呼叫的地方對引數替換後的程式碼進行編譯。
說明:通過 函式模板 產生的函式 被稱為 模板函式。
類模板
類模板用於實現類所需資料的型別引數化。
模板類的語法
#include <iostream>
using namespace std;
template <typename T>
class AA
{
public:
AA()
{
}
AA(T a)
{
this->a = a;
}
void print ()
{
cout << "a = " << a << endl;
}
private:
T a;
};
int main()
{
// 注意:函式模板支援隱式呼叫,類模板不支援隱式呼叫,必須說明模板型別
AA<int> a(10);
return 0;
}
類模板物件做函式引數傳遞時:
1)寫一個具體的 模板類 物件
template <typename T>
void print(AA<int> &a)
{
a.print();
template <typename T>
void print(AA<double> &a)
{
a.print();
}
2)使用函式模板
template <typename T>
void print(AA<T> &a)
{
a.print();
}
繼承時:
1)派生一個具體的類,繼承自一個具體的模板類
class BB:public AA<int>
{
public:
BB(int a, int b):AA(a)
{
this->b = b;
}
private:
int b;
};
2)派生一個 類模板
template <typename T>
class C:public AA<T>
{
public:
C(int a, T c):AA(a)
{
}
private:
T c;
};
類的實現
以 複數類 (Complex)為例進行說明
1.類的實現放在類的內部
nclude <iostream>
using namespace std;
template <typename T>
class Complex
{
// 友元函式寫到類的內部
// 雖然寫到了類的內部,但是該函式還是友元函式,不是類的內部函式
friend ostream &operator << (ostream &out, Complex &c)
{
out << c.a << " + " << c.b << "i";
return out;
}
friend Complex mySub(Complex &c1, Complex &c2)
{
Complex tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
public:
Complex(T a = 0, T b = 0)
{
this->a = a;
this->b = b;
}
Complex operator +(Complex &c)
{
Complex tmp(a + c.a, b + c.b);
return tmp;
}
private:
T a;
T b;
};
int main()
{
Complex<int> c1(1, 2), c2(3, 4), c;
// mySub的實現 雖然是寫到了類的內部,但是它還是一個友元函式
// 如果是類的內部函式 呼叫如下: c1.mySub(c2)
// 但因為 mySub 是友元函式,實際是一個外部的全域性函式,所以只能用如下呼叫:mySub(c1, c2);
c = mySub(c1, c2);
cout << c1 << endl;
cout << c << endl;
c = c1 + c2;
cout << c << endl;
return 0;
}
2.類的實現放在類的外部(同一檔案中)
類的成員函式在類的外部實現時,需要將成員函式寫成函式模板;
#include <iostream>
using namespace std;
// 宣告類
template <typename T>
class Complex;
// 函式宣告
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
template <typename T>
class Complex
{
friend ostream &operator<< <T>(ostream &out, Complex<T> &c);
friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);
public:
Complex(T a = 0, T b = 0);
Complex operator +(Complex &c);
private:
T a;
T b;
};
template <typename T>
Complex<T>::Complex(T a = 0, T b = 0)
{
this->a = a;
this->b = b;
}
template <typename T>
Complex<T> Complex<T>::operator +(Complex &c)
{
Complex tmp(a + c.a, b + c.b);
return tmp;
}
template <typename T>
ostream & operator << (ostream &out, Complex<T> &c)
{
out << c.a << " + " << c.b << "i";
return out;
}
template <typename T>
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2)
{
Complex<T> tmp(c1.a - c2.a, c1.b - c2.b);
return tmp;
}
int main()
{
Complex<int> c1(1, 2), c2(3, 4), c;
c = mySub(c1, c2);
cout << c1 << endl;
cout << c << endl;
c = c1 + c2;
cout << c << endl;
return 0;
}
3.類的實現放在類的外部(不同檔案中)
只需要將函式實現“cpp”檔案,改名為“hpp”作為 main.cpp 的標頭檔案即可。
總結:
1)對於在類的內部可 直接實現 的函式,放在類的外部實現時,類中的原型宣告不變;
例如:建構函式、運算子過載(除了 外部 實現的 那 4 種)
template <typename T>
Complex<T>::Complex(T a = 0, T b = 0); //外部原型
//前面的 Complex<T> 說明是 Complex 的成員函式
Complex(T a = 0, T b = 0); //類中宣告
template<typename T>
Complex<T> Complex<T>::operator+(Complex &c); // 外部原型
// 第一個 Complex<T> 是返回值
// 第二個 Complex<T> 說明是Complex 的成員函式
Complex operator+(Complex &c); // 類中宣告
2.對於只能通過 友元函式 實現的運算子過載函式,在類中宣告時 需要在函式名後,“()”前加上 < T >,以及在類名後加上 < T >;在外部實現時,函式原型 只需要在類名後加上< T >
friend ostream operator << <T>(ostream &out, Complex<T> &obj);
//類中宣告
template <typename T>
ostream operator << (ostream &out, Complex<T> &obj);
// 類外部函式原型
3.對於其他的友元函式(無法在類的內部,改變左值的),在類中宣告時,在函式名後,“()”前加上< T >,以及在類名後 加上 < T >;在類的外部宣告時, 需要在類的上面,對 類 和 函式 進行宣告;
friend Complex<T> mySub<T>(Complex<T> &c1, Complex<T> &c2);
// 類中宣告
template <typename T> // 宣告類
class Complex;
template <typename T> // 宣告函式
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
template <typename T> // 外部實現 函式原型
Complex<T> mySub(Complex<T> &c1, Complex<T> &c2);
類模板中的 static 關鍵字
對屬於 同一型別的 模板類的物件 共享 static
對不屬於 同一型別的 模板類的物件 不共享 static
#include <iostream>
using namespace std;
template <typename T>
class A
{
public:
T a;
static T sa;
};
template <typename T>
T A<T>::sa = 1;
int main()
{
A<int> a;
a.sa = 10;
A<double> d;
cout << "sa = " <<d.sa << endl; // sa = 1
cout << "sq = " <<A<int>::sa << endl; // sa = 10
return 0;
}