目錄
1、函數模板
1.1 函數模板概念
1.2 函數模板格式
1.3 函數模板的原理
1.4 函數模板的實例化
1.4.1 隱式實例化
1.4.2 顯式實例化
1.5 模板參數的匹配原則
2、類模板
2.1 類模板的定義格式
2.2 類模板的實例化
講模板之前呢,我們先來談談泛型編程:
泛型編程:編寫與類型無關的通用代碼,是代碼復用的一種手段。模板是泛型編程的基礎。
模板分為兩類:函數模板與類模板
1、函數模板
1.1 函數模板概念
函數模板代表了一個函數家族,該函數模板與類型無關,在使用時被參數化,根據實參類型產生函數的特定類型版本。
1.2 函數模板格式
template<typename T1, typename T2,......,typename Tn>
返回值類型 函數名(參數列表){}
//template<class T>
template<typename T>
void Swap(T& left, T& right)
{T tmp = left;left = right;right = tmp;
}
注意:typename是用來定義模板參數關鍵字,也可以使用class(切記:不能使用struct代替class) 。
1.3 函數模板的原理
函數模板是一個藍圖,它本身并不是函數,是編譯器用使用方式產生特定具體類型函數的模具。所以其實模板就是將本來應該我們做的重復的事情交給了編譯器。
在編譯器編譯階段,對于模板函數的使用,編譯器需要根據傳入的實參類型來推演生成對應類型的函數以供調用。比如:當用double類型使用函數模板時,編譯器通過對實參類型的推演,將T確定為double類型,然后產生一份專門處理double類型的代碼,對于字符類型也是如此。
1.4 函數模板的實例化
用不同類型的參數使用函數模板時,稱為函數模板的實例化。模板參數實例化分為:隱式實例化和顯式實例化。
1.4.1 隱式實例化
隱式實例化是讓編譯器根據實參推演模板參數的實際類型
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int a1 = 10, a2 = 20;double d1 = 10.0, d2 = 20.0;Add(a1, a2);Add(d1, d2);return 0;
}
這里Add(a1, a2)與Add(d1, d2)都是隱式實例化,編譯器會根據實參推出實際類型。
我們再來看一段代碼:
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}int main()
{int i = 1;double d = 1.1;Add(i, d);return 0;
}
該語句不能通過編譯,因為在編譯期間,當編譯器看到該實例化時,需要推演其實參類型通過實參i將T推演為int,通過實參d將T推演為double類型,但模板參數列表中只有一個T,編譯器無法確定此處到底該將T確定為int 或者 double類型而報錯。
注意:在模板中,編譯器一般不會進行類型轉換操作,因為一旦轉化出問題,編譯器就需要背黑鍋Add(i, d);
這里要解決這樣的問題有兩種方式:
1. 用戶自己來強制轉化;
2. 使用顯式實例化。
int main()
{int i = 1;double d = 1.1;Add(i, (int)d);return 0;
}
1.4.2 顯式實例化
顯式實例化是在函數名后的<>中指定模板參數的實際類型
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
int main()
{int i = 1;double d = 1.1;Add<int>(i, d);// 顯式實例化為intAdd<double>(i, d);// 顯示實例化為doublereturn 0;
}
如果類型不匹配,編譯器會嘗試進行隱式類型轉換,如果無法轉換成功編譯器將會報錯。
1.5 模板參數的匹配原則
1. 一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化為這個非模板函數
// 專門處理int的加法函數
int Add(int left, int right)
{return left + right;
}
// 通用加法函數
template<class T>
T Add(T left, T right)
{return left + right;
}
void Test()
{Add(1, 2); // 與非模板函數匹配,編譯器不需要特化Add<int>(1, 2); // 調用編譯器特化的Add版本
}
2. 對于非模板函數和同名函數模板,如果其他條件都相同,在調動時會優先調用非模板函數而不會從該模板產生出一個實例。如果模板可以產生一個具有更好匹配的函數, 那么將選擇模板
// 專門處理int的加法函數
int Add(int left, int right)
{return left + right;
}
// 通用加法函數
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{return left + right;
}
void Test()
{Add(1, 2); // 與非函數模板類型完全匹配,不需要函數模板實例化Add(1, 2.0); // 模板函數可以生成更加匹配的版本,編譯器根據實參生成更加匹配的Add函數
}
3. 模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換
2、類模板
2.1 類模板的定義格式
template<class T1, class T2, ..., class Tn>
class 類模板名
{// 類內成員定義
};
template<class T>
class stack
{
public:stack();//聲明,在類外面定義~stack(){delete[] _a;_top = _capacity = 0;}
private:T* _a;int _top;int _capacity;
};// 注意:類模板中函數放在類外進行定義時,需要加模板參數列表
template<class T>
stack<T>::stack(): _a(nullptr), _top(0), _capacity(0)
{}
2.2 類模板的實例化
類模板實例化與函數模板實例化不同,類模板實例化需要在類模板名字后跟<>,然后將實例化的類型放在<>中即可,類模板名字不是真正的類,而實例化的結果才是真正的類。
int main()
{stack<int> st1;stack<char> st2;return 0;
}
如果我們的棧是要存儲int類型的數據,我們就實例化為int,如果是存儲char類型的數據,就實例化為char類型。