类模板高级(1)
- 1.类模板具体化
- 部分具体化
- 完全具体化
- 2.类模板与继承
- 2.1模板类继承普通类
- 2.2普通类继承模板类的实例化版本
- 2.3普通类继承类模板
- 2.4模板类继承模板类
- 2.5模板类继承模板参数给出的类
1.类模板具体化
有了函数模板具体化的基础,学习类模板的具体化很简单。类模板具体化有两种方式,分别为部分具体化和完全具体化。假如有类模板:
template<class T1,class T2>
class AA
{
public:
T1 m_a;
T2 m_b;
AA(T1 a,T2 b):m_a(a),m_b(b)
{
cout<<"类模板构造函数"<<endl;
}
void show();
};
template<class T1,class T2>
void AA<T1,T2>::show()
{
cout<<m_a<<" "<<m_b<<endl;
}
这里把方法写在类外是为了更好地区分语法上的差别。这个模板的部分具体化和完全具体化有什么差别呢?
部分具体化
部分具体化是类模板特有的,它是指模板类的部分通用类型具有指定的数据类型,示例如下:
template<class T1>
class AA<T1,string>
{
public:
T1 m_a;
string m_b;
AA(T1 a,string b="ZhangSan"):m_a(a),m_b(b)
{
cout<<"类模板部分具体化构造函数"<<endl;
}
void show();
};
template<class T1>
void AA<T1,string>::show()
{
cout<<m_a<<" "<<m_b<<endl;
}
这就是AA模板类的一个不完全具体化版本了。这个AA模板缺省了一个通用类型参数,在类外对方法进行定义时需要指出AA的两个模板参数,具体参数也不可以省略,其余不变。
完全具体化
完全具体化就是类模板完全没有通用参数,它才像是函数模板具体化,举例如下:
template<>
class AA<char,string>
{
public:
char m_a;
string m_b;
AA(char a='m',string b="ZhangSan"):m_a(a),m_b(b)
{
cout<<"类模板部分具体化构造函数"<<endl;
}
void show();
};
void AA<char,string>::show()
{
cout<<m_a<<" "<<m_b<<endl;
}
这里有一个细节需要大家注意,我们在类外定义方法时不再需要写template标识符,但依然需要注明AA的两个具体的模板参数。
下面我们来调用一下模板类,看看调用的优先级:
int main()
{
AA<int,int> aa(10,20);
aa.show();
AA<int,string> bb(10);
bb.show();
AA<char,string> cc('w',"LiSi");
cc.show();
}
可以看到,如果满足完全具体化类的条件,会优先使用完全具体化类,如不满足,满足不完全具体化类条件优先使用不完全具体化类。只有当二者都不满足时才会使用普通的模板类。
2.类模板与继承
模板类的花样很多,因此类的继承玩法也很多。我们总结五种常用的用法,但其实只需知晓原理就行,用到的时候如果忘记了可以回来查看。
2.1模板类继承普通类
这个任务最简单,我们只需要把模板类当成普通类,写一个集成即可:
class AA
{
public:
int m_a;
AA(int a):m_a(a)
{cout<<"这里是AA的构造函数"<<endl;}
void fun1()
{cout<<"这里是fun1函数 "<<m_a<<endl;}
};
template<class T>
class BB:public AA
{
public:
T m_b;
BB(T b,int a):AA(a),m_b(b)
{cout<<"这里是BB的构造函数"<<endl;}
void fun2()
{cout<<"这里是fun2函数 "<<m_b<<endl;}
};
int main()
{
BB<string> b("ZhangSan",10);
b.fun1();
b.fun2();
}
// 输出为:这里是AA的构造函数
// 这里是BB的构造函数
// 这里是fun1函数 10
// 这里是fun2函数 ZhangSan
与普通的类继承一样,把基类的构造函数安排好就可以了。
2.2普通类继承模板类的实例化版本
实例化的类模板和普通类没有区别,继承方法也是一样。我们对上例中两个类稍作调整:
template<class T>
class BB
{
public:
T m_b;
BB(T b):m_b(b)
{cout<<"这里是BB的构造函数"<<endl;}
void fun2()
{cout<<"这里是fun2函数 "<<m_b<<endl;}
};
class AA:public BB<string>
{
public:
int m_a;
AA(int a,string b):BB(b),m_a(a)
{cout<<"这里是AA的构造函数"<<endl;}
void fun1()
{cout<<"这里是fun1函数 "<<m_a<<endl;}
};
int main()
{
AA b(10,"ZhangSan");
b.fun1();
b.fun2();
}
// 输出为:这里是BB的构造函数
// 这里是AA的构造函数
// 这里是fun1函数 10
// 这里是fun2函数 ZhangSan
继承一个实例化的模板类和继承普通类没有区别,但这种继承也并不实用。
2.3普通类继承类模板
继承模板类的一个具体化版本并不实用,我们更多用到的是继承模板类本身,以保留模板类的特性。但想做到这样,也得给子类加上模板头:
template<class T>
class BB
{
public:
T m_b;
BB(T b):m_b(b)
{cout<<"这里是BB的构造函数"<<endl;}
void fun2()
{cout<<"这里是fun2函数 "<<m_b<<endl;}
};
template<class T>
class AA:public BB<T>
{
public:
int m_a;
AA(int a,T b):BB<T>(b),m_a(a)
{cout<<"这里是AA的构造函数"<<endl;}
void fun1()
{cout<<"这里是fun1函数 "<<m_a<<endl;}
};
int main()
{
AA<string> b(10,"ZhangSan");
b.fun1();
b.fun2();
}
// 输出为:这里是BB的构造函数
// 这里是AA的构造函数
// 这里是fun1函数 10
// 这里是fun2函数 ZhangSan
AA现在也被迫成了模板类,所以我们实例化AA的时候需要指定模板通用类型。此外,我们在子类AA的构造函数中需要使用BB的构造函数,也应当注意语法的变化。这种继承也是比较常用的。
2.4模板类继承模板类
有了普通类继承模板类的基础,模板类继承模板类就非常好理解。回到上例,假如AA(子类)本身就是一个模板类,通用参数的名字是U,我们把基类的通用参数加到template里就可以了,其他的语法基本不需要改:
template<class T>
class BB
{
public:
T m_b;
BB(T b):m_b(b)
{cout<<"这里是BB的构造函数"<<endl;}
void fun2()
{cout<<"这里是fun2函数 "<<m_b<<endl;}
};
template<class U,class T>
class AA:public BB<T>
{
public:
U m_a;
AA(U a,T b):BB<T>(b),m_a(a)
{cout<<"这里是AA的构造函数"<<endl;}
void fun1()
{cout<<"这里是fun1函数 "<<m_a<<endl;}
};
int main()
{
AA<int,string> b(10,"ZhangSan");
b.fun1();
b.fun2();
}
// 输出为:这里是BB的构造函数
// 这里是AA的构造函数
// 这里是fun1函数 10
// 这里是fun2函数 ZhangSan
2.5模板类继承模板参数给出的类
这是一种很新奇的玩法,简单地说就是基类是不确定的,具体继承谁要在调用时才会确定,很像是容器嵌套:
class AA
{
public:
int m_a;
AA(int a):m_a(a)
{cout<<"这里是AA的构造函数"<<endl;}
void fun1()
{cout<<"这里是fun1函数 "<<m_a<<endl;}
};
template<class T>
class BB
{
public:
T m_b;
BB(T b):m_b(b)
{cout<<"这里是BB的构造函数"<<endl;}
void fun2()
{cout<<"这里是fun2函数 "<<m_b<<endl;}
};
template<class U,class T>
class CC:public U
{
public:
CC(T a):U(a)
{cout<<"这里是CC的构造函数"<<endl;}
void fun3()
{cout<<"这里是fun3函数 "<<endl;}
};
int main()
{
CC<AA,int> c1(15);
c1.fun1();c1.fun3();
cout<<endl;
CC<BB<string>,string> c("LiSi");
c1.fun1();c1.fun3();
}
这段代码会有些难度,大家可以自己写写试试。另外提示一点,template里定义的参数只有在当前的模板类起作用,后面的模板里即使有重名也不会影响代码。换句话说假如AA类用T1表示通用类型,CC类用T2,T3表示通用数据类型效果实际和这个例子上是一样的,不要被重名迷惑了。
这节我们具体学习了类模板的具体化和几种常用的继承,语法并不困难,我们也只需要理解原理,记不住的话后面用到的时候查一下就好。