一、模闆的起源
1、C/C++是靜态的語言(編譯型語言)
- 1)這類語言有很多的資料類型(int/double/float等等…)在效率和安全性的方面是無可比拟的
- 2)這類語言在很大程度上也給程式員編寫
帶來瓶頸,使程式員不得不為每一種通用代碼
編寫資料類型
,雖然他們在抽象層面是一緻的完全相同或幾乎完全相同的代碼實作
eg:type01.cpp
#include <iostream>
using namespace std;
int max_int(int x, int y)
{
return x > y ? x : y;
}
double max_double(double x, double y)
{
return x > y ? x : y;
}
string max_string(string x, string y)
{
return x > y ? x : y;
}
int main(void)
{
int nx=10, ny=20;
cout << max_int(nx, ny) << endl;
double dx=12.3, dy=45.6;
cout << max_double(dx, dy) << endl;
string sx="hello", sy="world";
cout << max_string(sx, sy) << endl << endl;
return 0;
}
2、借助參數宏可以擺脫資料類型的限制
- 1)宏隻是在預處理階段針對代碼的
純文字替換
- 2)宏本身沒有函數的語義(
)不會對資料類型進行檢查
- 3)是以借助參數宏雖然可以拜托類型的限制和限制,但同時也喪失了對
資料類型的檢查
eg:untype02.cpp
#include <iostream>
using namespace std;
#define Max(x, y) (x > y ? x : y)
int main(void)
{
int nx=10, ny=20;
cout << Max(nx, ny) << endl;
//cout << (nx>ny?nx:ny) << endl;//純文本類型替換
double dx=12.3, dy=45.6;
cout << Max(dx, dy) << endl;
string sx="hello", sy="world";
cout << Max(sx, sy) << endl;//world
char cx[256]="world", cy[256]="hello";
cout << Max(cx, cy) << endl;//hello
//cout << (cx>cy?cx:cy) << endl;//比較的是位址誰大
return 0;
}
3、借助宏建構通用函數的架構
- 1)通過
,讓預處理将這個宏擴充針對不同執行個體化宏
的真正函數資料類型
- 2)将
和宏的通用性
完美結合起來函數的類型安全性
eg:marco03.cpp
#include <iostream>
using namespace std;
//##是拼接
#define MAX(T) T max_##T(T x, T y){\
return x > y ? x : y;\
}
MAX(int)
//int max_int(int x, int y){return x>y ? x:y;}
MAX(double)
//int max_dooublc(double x, double y){...}
MAX(string)
//string max_string(string x, string y){...}
int main(void)
{
int nx=10, ny=20;
cout << max_int(nx, ny) << endl;
double dx=12.3, dy=45.6;
cout << max_double(dx, dy) << endl;
string sx="world", sy="hello";
cout << max_string(sx, sy) << endl;
return 0;
}
eg:macro03_02.cpp
#include <iostream>
using namespace std;
//##是拼接
#define MAX(T) T max_##T(T x, T y){\
return x > y ? x : y;\
}
MAX(int)
//int max_int(int x, int y){return x>y ? x:y;}
MAX(double)
//int max_dooublc(double x, double y){...}
MAX(string)
//string max_string(string x, string y){...}
#define Max(T) max_##T
int main(void)
{
int nx=10, ny=20;
cout << MAX(int)(nx, ny) << endl;
double dx=12.3, dy=45.6;
cout << MAX(double)(dx, dy) << endl;
string sx="world", sy="hello";
cout << MAX(string)(sx, sy) << endl;
return 0;
}
二、函數模闆
1、函數模闆聲明
- 1)函數模闆的聲明形式:
template<class 類型形參1, class 類型形參2, ...>
傳回值類型 函數模闆名(調用形參1, 調用形參2, ...){
...
}
eg:
template<class T>T Max(T x, T y){
return x>y?x:y;
}
可以使用
任何辨別符
作為類型形參的名稱,但使用"T"已經成為一種慣例,"T"表示的是調用者在使用這個函數模闆時指定的
任意資料類型
。
2、函數模闆的使用
- 使用函數模闆
必須對模闆進行執行個體化
- 形式:
函數模闆名<類型實參1, 類型實參2,...>(調用實參1,...);
eg:
Max<int>(123, 456);
Max<double>(12.3,45.6);
Max<string>("hello", "world");
3、分析函數模闆
- 切記編譯器并不把函數模闆編譯成一個可以處理任何資料類型的
,而應該是編譯器在執行個體化函數模闆時根據單一實體
從函數模闆中産生一個真正的函數實體。(注:類型實參
,通過執行個體化産能産生真正的函數實體,函數模闆并不是一個函數實體
)函數模闆可以認為是編譯器生産函數實體的一個依據而已
- 這種用
替換具體資料類型
的過程叫做執行個體化,這個過程産生一個函數模闆的執行個體(函數實體)。函數模闆類型形參
- 隻是使用函數模闆,就會自動引發編譯器執行個體化過程,是以
。程式員不需要額外地請求對函數模闆的執行個體化
eg:functmpl04.cpp
#include <iostream>
using namespace std;
//函數模闆(函數模闆並不是一個函數)
template<class T>T Max(T x, T y){
return x > y ? x : y;
}
/*
Max<int>等價
int Max(int x, int y){return x>y?x:y;}
*/
int main(void)
{
int nx=10, ny=20;
cout << Max<int>(nx, ny) << endl;
//cout << Max(nx, ny) << endl;
double dx=12.3, dy=45.6;
cout << Max<double>(dx, dy) << endl;
string sx="world", sy="hello";
cout << Max<string>(sx, sy) << endl;
return 0;
}
4、函數模闆的擴充
- 可以使用
(基本類型和類類型)執行個體化函數模闆任何資料類型
-
但前提是這個資料類型必須支援函數模闆所要執行的操作
例如:一個不支援“>”運算符操作的類型,執行個體化Max函數模闆,編譯器将報錯。
eg:functmpl04_02.cpp
#include <iostream>
using namespace std;
class Integer{
public:
Integer(int const& i=0):m_i(i){}
bool operator>(Integer const& that)const{
return m_i > that.m_i;
}
friend ostream& operator<<(ostream& os, Integer const& that){
return os << that.m_i;
}
private:
int m_i;
};
//函數模闆(函數模闆並不是一個函數)
template<class T>T Max(T x, T y){
return x > y ? x : y;
}
/*
Max<int>等價
int Max(int x, int y){return x>y?x:y;}
*/
int main(void)
{
int nx=10, ny=20;
cout << Max<int>(nx, ny) << endl;
//cout << Max(nx, ny) << endl;
double dx=12.3, dy=45.6;
cout << Max<double>(dx, dy) << endl;
string sx="world", sy="hello";
cout << Max<string>(sx, sy) << endl;
Integer ix=100, iy=200;
//在沒有重載>的時候下面的語句會報錯
//functmpl04_02.cpp:13:11: error: no match for ‘operator>’ (operand types are ‘Integer’ and ‘Integer’)
//重載>後就不會報錯
cout << Max<Integer>(ix, iy) << endl;
//Integer需要重載<<運算符
return 0;
}
5、二次編譯:編譯器對模闆都會進行兩次編譯
- 第一次編譯發生在執行個體化函數模闆之前(
),先檢查模闆本身内部代碼,檢視基本詞法是否正确(例如:函數模闆内部出現的所有辨別符是否均有出處)對于産生真正函數實體之前
,對于已知類型的調用要檢查調用是否有效
未知類型調用都合理
- 第二次發生在執行個體化之後(
),結合所有使用的類型實參,再次檢查模闆代碼,檢視所有調用是否均有效。産生真正函數實體之後
eg:05complie.cpp
#include <iostream>
using namespace std;
class Integer
{
public:
void foo(){
cout << "Integer::foo()" << endl;
}
int m_i;
};
template<class T>void Max(){
//一下都是第一次編譯
//abcd;//亂寫的內容會報錯
Integer i;//對已知類型的調用
i.foo();//調用合理
//i.abcd();//不合理的調用會報錯
T t;//對未知類型的調用
t.abcd();//調用合理,第二次不合理
//t.ab<c>d();//出現括號也會報錯
}
int main(void)
{
//函數模闆實例化,第二次編譯,未知類型調用未知的調用會報錯
Max<Integer>();
return 0;
}
6、函數模闆的隐式推斷
- 如果函數模闆的
和調用形參
類型形參
相關
例如:
template<class T>T Max(T x, T y){...}
- 那麼在執行個體化函數模闆時即使不顯式指明函數模闆的
,編譯器也有能力根據類型實參
隐式推斷出正确的調用實參的類型
例如:類型實參的類型
Max(123, 456)->Max<>(123, 456)->Max<int>(123, 456);
- 獲得和調用普通函數一緻的文法表現形式
eg:06deduction.cpp
#include <iostream>
using namespace std;
class Integer{
public:
Integer(int const& i=0):m_i(i){}
bool operator>(Integer const& that)const{
return m_i > that.m_i;
}
friend ostream& operator<<(ostream& os, Integer const& that){
return os << that.m_i;
}
private:
int m_i;
};
//函數模闆(函數模闆並不是一個函數)
template<class T>T Max(T x, T y){
return x > y ? x : y;
}
/*
Max<int>等價
int Max(int x, int y){return x>y?x:y;}
*/
int main(void)
{
int nx=10, ny=20;
cout << Max<>(nx, ny) << endl;
//也可以寫成Max(nx, ny), 過程Max<>(nx, ny)==>Max<int>(nx, ny)
double dx=12.3, dy=45.6;
cout << Max<>(dx, dy) << endl;
string sx="world", sy="hello";
cout << Max<>(sx, sy) << endl;
Integer ix=100, iy=200;
//在沒有重載>的時候下面的語句會報錯
//functmpl04_02.cpp:13:11: error: no match for ‘operator>’ (operand types are ‘Integer’ and ‘Integer’)
//重載>後就不會報錯
cout << Max<>(ix, iy) << endl;
//Integer需要重載<<運算符
return 0;
}
7、三種情況不能做隐式推斷
-
調用參數和類型參數不能完全相關
eg:
template<class T, class D>T Max(T x, T y){}
-
隐式推斷不支援隐式類型轉換
eg:
使用時:template<class T>T Max(T x, T y){...}
Max(123, 45.6);
- 傳回值類型不支援隐式推斷
eg:06deduction_02.cpp
#include <iostream>
using namespace std;
class Integer{
public:
Integer(int const& i=0):m_i(i){}
bool operator>(Integer const& that)const{
return m_i > that.m_i;
}
friend ostream& operator<<(ostream& os, Integer const& that){
return os << that.m_i;
}
private:
int m_i;
};
template<class T>T Max(T x, T y){
//調用行參和類型形參完全相關
return x > y ? x : y;
}
template<class D, class T>void Foo(T x){
//調用行參和類型形參不完全相關
}
template<class R, class T>R Bar(T x){
R r;
return r;
}
int main(void)
{
int nx=10, ny=20;
cout << Max<>(nx, ny) << endl;
//也可以寫成Max(nx, ny), 過程Max<>(nx, ny)==>Max<int>(nx, ny)
double dx=12.3, dy=45.6;
cout << Max<>(dx, dy) << endl;
string sx="world", sy="hello";
cout << Max<>(sx, sy) << endl;
Integer ix=100, iy=200;
cout << Max<>(ix, iy) << endl;
//1)
//Foo(nx);//報錯,不能隱式推斷
Foo<double>(nx);//不報錯可以推斷T的類型是int,因爲顯式指定了D爲double
//2)
//Max(nx,dy);//報錯
Max(nx,static_cast<int>(dy));//顯式轉換後不會報錯
//3)
//Bar(nx);//報錯,無法推斷傳回值類型
Bar<double>(nx);//顯式給出R類型爲double
return 0;
}
8、函數模闆的重載
- 普通函數和
構成重載關系。在可執行個體化出該函數的函數模闆
相同情況下編譯器優先選擇普通函數。除非函數模闆可以産生更好的資料類型比對度的執行個體。資料類型比對度
-
但普通函數支援。在傳遞參數時如果需要編譯器做隐式類型轉換,則編譯器選擇普通函數。函數模闆的執行個體化不支援隐式類型轉換
- 可以在執行個體化時用<>強行通知編譯器選擇函數模闆。
- 但是如果讓編譯器隐式
,編譯器讓然堅持選擇限制性較強的版本(即更特殊的版本)。推斷類型
eg:07overload.cpp
#include <iostream>
using namespace std;
void Max(int x, int y)
{
cout << "1:Max(int, int)" << endl;
}
template<class T>void Max(T x, T y)
{
cout << "2:Max(T, T)" << endl;
}
template<class T>void Max(T* x, T* y)
{
cout << "3:Max(T*, T*)" << endl;
}
int main(void)
{
int nx=10, ny=20;
double dx=12.3, dy=45.6;
//1)
Max(nx, ny);//選擇普通函數
Max(dx, ny);//選擇函數模闆
//2)
Max(nx, dy);//選擇普通函數
//3)
Max<>(nx, ny);//強行通知選擇模闆
//4)
Max<>(&nx, &ny);//選擇模闆2
return 0;
}
三、類模闆
1、類模闆的定義
- 在類模闆内部
可以想其他類型一樣,用于類型實參
成員變量,成員函數,成員類型(内部類),甚至基類聲明
.
eg:
template<class A, class B>
class CMath{
public:
A m_a;
B func(){...};
};
- 如果在類外實作成員函數:
template<class 類型參數1,...>
傳回值類型 類模闆名<類型形參1,...>::函數名(調用形參1,...){
函數體實作;
}
例如:
2、類模闆的使用
- 1)使用類模闆必須對類模闆進行執行個體化(
)。産生真正的類
(即不能用于定義對象),隻有通過類型執行個體化成真正的類後才具備類的語義(即可以定義對象)。類模闆本身并不代表一個确定的類型
eg:08clstemplate.cpp
#include <iostream>
using namespace std;
//類模闆(並非真正的類)
template<class T>
class CMath
{
public:
CMath(T const& t1, T const& t2):m_t1(t1), m_t2(t2){}
T sum(){
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
//實例化的原理
//class CMatn<int>{...}
int main(void)
{
int nx=10, ny=20;
//實例化類模闆
CMath<int> math1(nx, ny);
cout << math1.sum() << endl;
double dx=12.3, dy=45.6;
CMath<double> math2(dx, dy);
cout << math2.sum() << endl;
string sx="hello ", sy="world";
CMath<string> math3(sx, sy);
cout << math3.sum() << endl;
return 0;
}
- 2)類模闆被執行個體化時類模闆中的成員函數并沒有執行個體化,
成員函數隻是被調用時才會被執行個體化
(即産生真正成員函數)
注: 成員虛函數除外
- 某些類型雖然并沒有提供類模闆所需要的全部功能但照樣可以執行個體化類模闆,隻要不調用那些未提供功能的成員函數即可
- 3)類模闆的類型實參不支援隐式推斷
eg:08clstemplate_02.cpp
#include <iostream>
using namespace std;
class Integer
{
public:
Integer(int const& i):m_i(i){}
//重載運算符+ <<
/*
Integer operator+(Ineger const& that){
return m_i + thar.m_i;
}
friend ostream& operator<<(ostream& os, const& i){
return os << i.m_i;
}
*/
private:
int m_i;
};
template<class T>
class CMath
{
public:
CMath(T const& t1, T const& t2):m_t1(t1), m_t2(t2){}
T sum(){
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
int main(void)
{
int nx=10, ny=20;
CMath<int> math1(nx, ny);//調用構造函數時才實例化構造函數
math1.sum();//調用sum()函數才實例化sum函數
Integer ix=100, iy=200;
//Integer沒有滿足類模闆提供的加法功能,但是還是能使用類模闆實例化
CMath<Integer> math2(ix, iy);
//math2.sum();//報錯Integer不支援加法功能,隻能重載+運算符後才不報錯
//cout << math2.sum() << endl;//需要重載<<運算符
//CMath<> math3(nx, ny);//報錯,不能隱式推斷類模闆的類型實參
return 0;
}
3、類模闆的靜态成員
- 類模闆中的靜态成員
,而應該是由類模闆執行個體化出的既不是每個對象擁有一份,也不是類模闆擁有一份
,且為該執行個體化定義的每一個真正的類各有一份
。所有對象共享
eg:09static.cpp
#include <iostream>
using namespace std;
template<class T>
class CMath
{
public:
static void print(){
cout << "&m_i:" << &m_i << ",&m_t:" << &m_t << endl;
}
private:
static int m_i;
static T m_t;
};
//靜態成員初始化(這裏暫時不給初值,後面講)
template<class T>int CMath<T>::m_i;
template<class T>T CMath<T>::m_t;
int main(void)
{
CMath<int> a, b;
a.print();
b.print();
CMath<int>::print();
CMath<double> c, d;
c.print();
d.print();
CMath<double>::print();
return 0;
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnLzgDN5UjNxMTMxETNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
4、類模闆的遞歸執行個體化
- 可以使用任何類型來執行個體化類模闆,隻要這個類型提供了類模闆所需要的功能。
- 由
也可以用來類模闆執行個體化所産生的類
,這種做法稱之為類模闆的遞歸執行個體化。執行個體化模闆自身
- 通過這種方法可以建構空間上具有遞歸特性的資料結構(例如:多元數組)
eg:10recursion.cpp
#include <iostream>
using namespace std;
template<class T>
class Arrary
{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
int main(void)
{
Arrary<int> a;
for(size_t i=0; i<10; i++){
a[i] = i+1;
}
for(size_t i=0; i<10; i++){
cout << a[i] << ' ';
}
cout << endl;
//遞歸實例化
Arrary<Arrary<int> > b;//注意不> >中間要有空格,否則編譯器認爲是>>
for(size_t i=0; i<10; i++){
for(size_t j=0; j<10; j++){
b[i][j] = i + j;
}
}
for(size_t i=0; i<10; i++){
for(size_t j=0; j<10; j++){
cout << b[i][j] << '\t';
}
cout << endl;
}
return 0;
}
5、類模闆的特化
- 全局特化:包括全類特化和成員特化
- 全類特化:特化一個類模闆可以特化該類模闆所有的成員函數,相當于
寫了一個重新
。針對某種特定資料類型的具體類
- 聲明形式:
eg:template<>class 類模闆名 <類型參數1,...>{...};
template<>class CMath <char*>{...}
- 聲明形式:
- 成員特化:類模闆特化除了可以對整個類進行特化以外,還可以隻針對某個部分成員函數進行特化
- 聲明形式:
eg:tamplate<> 傳回值類型 類模闆名<類型參數1,...>::成員函數名(調用參數1,...){...}
template<> char* const CMath<char* const>::sum(..){...}
- 聲明形式:
- 全類特化:特化一個類模闆可以特化該類模闆所有的成員函數,相當于
- 局部特化:類模闆的局部特化,除非必要,否則盡量不要特化,因為特化版本過多容易引發編譯器比對歧義。
eg:11special.cpp(全類特化)
#include <iostream>
#include <cstring>
using namespace std;
template<class T>class CMath
{
public:
CMath(T const& t1, T const& t2):m_t1(t1), m_t2(t2){}
T sum(){
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
//全類特化
template<>class CMath<char* const>
{
public:
CMath(char* const& t1, char* const& t2):m_t1(t1), m_t2(t2){}
char* const sum(){
return strcat(m_t1, m_t2);
}
private:
char* const m_t1;
char* const m_t2;
};
int main()
{
int nx=10, ny=20;
CMath<int> m1(nx, ny);
cout << m1.sum() << endl;
string sx="hello", sy="world";
CMath<string> m2(sx, sy);
cout << m2.sum() << endl;
char cx[256]="hello", cy[256]="world";
CMath<char* const> m3(cx, cy);
cout << m3.sum() << endl;
return 0;
}
eg:11special_02.cpp
#include <iostream>
#include <cstring>
using namespace std;
template<class T>class CMath
{
public:
CMath(T const& t1, T const& t2):m_t1(t1), m_t2(t2){}
T sum(){
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
//成員特化
template<>char* const CMath<char* const>::sum()
{
return strcat(m_t1, m_t2);
}
//全類特化
/*
template<>class CMath<char* const>
{
public:
CMath(char* const& t1, char* const& t2):m_t1(t1), m_t2(t2){}
char* const sum(){
return strcat(m_t1, m_t2);
}
private:
char* const m_t1;
char* const m_t2;
};
*/
int main()
{
int nx=10, ny=20;
CMath<int> m1(nx, ny);
cout << m1.sum() << endl;
string sx="hello", sy="world";
CMath<string> m2(sx, sy);
cout << m2.sum() << endl;
char cx[256]="hello", cy[256]="world";
CMath<char* const> m3(cx, cy);
cout << m3.sum() << endl;
return 0;
}
eg:12partspecial.cpp(局部特化)
#include <iostream>
using namespace std;
template<class T1, class T2>class CMath
{
public:
static void foo(){
cout << "1:CMath<T1, T2>" << endl;
}
};
//局部特化
template<class T1>class CMath<T1, short>
{
public:
static void foo(){
cout << "2:CMath<T1, short>" << endl;
}
};
template<class T>class CMath<T, T>
{
public:
static void foo(){
cout << "3:CMath<T, T>" << endl;
}
};
template<class T1, class T2>class CMath<T1*, T2*>
{
public:
static void foo(){
cout << "4:CMath<T*, T*>" << endl;
}
};
int main(void)
{
CMath<int, double>::foo();
CMath<int, short>::foo();
//CMath<short, short>::foo();//?沒法選擇, 會報錯
CMath<int*, double*>::foo();
//CMath<int*, int*>::foo();//?沒法選擇,會報錯
return 0;
}
6、類模闆類型參數預設值
-
1)類模闆的類型參數可以帶預設值
執行個體化類模闆時,如果提供了模闆的類型實參則用所提供的類型實參來執行個體化類模闆,如果沒有提供模闆類型實參則用相應的
來執行個體化模闆。模闆形參的預設類型
- 2)如果模闆的某個類型形參帶有預設值,那麼它後面的模闆形參都必須帶有預設值。
eg:13default.cpp
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T, class D=short>class CMath
{
public:
void print(){
cout << "m_t:" << typeid(m_t).name() <<
", m_d:" << typeid(m_d).name() << endl;
}
private:
T m_t;
D m_d;
};
int main(void)
{
CMath<int, double> m;
m.print();
CMath<int> m2;
m2.print();
return 0;
}
7、類模闆的數值型模闆參數
- 數值形式的模闆參數:類模闆的模闆參數并不限于類型參數,普通數值也可以作為模闆的參數。
eg:14valparam.cpp
#include <iostream>
using namespace std;
template<class T, size_t S=10>class Arrary
{
public:
T& operator[](size_t i){
return m_arr[i];
}
size_t size(){
return S;
}
private:
T m_arr[S];
};
int main(void)
{
//Arrary<int> a;
Arrary<int, 5> a;
for(size_t i=0; i<a.size(); i++){
a[i] = i+1;
}
for(size_t i=0; i<a.size(); i++){
cout << a[i] << ' ';
}
cout << endl;
return 0;
}