模板类
跟之前介绍过的模板函数类似,模板类并不是一个类,而是像编译器解释了如何生成一个类。所以,声明一个模板类的时候是不会占用内存的,只有在将模板具体化,形成真正可以使用类时,才会为该类分配内存。
基本语法
template <typename T>
class stack{
private:
static int num = 0;
int cap;
T * st;
};
注意,当类模板中存在静态变量的时候,并不是所有由该模板初始化出来的类都共享这一个变量。而是所有由相同参数具体化出来的类共享同一个静态变量。例如所有的Stack<int>
对象共享同一个num
,而所有的Stack<double>
共享另一个num
。因为静态变量是由一个类生成的所有对象所共享的,而模板类不是一个类,由模板类具体化出来的才是真正的类。
可以使用模板声明传递参数
在使用模板的时候我们可以看到,template <typename T>
类似一个函数签名,而T
就是这个函数签名里面的形参,typename
就是参数类型。typename
表示该位置接收一个类型名作为参数。
除了typename
接收一个类型名作为参数之外,模板还可以接收一个数据作为参数,例如一个int
类型的数据。例如:
template <typename T, int n> //接收一个整型数据作为模板的参数
class Stack{
private:
static int num = 0;
int cap;
T st[n]; //用接收到的数据初始化数组长度
};
注意模板参数只能接收整型
,枚举
,引用
和指针
类型的数据,并且在使用时传入数据的时候,只能使用常量表达式传入数据。
//正确
Stack<int, 4> s;
//错误,传入数据的时候只能使用常量表达式
int x = 4;
Stack<double, x> s;
具体化的方式
跟函数模板一样,类模板的具体化也有不同的方式。
隐式实例化化
这是最普通的具体化,不需要用户自己指出,编译器会在使用用户用模板声明一个类的时候自动生成类。例如:
Stack<int, 4> s;
显式实例化
使用一个语句来向编译器发出指令,让其立即生成一个类,即使现在不需要这个类。例如:
template class Stack<int, 5>;
显式具体化
之前在介绍模板函数的时候也提到过,显式实例化跟显式具体化的区别是,一个不需要修改模板,另一个需要修改模板。显式具体化的作用是,为特定的模板参数实现对应的类。例如:
template <>
class stack<double , 5>{
private:
static int num;
int cap = 5;
double * st;
};
上面代码的含义是当模板参数为<double, 5>
时,使用另外给出的类实现方式,而放弃原来的方式。
部分具体化
也可以在显式具体化时,之规定部分参数的值,例如:
template <int n>
class stack<double, n>{
private:
static int num;
int cap = 5;
double * st;
};
上面代码表示的是,如果模板参数的第一个参数为double
,则使用另外给定的类型。这里只规定了第一个参数的值,而没有规定第二个参数,所以是部分显式具体化。
指针部分具体化
如果传入的参数是一个指针,则使用给定的实现:
template <typename T>
class stack{
private:
static int num;
int cap;
T st[4];
public:
stack(){
cout << "normal class" << endl;
}
};
template <typename T>
class stack<T*>{ //表明参数为指针时,使用该实现
private:
static int num;
int cap = 5;
T* st;
public:
stack(){
cout << "pointer class" << endl;
}
};
stack<int> s; //使用第一个实现,普通实现
stack<int*> s1; //使用第二个实现,指针专用实现
内部模板
定义一个模板的时候也可以在一个类模板的内部定义另一个类模板。例如:
template <typename T>
class Outer{
private:
template<typename U>
class Inner{
private:
U u;
public:
U getU();
};
Inner<int> intInner;
Inner<T> argInner;
public:
T getU(){
return argInner.getU();
}
};
//如果要在类外定义函数,使用这种嵌套模板的格式
template <typename T>
template <typename U>
U Outer<T>::Inner<U>::getU(){
return u;
}
使用模板作为参数
模板也可以接收另外一个模板作为参数,例如下面实现的Store
类,接收模板作为参数,以使得在使用的时候可以改变数据的存取方式:
template <typename T> class Stack{};
template <typename T> class Queue{};
template <template<typename T> class Ag, typename u>
class Store{
private:
Ag<u> st;
};
Store<Stack, int> s1;
Store<Queue, double> s2;
这样就能在运行的时候决定Store
类运用哪种数据结构组织数据。
模板类和友元
在模板类中声明友元的时候,根据友元的类型不同,具体化类时的行为不同,可以分为三类。
非模板友元
当模板类的友元不是模板函数时,其声明方式跟普通的友元声明方式没有区别。
template <typename T>
class Origin{
private:
T val;
public:
friend void printVal(Origin<T>);
Origin(int v): val(v){}
};
//显式具体化模板
void printVal(Origin<int> o){
cout << o.val << endl;
}
Origin<int> o(3);
printVal(o);
因为上面的友元函数不是模板函数,所以没有办法接收一个没有实现确定好的模板,而只能接收一个类作为参数,所以在函数签名中只能先对模板显式具体化。
这种用法不常见,并且有些编译器可能会发出警告,但不影响运行。
约束模板友元
当模板类的友元函数是一个函数模板,可以使用这种形式声明友元函数。
//主要在类中显式具体化(不是声明)友元之前要声明模板
template <typename T> void printVal(T);
template <typename T>
class Origin{
private:
T val;
public:
//本质上是在模板类中显式具体化一个函数
friend void printVal<>(Origin<T>);
Origin(int v): val(v){}
};
template <typename T>
void printVal(T o){
cout << o.val << endl;
}
Origin<int> o(3);
printVal(o);
上面实现方法实质上是在类模板具体化的时候显式具体化一个友元函数。其具体过程如下:
- 当具体化类模板时,类模板的参数已经确定了,所以
Origin
类已经可以生成出来了。 - 在生成类的时候,执行到
friend void printVal<>(Origin<T>)
这里时,显式从函数模板中具体化一个参数类型为Origin<T>
的函数,因为这个时候模板参数T
的参数已经给定了,所以可以生成出一个参数类型确定的,具体的函数,并将该函数声明为友元函数。
非约束模板友元
通过在模板类中声明友元函数模板,可以创建非约束模板友元。
template <typename T>
class Origin{
private:
T val;
public:
template<typename U> friend void printVal(U);
Origin(int v): val(v){}
};
template <typename T>
void printVal(T o){
cout << o.val << endl;
}
Origin<int> o(3);
printVal(o);
这样就是直接声明printVal()
这个模板为友元模板,而不是像上面那样先显式具体化模板,在将生成的函数声明成友元函数。