目录
1、定义
2、验证类模板生成的类定义
3、非类型参数
4、模板别名
5、模板类
6、多个参数类型
7、类型参数默认值
8、模板类作为模板函数的入参
9、模板具体化
10、成员模板
11、将模板类用作类型参数
12、模板类中的友元
1、定义
类模板的概念用法同之前讲解过的函数模板是基本一致的,类模板不是一个完整的可以被单独编译的类定义,只是一个模板而已,因为类型对类模板而言是未知的,编译器会根据使用模板的实际类型在编译期生成对应的完整的类定义,然后用该类定义生成对应的类实例。参考如下示例:
#include <iostream>
#include <typeinfo>
using std::cout;
//template表示声明一个模板,class也可以用typename代替,可以有多个参数类型
//Type表示一个类型名,不一定是具体的类名,可以是int这种基本类型或者模板类型
//注意模板类同模板函数都不是具体的类和函数,不能单独编译
//template <class Type>
template <typename Type>
class Stack
{
private:
Type * items;
int size;
int top;
public:
Stack(int size);
~Stack();
bool isempty();
bool isfull();
bool push(Type & item);
Type & pop();
};
template <class Type>
Stack<Type>::Stack(int size)
{
this->size=size;
this->top=0;
this->items=new Type[size];
cout<<"Stack(int size) size:"<<size <<",Type:"<<typeid(Type).name() <<"\n";
}
template <class Type>
Stack<Type>::~Stack()
{
delete[] items;
}
template <class Type>
bool Stack<Type>::isempty()
{
return top == 0;
}
template <class Type>
bool Stack<Type>::isfull()
{
return top == size;
}
template <class Type>
bool Stack<Type>::push(Type & item)
{
if (top < size)
{
items[top++] = item;
return true;
}
else
return false;
}
template <class Type>
Type & Stack<Type>::pop()
{
if (top > 0)
{
//C++中不能把引用置空,所以这里实际未pop掉
return items[--top];
}
else
//C++中没有空引用,引用不能为空
return items[0];
}
int main(){
Stack<int> a(5);
int b=1,c=2,d=3;
a.push(b);
a.push(c);
a.push(d);
int d2=a.pop();
int c2=a.pop();
int b2=a.pop();
int b3=a.pop();
cout<<"d2->"<<d2<<"\n";
cout<<"c2->"<<c2<<"\n";
cout<<"b2->"<<b2<<"\n";
cout<<"b3->"<<b3<<"\n";
return 0;
}
2、验证类模板生成的类定义
类模板会根据实际使用的类型生成对应类型下的类定义,然后用该类定义生成类实例,怎么验证生成了不同的类定义了?main方法修改如下:
int main(){
Stack<int> a(3);
Stack<int> a2(3);
Stack<double> d(3);
Stack<double> d2(3);
return 0;
}
反汇编main方法,结果如下:
可以看出分别生成了Stack<int> 和Stack<double>两个类定义,他们的构造函数和析构函数的地址是不一样的,而属于同一个类型的a和a2, d和d2调用的构造函数和析构函数的地址是一样的。
3、非类型参数
模板参数中出现的常量表达式称为非类型参数,如下列示例中的n,非类型参数的类型只能是整形,枚举,引用或者指针,注意模板代码不能修改非类型参数的值,也不能获取非类型参数的地址,因为该参数在编译结束后在生成的类定义中会被替换成常量。非类型参数也是模板参数的一部分,因此类型参数相同而非类型参数不同依然会生成不同的类定义。对于数组这类需要在编译期确认数组大小的场景可以使用非类型参数传递数组大小,从而利用栈帧分配内存,效率更高,适合容量小的数组;如果是利用构造函数传递数组大小则只能通过new在堆内存中分配数组内存,并且需要在析构函数中用delete释放数组内存,适合容量较大的数组场景。如下示例:
#include <iostream>
#include <cstdlib>
using std::cout;
#define TEMP template <class T, int n>
//其中n表示非类型或者表达式参数,n的类型只能是整形,枚举,引用或者指针,且必须是常量表达式
//T相同而n不同将生成两个不同的类定义,如ArrayTP<int,5>,ArrayTP<int,6>
TEMP
class ArrayTP
{
private:
T ar[n];
public:
ArrayTP() {};
explicit ArrayTP(const T & v);
//重载下标运算符时应该提供两种形式的重载函数,第一种允许修改元素,第二种不允许
T & operator[](int i);
//方法后的const表示该方法不修改类成员属性
T operator[](int i) const;
void show();
};
TEMP
ArrayTP<T,n>::ArrayTP(const T & v)
{
for (int i = 0; i < n; i++)
ar[i] = v;
}
TEMP
T & ArrayTP<T,n>::operator[](int i)
{
if (i < 0 || i >= n)
{
std::cerr << "Error in array limits: " << i
<< " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
TEMP
T ArrayTP<T,n>::operator[](int i) const
{
if (i < 0 || i >= n)
{
std::cerr << "Error in array limits: " << i
<< " is out of range\n";
std::exit(EXIT_FAILURE);
}
return ar[i];
}
TEMP
void ArrayTP<T,n>::show()
{
for (int i = 0; i < n; i++)
cout<<ar[i]<<",";
cout<<"show end\n";
}
int main(){
ArrayTP<int,6> a(0);
a[0]=1;
a[1]=2;
a[2]=3;
a.show();
return 0;
}
4、模板别名
使用using指令对类模板重命名,使用typedef或者using指令对类模板的实例重命名,基于上节的ArrayTP示例,main方法修改如下:
//模板重命名,声明必须放在函数外
template<typename U>
using array = ArrayTP<U,6>;
int main() {
array<int> a(0);
a[0] = 1;
a[1] = 2;
a[2] = 3;
a.show();
//使用typedef或者using对某个模板类重命名
// typedef ArrayTP<int, 6> arrayi;
using arrayi=ArrayTP<int, 6> ;
arrayi b(0);
b[0] = 1;
b[1] = 2;
b[2] = 3;
b.show();
return 0;
}
5、模板类
用类模板生成的类叫做模板类,模板类同正常的类一样可以作为基类被继承,可以作为组件类,可作为其他模板类的类型参数,只是需要注意模板类的类名必须带上明确的类型参数,如上例中的ArrayTP<int,6>视为一个完整的类名。
用作基类的示例:
class IntArray:public ArrayTP<int,10>{
public:
IntArray();
int sum();
};
IntArray::IntArray():ArrayTP<int,10>(0){
}
int IntArray::sum(){
int sum=0;
for(int i=0;i<10;i++){
sum+=operator [](i);
}
return sum;
}
int main(){
IntArray a;
a[0]=1;
a[1]=2;
a[2]=3;
a.show();
cout<<"sum="<<a.sum()<<"\n";
return 0;
}
用作组合类的示例如下:
class IntArray{
private:
ArrayTP<int,10> array;
public:
IntArray();
int sum();
void set(int i,int val);
int get(int i);
};
IntArray::IntArray():array(0){
}
int IntArray::sum(){
int sum=0;
for(int i=0;i<10;i++){
sum+=array[i];
}
return sum;
}
void IntArray::set(int i,int val){
array[i]=val;
}
int IntArray::get(int i){
return array[i];
}
int main(){
IntArray a;
a.set(0,1);
a.set(1,2);
a.set(2,3);
cout<<"a.get(2)="<<a.get(2)<<"\n";
cout<<"sum="<<a.sum()<<"\n";
return 0;
}
用作模板类型参数时,如果模板类所属的类模板和目标类模板是同一个则称为模板递归,如下示例:
int main(){
//其效果跟二维数组类似,第一维是3,第二维是6
ArrayTP<ArrayTP<double,6>,3> a;
a[0]=ArrayTP<double,6>(1);
a[1]=ArrayTP<double,6>(2);
a[2]=ArrayTP<double,6>(3);
for(int i=0;i<3;i++){
a[i].show();
}
return 0;
}
6、多个参数类型
同函数模板,类模板同样支持多个参数类型,如下示例:
#include <iostream>
#include <string>
#define TEMP template <class T1, class T2,int typeNum>
TEMP
class Pair
{
private:
T1 a;
T2 b;
int type=typeNum;
public:
T1 & first();
T2 & second();
T1 first() const { return a; }
T2 second() const { return b; }
int getType() const { return type; }
Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
Pair() {}
};
TEMP
T1 & Pair<T1,T2,typeNum>::first()
{
return a;
}
TEMP
T2 & Pair<T1,T2,typeNum>::second()
{
return b;
}
int main()
{
Pair<int ,std::string,11> a(1,"test");
std::cout<<"first:"<<a.first()<<",second:"<<a.second()<<",type:"<<a.getType()<<"\n";
return 0;
}
7、类型参数默认值
C++允许给类模板的类型参数和非类型参数提供默认值,注意如果全部使用默认值则类模板后面需加上空的<>,表示这是一个模板类,否则编译器将其视为普通的类而找不到该类的定义。模板函数只允许对非类型参数提供默认值,不能对类型参数提供默认值,但是GUN C下无此限制。如下示例:
#include <iostream>
#include <string>
#define TEMP template <class T1, class T2,int typeNum>
template <class T1=int, class T2=std::string,int typeNum=10>
class Pair
{
private:
T1 a;
T2 b;
int type=typeNum;
public:
T1 & first();
T2 & second();
T1 first() const { return a; }
T2 second() const { return b; }
int getType() const { return type; }
Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
Pair() {}
};
TEMP
T1 & Pair<T1,T2,typeNum>::first()
{
return a;
}
TEMP
T2 & Pair<T1,T2,typeNum>::second()
{
return b;
}
template <class T1=int, class T2=std::string,int typeNum=10>
void show(T1 t,T2 t2){
std::cout<<"T1:"<< t <<",T2:"<<t2<<",type:"<<typeNum<<"\n";
}
int main()
{
Pair<> a(1,"test");
std::cout<<"first:"<<a.first()<<",second:"<<a.second()<<",type:"<<a.getType()<<"\n";
show<>(1,"test2");
return 0;
}
8、模板类作为模板函数的入参
模板类作为模板函数的入参时,模板类的参数类型信息会自动传递到模板函数中,如下示例:
#include <iostream>
#include <string>
#define TEMP template <class T1, class T2,int typeNum>
template <class T1=int, class T2=std::string,int typeNum=10>
class Pair
{
private:
T1 a;
T2 b;
int type=typeNum;
public:
T1 & first();
T2 & second();
T1 first() const { return a; }
T2 second() const { return b; }
int getType() const { return type; }
Pair(const T1 & aval, const T2 & bval) : a(aval), b(bval) { }
Pair() {}
};
TEMP
T1 & Pair<T1,T2,typeNum>::first()
{
return a;
}
TEMP
T2 & Pair<T1,T2,typeNum>::second()
{
return b;
}
template <class T1=int, class T2=int,int typeNum=12>
void show(Pair<T1,T2,typeNum> & pair){
std::cout<<"typeNum:"<<typeNum <<"\n";
std::cout<<"first:"<<pair.first()<<",second:"<<pair.second()<<",type:"<<pair.getType()<<"\n";
}
int main()
{
Pair<int ,std::string,11> a(1,"test");
//如果显示指定,则必须为11,否则报错参数类型匹配错误
//不显示指定的情况下默认使用入参的参数类型信息
show<>(a);
//全部使用默认值时必须带上<>,表示这是一个类模板,否则编译器认为这是一个普通的类而无法解析
Pair<> b(2,"test2");
show<>(b);
return 0;
}
9、模板具体化
部分场景下需要改写模板类的实现以定制部分特殊类型的行为,可通过模板具体化即明确指定在使用某种类型下模板类的实现来实现上述需求,有多个类型参数时还允许部分具体化,即只指定部分类型参数。当编译器匹配具体的模板类时会根据当前参数类型选择具体化程度最高的一个模板类,按需生成类定义。
#include <iostream>
#include <string>
#define TEMP template <class T1, class T2>
template <class T1=int, class T2=std::string>
class Pair
{
private:
T1 a;
T2 b;
public:
T1 & first();
T2 & second();
Pair(T1 i,T2 j):a(i),b(j){}
};
TEMP
T1 & Pair<T1,T2>::first()
{
return a;
}
TEMP
T2 & Pair<T1,T2>::second()
{
return b;
}
//显示具体化,覆盖原来的实现
template <>
class Pair<int,double>
{
private:
int a;
double b;
public:
int & first();
double & second();
Pair(int i,double j):a(i),b(j){}
};
int & Pair<int,double>::first(){
a++;
return a;
}
double & Pair<int,double>::second(){
b++;
return b;
}
//部分具体化,覆盖原来的实现
template <class T1>
class Pair<T1,double>
{
private:
T1 a;
double b;
public:
T1 & first();
double & second();
Pair(T1 i,double j):a(i),b(j){}
};
template <class T1>
T1 & Pair<T1,double>::first(){
a+=2;
return a;
}
template <class T1>
double & Pair<T1,double>::second(){
b+=2;
return b;
}
int main()
{
using std::cout;
//优先使用具体化程度最高的实现
Pair<int,double> a(1,2);
cout<<"first="<<a.first()<<"\n";
cout<<"second="<<a.second()<<"\n";
Pair<double,double> b(1,2);
cout<<"first="<<b.first()<<"\n";
cout<<"second="<<b.second()<<"\n";
Pair<int,int> c(1,2);
cout<<"first="<<c.first()<<"\n";
cout<<"second="<<c.second()<<"\n";
return 0;
}
执行结果如下:
10、成员模板
模板类或者模板方法本身可以作为结构,类或者模板类的成员,注意模板作为成员时其类型参数与外部的模板类型参数是独立的,可以使用外部的类型参数或者单独定义一个类型参数。如下示例:
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class beta
{
private:
//内部模板类
template <typename V>
class hold
{
private:
V val;
public:
hold(V v = 0) : val(v) {}
void show() const { cout << val << endl; }
V Value() const { return val; }
};
hold<T> q;
hold<int> n;
public:
beta( T t, int i) : q(t), n(i) {}
//模板方法
template<typename U>
U blab(U u, T t) { return (n.Value() + q.Value()) * u / t; }
void Show() const { q.show(); n.show();}
};
int main()
{
beta<double> guy(3.5, 3);
guy.Show();
//自动根据参数类型自动将U设为int
cout << guy.blab(10, 2.3) << endl;
//显示指定U的类型
cout << guy.blab<double>(10, 2.3) << endl;
cout << guy.blab(10.0, 2.3) << endl;
return 0;
}
也可以把将模板成员的定义放在模板类的外面,更能体现两者类型参数的独立性,如下所示:
#include <iostream>
using std::cout;
using std::endl;
//T作用于整个beta模板类,调用该模板类必须指明T的具体类型
template<typename T>
class beta {
private:
//V作用于hold模板类
template<typename V>
class hold;
hold<T> q;
hold<int> n;
public:
beta(T t, int i) :
q(t), n(i) {
}
//U只作用于blab模板方法
template<typename U>
U blab(U u, T t);
void Show() const;
};
template<typename T>
template<typename V>
class beta<T>::hold {
private:
V val;
public:
hold(V v = 0) :
val(v) {
}
void show() const {
cout << val << endl;
}
V Value() const {
return val;
}
};
template<typename T>
template<typename U>
U beta<T>::blab(U u, T t) {
return (n.Value() + q.Value()) * u / t;
}
template<typename T>
void beta<T>::Show() const {
q.show();
n.show();
}
int main() {
beta<double> guy(3.5, 3);
guy.Show();
//自动根据参数类型自动将U设为int
cout << guy.blab(10, 2.3) << endl;
//显示指定U的类型
cout << guy.blab<double>(10, 2.3) << endl;
cout << guy.blab(10.0, 2.3) << endl;
return 0;
}
11、将模板类用作类型参数
模板类本身可以作为另一个模板类的类型参数,不同于第四节中模板类作为类型参数时实际是隐式实例化的模板类作为类型参数,这里没有隐式实例化,只是表明该参数是模板类类型而已,具体是哪个模板类根据实际传入的模板类类型确认。如下所示:
#include <iostream>
using std::cout;
template <class T>
class Printer{
private:
T val;
public:
Printer(T i):val(i){}
void show();
};
template <class T>
void Printer<T>::show(){
cout<<"print val="<<val<<"\n";
}
//T是模板类U的类型参数而非Shower模板类的类型参数,U和V才是Shower的类型参数
template <template <typename T> class U,class V>
class Shower
{
private:
U<V> s1;
U<double> s2;
public:
Shower(int i,double j):s1(i),s2(j) {};
void print();
};
template <template <typename T> class U,class V>
void Shower<U,V>::print(){
//这里并不知道U的实际类型,所以编写代码时并不知道s1可能有的方法
//编译时会校验该方法是否存在
s1.show();
s2.show();
}
int main()
{
Shower<Printer,int> a(1,1.2);
a.print();
return 0;
}
12、模板类中的友元
模板类中的友元有三种类型:非模板友元,约束模板友元和非约束模板友元。非模板友元即友元函数不是一个函数模板而是一个明确类型的普通函数;约束模板友元下友元函数是一个函数模板,这里的约束是指友元的参数类型和模板类的参数类型一致下友元才成立;非约束模板友元下友元函数是一个函数模板,友元的参数类型和模板类的参数类型不要求一致,两者是独立的,即模板函数的所有实例是任何一个模板类实例的友元。
非模板友元的示例如下:
#include <iostream>
using std::cout;
using std::endl;
template <typename T>
class HasFriend
{
private:
T item;
static int ct;
public:
HasFriend(const T & i) : item(i) {ct++;}
~HasFriend() {ct--; }
friend void counts();
//不能写成HasFriend,因为这个类不存在
// friend void reports(HasFriend &);
friend void reports(HasFriend<T> &);
};
//只要符合void reports(HasFriend<T> &)的要求都是HasFriend的友元
void reports(HasFriend<int> & hf);
void reports(HasFriend<double> & hf);
template <typename T>
int HasFriend<T>::ct = 0;
//该函数没有参数类型限制,所以改函数是所有HasFriend模板类实例的友元
void counts()
{
cout << "int count: " << HasFriend<int>::ct << "; ";
cout << "double count: " << HasFriend<double>::ct << endl;
}
void reports(HasFriend<int> & hf)
{
cout <<"HasFriend<int>: " << hf.item << endl;
}
void reports(HasFriend<double> & hf)
{
cout <<"HasFriend<double>: " << hf.item << endl;
}
int main()
{
cout << "No objects declared: ";
counts();
HasFriend<int> hfi1(10);
cout << "After hfi1 declared: ";
counts();
HasFriend<int> hfi2(20);
cout << "After hfi2 declared: ";
counts();
HasFriend<double> hfdb(10.5);
cout << "After hfdb declared: ";
counts();
//Eclipse编译器报错有歧义,实际GNU C编译下没有报错
reports(hfi1);
reports(hfi2);
reports(hfdb);
return 0;
}
约束模板友元的示例如下:
#include <iostream>
using std::cout;
using std::endl;
template <typename U> void counts();
template <typename U> void reports(U &);
template <typename T>
class HasFriend
{
private:
T item;
static int ct;
public:
HasFriend(const T & i) : item(i) {ct++;}
~HasFriend() {ct--; }
//用模板类的T来具体化函数模板,即只有函数模板的类型参数和模板类一致友元才成立
friend void counts<T>();
//两种是等价的,<>表示函数模板具体化
// friend void reports<T>(T &);
friend void reports<>(T &);
};
template <typename T>
int HasFriend<T>::ct = 0;
//函数模板定义
template <typename U>
void counts()
{
//HasFriend的参数类型是int,所以只有U是int下友元才成立,如果是double就报错ct是private,不可访问
cout << "count: " << HasFriend<int>::ct << endl;
cout << "count: " << HasFriend<U>::ct << endl;
}
template <typename U>
void reports(U & hf)
{
//在U是double时同上报错item是private,不可访问
HasFriend<int> a(1);
cout <<"HasFriend<U>: " <<a.item << endl;
}
int main()
{
counts<int>();
//编译报错
// counts<double>();
int a=1;
reports(a);
double b=2.1;
//编译报错
// reports(b);
return 0;
}
非约束模板的友元如下:
#include <iostream>
using std::cout;
using std::endl;
template <typename U> void counts();
template <typename U> void reports(U &);
template <typename T>
class HasFriend
{
private:
T item;
static int ct;
public:
HasFriend(const T & i) : item(i) {ct++;}
~HasFriend() {ct--; }
//U和T是独立的,即counts函数的任何实例都是HasFriend模板类任何实例的友元
template <typename U> friend void counts();
template <typename U> friend void reports(U &);
};
template <typename T>
int HasFriend<T>::ct = 0;
template <typename U>
void counts()
{
cout << "count: " << HasFriend<int>::ct << endl;
cout << "count: " << HasFriend<U>::ct << endl;
}
template <typename U>
void reports(U & hf)
{
HasFriend<int> a(1);
cout <<"HasFriend<U>: " <<a.item << endl;
}
int main()
{
counts<int>();
counts<double>();
int a=1;
reports(a);
double b=2.1;
reports(b);
return 0;
}