6、【C++】模板
C++ 模板
模板是泛型程式設計的基礎,泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼。
1、函式模板
int swap(int &a, int &b){int temp = a;a = b; b=temp;}
float swap(float &a, float &b){float temp = a;a = b; b=temp;}
char swap(char &a, char &b){char temp = a;a = b; b=temp;}
在這上面三條語句當中,int float char 這三種資料型別,但是他們具體實現的函式是一樣的,所以我們就是想把函式作為引數傳入進去,然後讓計算機幫我們實現這三條語句的編寫。
函式模板關鍵字:template;typename;class
函式模板可以用來建立一個通用的函式,以支援多種不同的形參,避免過載函式的函式體重複設計。它的最大特點是把函式使用的資料型別作為引數。
函式模板的宣告形式為:
template<typename 資料型別引數識別符號>
<返回型別><函式名>(引數表)
{
函式體
}
【示例】
//method.h
template<typename T> void swap(T& t1, T& t2);
#include "method.cpp"
template<typename T>
void swap(T& t1, T& t2) {
T tmpT;
tmpT = t1;
t1 = t2;
t2 = tmpT;
}
//main.cpp
#include <stdio.h>
#include "method.h"
int main() {
//模板方法
int num1 = 1, num2 = 2;
swap<int>(num1, num2);
printf("num1:%d, num2:%d\n", num1, num2);
return 0;
}
這裡使用swap函式,必須包含swap的定義,否則編譯會出錯,這個和一般的函式使用不一樣。所以必須在method.h檔案中加入#include “method.cpp”。
2、類模板
正如我們定義函式模板一樣,我們也可以定義類模板。泛型類宣告的一般形式如下所示:
template <class type> class class-name
{
……
}
在這裡,type 是佔位符型別名稱,可以在類被例項化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型資料型別。
考慮我們寫一個簡單的棧的類,這個棧可以支援int型別,long型別,string型別等等,不利用類模板,我們就要寫三個以上的stack類,其中程式碼基本一樣,通過類模板,我們可以定義一個簡單的棧模板,再根據需要例項化為int棧,long棧,string棧。
類模板定義
//類模板定義
//statck.h
template <class T>
class Stack {
public:
Stack();//建構函式
~Stack();//解構函式
void push(T t);
T pop();
bool isEmpty();
private:
T *m_pT;
int m_maxSize;
int m_size;
};
//stack.cpp
//建構函式
template <class T>
Stack<T>::Stack(){
m_maxSize = 100;
m_size = 0;
m_pT = new T[m_maxSize];
}
//解構函式
template <class T>
Stack<T>::~Stack() {
delete [] m_pT ;
}
template <class T>
void Stack<T>::push(T t) {
m_size++;
m_pT[m_size - 1] = t;
}
template <class T>
T Stack<T>::pop() {
T t = m_pT[m_size - 1];
m_size--;
return t;
}
template <class T>
bool Stack<T>::isEmpty() {
return m_size == 0;
}
//main.cpp
#include <stdio.h>
#include "stack.h"
int main() {
Stack<int> intStack;
intStack.push(1);
intStack.push(2);
intStack.push(3);
while (!intStack.isEmpty()) {
printf("num:%d\n", intStack.pop());
}
return 0;
}
3、模板引數
模板可以有型別引數,也可以有常規的型別引數int,也可以有預設模板引數,例如:
template<class T, T def_val>
class Stack
{
...
}
在類模板的示例中,有一個限制,就是最多隻能支援100個元素,我們可以使用模板引數配置這個棧的最大元素數,如果不配置,就設定預設最大值為100,程式碼如下:
//statck.h
template <class T,int maxsize = 100>
class Stack {
public:
Stack();
~Stack();
void push(T t);
T pop();
bool isEmpty();
private:
T *m_pT;
int m_maxSize;
int m_size;
};
//stack.cpp
template <class T,int maxsize>
Stack<T, maxsize>::Stack(){
m_maxSize = maxsize;
m_size = 0;
m_pT = new T[m_maxSize];
}
template <class T,int maxsize>
Stack<T, maxsize>::~Stack() {
delete [] m_pT ;
}
template <class T,int maxsize>
void Stack<T, maxsize>::push(T t) {
m_size++;
m_pT[m_size - 1] = t;
}
template <class T,int maxsize>
T Stack<T, maxsize>::pop() {
T t = m_pT[m_size - 1];
m_size--;
return t;
}
template <class T,int maxsize>
bool Stack<T, maxsize>::isEmpty() {
return m_size == 0;
}
//main.cpp
#include <stdio.h>
#include "stack.h"
int main() {
int maxsize = 1024;
Stack<int,1024> intStack;
for (int i = 0; i < maxsize; i++) {
intStack.push(i);
}
while (!intStack.isEmpty()) {
printf("num:%d\n", intStack.pop());
}
return 0;
}
4、模板專門化
當我們要定義模板的不同實現,我們可以使用模板的專門化。例如我們定義的stack類模板,如果是char*型別的棧,我們希望可以複製char的所有資料到stack類中,因為只是儲存char指標,char指標指向的記憶體有可能會失效,stack彈出的堆疊元素char指標,指向的記憶體可能已經無效了。還有我們定義的swap函式模板,在vector或者list等容器型別時,如果容器儲存的物件很大,會佔用大量記憶體,效能下降,因為要產生一個臨時的大物件儲存a,這些都需要模板的專門化才能解決。
(1)函式模板專門化
假設我們swap函式要處理一個情況,我們有兩個很多元素的vector,在使用原來的swap函式,執行tmpT = t1要拷貝t1的全部元素,佔用大量記憶體,造成效能下降,於是我們系統通過vector.swap函式解決這個問題,程式碼如下:
//method.h
#include "method.cpp"
template<class T> void swap(T& t1, T& t2);
#include <vector>
using namespace std;
template<class T>
void swap(T& t1, T& t2) {
T tmpT;
tmpT = t1;
t1 = t2;
t2 = tmpT;
}
//模板專門化
template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
t1.swap(t2);
}
template<>字首表示這是一個專門化,描述時不用模板引數,使用示例如下:
//main.cpp
#include <stdio.h>
#include <vector>
#include <string>
#include "method.h"
int main() {
using namespace std;
//模板方法
string str1 = "1", str2 = "2";
swap(str1, str2);
printf("str1:%s, str2:%s\n", str1.c_str(), str2.c_str());
vector<int> v1, v2;
v1.push_back(1);
v2.push_back(2);
swap(v1, v2);
for (int i = 0; i < v1.size(); i++) {
printf("v1[%d]:%d\n", i, v1[i]);
}
for (int i = 0; i < v2.size(); i++) {
printf("v2[%d]:%d\n", i, v2[i]);
}
return 0;
}
vector的swap程式碼還是比較侷限,如果要用模板專門化解決所有vector的swap,該如何做呢,只需要把下面程式碼
template<> void swap(std::vector<int>& t1, std::vector<int>& t2) {
t1.swap(t2);
}
改為
template<class V> void swap(std::vector<V>& t1, std::vector<V>& t2) {
t1.swap(t2);
}
就可以了,其他程式碼不變。
(2)類模板專門化
//compare.h
template <class T>
class compare
{
public:
bool equal(T t1, T t2)
{
return t1 == t2;
}
};
#include <iostream>
#include "compare.h"
int main()
{
using namespace std;
char str1[] = "Hello";
char str2[] = "Hello";
compare<int> c1;
compare<char *> c2;
cout << c1.equal(1, 1) << endl; //比較兩個int型別的引數
cout << c2.equal(str1, str2) << endl; //比較兩個char *型別的引數
return 0;
}
在比較兩個整數,compare的equal方法是正確的,但是compare的模板引數是char*時,這個模板就不能工作了,於是修改如下:
//compare.h
#include <string.h>
template <class T>
class compare
{
public:
bool equal(T t1, T t2)
{
return t1 == t2;
}
};
template<>class compare<char *>
{
public:
bool equal(char* t1, char* t2)
{
return strcmp(t1, t2) == 0;
}
};
main.cpp檔案不變,此程式碼可以正常工作。
5、模板型別轉換
還記得我們自定義的Stack模板嗎,在我們的程式中,假設我們定義了Shape和Circle類,程式碼如下:
//shape.h
class Shape {
};
class Circle : public Shape {
};
然後我們希望可以這麼使用:
//main.cpp
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
Stack<Circle*> pcircleStack;
Stack<Shape*> pshapeStack;
pcircleStack.push(new Circle);
pshapeStack = pcircleStack;
return 0;
}
這裡是無法編譯的,因為Stack<Shape*>不是Stack<Circle*>的父類,然而我們卻希望程式碼可以這麼工作,那我們就要定義轉換運算子了,Stack程式碼如下:
//statck.h
template <class T>
class Stack {
public:
Stack();
~Stack();
void push(T t);
T pop();
bool isEmpty();
template<class T2> operator Stack<T2>();
private:
T *m_pT;
int m_maxSize;
int m_size;
};
#include "stack.cpp"
template <class T>
Stack<T>::Stack(){
m_maxSize = 100;
m_size = 0;
m_pT = new T[m_maxSize];
}
template <class T>
Stack<T>::~Stack() {
delete [] m_pT ;
}
template <class T>
void Stack<T>::push(T t) {
m_size++;
m_pT[m_size - 1] = t;
}
template <class T>
T Stack<T>::pop() {
T t = m_pT[m_size - 1];
m_size--;
return t;
}
template <class T>
bool Stack<T>::isEmpty() {
return m_size == 0;
}
template <class T> template <class T2>
Stack<T>::operator Stack<T2>() {
Stack<T2> StackT2;
for (int i = 0; i < m_size; i++) {
StackT2.push((T2)m_pT[m_size - 1]);
}
return StackT2;
}
//main.cpp
#include <stdio.h>
#include "stack.h"
#include "shape.h"
int main() {
Stack<Circle*> pcircleStack;
Stack<Shape*> pshapeStack;
pcircleStack.push(new Circle);
pshapeStack = pcircleStack;
return 0;
}
這樣,Stack
6、其他
一個類沒有模板引數,但是成員函式有模板引數,是可行的,程式碼如下:
class Util {
public:
template <class T> bool equal(T t1, T t2) {
return t1 == t2;
}
};
int main() {
Util util;
int a = 1, b = 2;
util.equal<int>(1, 2);
return 0;
}
甚至可以把Util的equal宣告為static,程式碼如下:
class Util {
public:
template <class T> static bool equal(T t1, T t2) {
return t1 == t2;
}
};
int main() {
int a = 1, b = 2;
Util::equal<int>(1, 2);
return 0;
}