1. 关于模板
1.1 基本概念
模板其实是C++泛型编程思想的一种体现。简单来说,泛型编程,意思就是针对广泛类型的编程方式。具体类型可以有不同的实现方式,但是针对广泛类型编程,就能在需要调用时才指定参数类型或者调用类型。
通过把通用逻辑设计为模板,C++得以摆脱了类型的限制,提供了继承机制以外的另一种抽象机制,极大提升了代码的可重用性。
当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。
1.2 模板与普通类定义上区别
模板的声明和定义只能在全局,命名空间或类范围内进行。不能在局部,函数内进行,比如不能在main函数中声明和定义一个模板。
通常,当我们调用一个函数时,编译器只需要掌握函数的声明。类似的,当我们使用一个类类型的对象时,类定义必须是可用的,但成员函数的定义不必已经出现。因此我们将类定义和函数声明放在头文件中,而普通函数和类的成员函数的定义放在源文件中。
然而,模板并不同。为了生成一个实例化版本,编译器需要掌握函数模板或类模板成员函数的定义。因此,与非模板代码不同,模板的头文件通常即包括声明也包括定义。
2. 两种模板
2.1 函数模板
2.1.1 普通函数模板
【格式】
//定义函数模板
//这里class也可以用typename来表示
template<class T1, class T2>返回类型 函数名(参数列表){函数体}
//实例化
函数名(参数表)
【例子】比较函数
//定义
template<typename T>
T getMin(T t1, T t2){
return t1 < t2 ? t1 : t2;
}
//实例化
getMin(3, 2); //方法1
getMin<int>(3, 2);//方法2
模板函数的使用与普通非模板函数使用相同,因为模板函数的参数可以从其传入参数中解析出来。当然,如果函数参数没有包含T类变量,则只能用方法2(不过这种情况的模板函数并没有什么意义)。
2.1.2 成员函数模板
【例子】
//定义
class A{
public:
template<typename T>
bool templateTest(T t1, T t2);
};
template<typename T>
bool A::templateTest(T t1, T t2){
return t1 < t2;
}
//实例化
A a;
a.templateTest<int>(2, 1);
2.2 类模板
我们既可以在类模板内部,也可以在类模板外部为其定义成员函数,且定义在类模板内的成员函数被隐式声明为内联函数。
类模板的成员函数本身是一个普通函数,但是类模板的每个实例都有其自己版本的成员函数,因此类模板的成员函数具有和模板相同的模板参数。因而,定义在类模板之外的成员函数就必须以关键字template开始,后接类模板参数列表。
【格式】
//定义模板类
template<class T1, class T2> class 类名{};
//类外定义成员函数
template<class T1, class T2> 函数返回类型 类名<T1, T2>::函数名(函数参数列表){函数体}
//实例化
类名<参数列表> 对象名;
对象名.类成员变量/成员函数;
【例子】定义栈的模板类
template<class T>
class Stack{
public:
Stack(int size = 100);
~Stack();
bool isEmpty();
bool isFull();
void push_back(const T &t);
T pop_back();
private:
T* stack;
int capacity;
int top;
};
template<class T>
Stack<T>::Stack(int size){
capacity = size;
stack = new T[capacity];
top = -1;
}
template<class T>
Stack<T>::~Stack(){
top = -1;
delete[] stack;
}
template<class T>
bool Stack<T>::isEmpty(){
if(top >= 0) return false;
else return true;
}
template<class T>
bool Stack<T>::isFull(){
if(top < capacity - 1)
return false;
else return true;
}
template<class T>
void Stack<T>::push_back(const T &t){
if(isFull()){
cout << "is full" << endl;
return;
}
stack[++top] = t;
}
template<class T>
T Stack<T>::pop_back(){
if(isEmpty()){
cout << "is empty" << endl;
return 0;
}
top = top - 1;
return stack[top + 1];
}
//实例化
Stack<int> int_stack;
int_stack.push_back(1);
3. 模板特化/专门化
有时通用的函数模板不能解决个别类型的问题,因此,我们必须对其进行定制,这就是模板特化。模板特化必须把所有的模版参数全部指定。
模板特化分为两种:全特化和偏特化。全特化是所有的模板参数都被进行特化,偏特化则是局部参数的特化。
3.1 全特化
3.1.1 模板函数的特化
前面,我们定义了getMin函数模板。对于int、float、char等类型,它都可以很好的工作,但是对于char *却没有办法,因此我们可以针对其进行特化。
template<>
const char * getMin<char *>(const char *t1, const char *t2){
return (strcmp(t1,t2) < 0) ? t1 : t2;
}
3.1.2 模板类的特化
前面,我们定义了Stack类模板。我们可以用int、char、bool去实例化它。但是,因为bool类型只需要一个二进制位就可以对其进行存储,使用一个字或者一个字节都是浪费存储空间的,因此,我们可以对它进行特化。
template <>
class stack<bool> {
//...
};
3.2 偏特化
3.2.1 函数模板的偏特化
严格的来说,函数模板并不支持偏特化,但由于可以对函数进行重载,所以可以达到类似于类模板偏特化的效果。比如,我们有如下函数:
template <class T> void f(T); (a)
根据重载规则,对(a)进行重载。
template < class T> void f(T*); (b)
如果将(a)称为基模板,那么(b)称为对基模板(a)的重载,而非对(a)的偏特化。
3.2.2 类模板的偏特化
c++标准库中,包含了类vector的定义。我们可以将第一个参数被绑定到bool类型,而另一个参数仍未绑定需要由用户指定。
template <class T, class Allocator>
class vector {
// … //
};
template <class Allocator>
class vector<bool, Allocator> {
//…//
};
4. 容器适配器
点击链接,感觉这篇博文把容器适配器讲得挺清楚的。
5. 模板与虚函数
5.1 模板类中使用虚函数
#include <iostream>
using namespace std;
template<class T>
class A{
public:
virtual void f1();
virtual void f2() = 0;
};
template<class T> void A<T>::f1(){
cout << "A::f1()" << endl;
}
template<class T>
class B : public A<T>{
public:
void f1();
void f2();
};
template<class T> void B<T>::f1(){
cout << "B::f1()" << endl;
}
template<class T> void B<T>::f2(){
cout << "B::f2()" << endl;
}
int main(){
B<int>* p2 = new B<int>; p2->f1();
A<int>* p3 = new B<int>; p3->f1();
}
输出结果:
B::f1()
B::f1()
非模板类怎么用虚函数,模板类就怎么用。模板是不影响类的多态的。但是需要注意的是:A<int>和A<char>是两个不完全相同的类,因此A<int>不是B<char>的基类。
5.2 模板成员函数不可以是虚函数
模板定义本身不参与编译,而是编译器根据模板的用户使用模板时提供的类型参数生成代码,再进行编译,这一过程被称为模板实例化。用户提供不同的类型参数,就会实例化出不同的代码。
因为每个包含虚函数的类具有一个virtual table(vtable),包含该类的所有虚函数的地址,因此vtable的大小是确定的。成员函数模板只有被使用时才会被实例化,将其声明为虚函数会使vtable的大小不确定。所以,成员函数模板不能为虚函数。(参考)
6. 模板类型转换
假设我们定义了Shape和Circle类,代码如下:
//shape.h
class Shape {
};
class Circle : public Shape {
};
我们希望可以这么用:
#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代码如下:
template <class T>
class Stack {
public:
Stack(int size = 100);
~Stack();
bool isEmpty();
bool isFull();
void push_back(const T &t);
T pop_back();
template<class T2> operator Stack<T2>();
private:
T* stack;
int capacity;
int top;
};
//这里只写了转换运算符函数
template <class T> template <class T2> Stack<T>::operator Stack<T2>() {
Stack<T2> StackT2;
for (int i = 0; i < top; i++) {
StackT2.push_back((T2)m_pT[i]);
}
return StackT2;
}
这样,Stack