文章目錄
- 泛型編程
- 函數模板
- 函數模板實例化
- 隱式實例化
- 顯式實例化
- 函數模板的匹配規則
- 類模板
- 類模板的實例化
泛型編程
泛型編程旨在削減重復工作,如:
- 將一個函數多次重載不如將他寫成泛型。
void Swap(int& left, int& right) {int temp = left;left = right;right = temp;
}
void Swap(double& left, double& right) {double temp = left;left = right;right = temp;
}
void Swap(char& left, char& right) {char temp = left;left = right;right = temp;
}
就比如非常常用的swap函數,雖然c++支持重載,但是如果對于這種函數我們每一個都要自己去實現,是一件很麻煩并且多余的工作,因為我們將重復邏輯的東西多次實現。
- 比如我們想向一個封裝好的類添加新的數據成員,重新寫一個不如一開始就寫成泛型。
class Stack
{private:int capacity;int size;int* arr;
};
如果我們實現了一個棧,如果他此時要存儲其他的數據類型,那我們就必須還要重新修改一個,這無疑是繁瑣的。
所以,C++
基于重載這一項機制,實現了模板編程,模板作為類或者函數的藍圖,可以針對不同類型,來實現對應操作。
函數模板
函數模板與類型無關,被調用時根據實參類型產生函數的對應類型版本。
template<typename T1, typename T2,......,typename Tn>
//typename即類型名,也可以用class替代,這里的class指的是類型,不能用struct替換
編寫函數模板時,只需要在對應的函數前面加上上面的語句即可,然后將所有需要替換類型的參數改為上面的 T1、T2
即可
template<class T>
void Swap(T& left, T& right) {T temp = left;left = right;right = temp;
}int main()
{int i = 3, j = 4;double a = 3.4, b = 5.6;char x = 'x', y = 'y';Swap(i, j);Swap(a, b);Swap(x, y);cout << i << ' ' << j << endl;cout << a << ' ' << b << endl;cout << x << ' ' << y << endl;
}
當我們調用這個模板函數的時候,是生成一個函數重載并調用三次呢?還是生成三個返回值不同的函數各調用一次呢?
通過地址信息可以看到,它調用的是三個不同的函數。
因此,模板函數本身并不是一個函數,而是一個藍圖,通過識別我們傳入的參數,然后在底層生成一個該類型的模板函數,并調用該函數。
并且這個階段是在預處理的時候就進行了,因為如果它是在編譯階段才進行函數的生成,那肯定是無法通過語法的檢查的,所以這一階段只能在預處理進行。
函數模板實例化
函數模板只在第一次使用的時候才會實例化出來,它的實例化有兩種方法,一種是顯式實例化,一種是隱式實例化。
隱式實例化
template<class T> T Add(const T& left, const T& right)
{return left + right;
}int main()
{int i = 3, j = 4;double a = 3.4, b = 5.6;Add(i, j);Add(a, b);/*Add(i, a);這時編譯器就會報錯,因為需要通過參數推演出對應類型,但是此時就無法推演出到底是將 T 推演成 double 還是將 T 推演成 int*/// 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化Add(i, (int)a); // 強制轉化,將 T 推演成 intreturn 0;
}
顯式實例化
int main() {int i = 3;double x = 3.4;// 顯式實例化Add<int>(i, x);return 0; }
用尖括號來顯式的聲明,STL
中的容器等就是采用這種顯式的實例化來明確參數的類型。
函數模板的匹配規則
- 一個非模板函數可以和一個同名的函數模板同時存在,而且該函數模板還可以被實例化為這個非模板函數。這時如果使用隱式實例化,則會調用非模板函數;如果使用顯式實例化,則調用模板函數。
//非模板函數
int Add(int left, int right)
{return left + right;
}template<class T>
T Add(T left, T right)
{return left + right;
}int main()
{Add(1, 2); // 調用非模板函數Add<int>(1, 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函數
}
- 模板函數不允許自動類型轉換,但普通函數可以進行自動類型轉換。
類模板
類模板的用法和函數模板其實一樣
template<class T>
class Stack
{private:int capacity;int size;T* arr;
};
這里的 Stack
不是一個具體的類,他是類模板,也就是一個藍圖,通過這個模板來識別參數的類型并生成對應的模板類。
所以可以理解為類模板是一個類家族,模板類是通過類模板實例化的具體類。
如果類中的成員函數需要在類外定義的話,需要每一個定義前都要聲明一次類模板的參數列表。
template<class T>
class Stack
{
public:void push();void pop();
private:int capacity;int size;T* arr;
};template<class T>
void Stack<T>::push()
{}template<class T>
void Stack<T>::pop()
{}
還有一個需要注意的地方就是,類模板的所有成員函數都是模板函數。
類模板的實例化
類模板只能夠通過顯式實例化
如 STL
中的幾個容器都是通過類模板實現的:
stack<int> s1;stack<double> s2;
stack<int>
并不是類名,而是顯式實例化指定的類型,stack
才是類名。