17、类模板
- 类模板
- 类模板的声明
- 类模板的使用
- 类模板的静态成员
- 类模板的递归实例化
- 类模板扩展
- 数值型的模板参数
- 模板型成员变量
- 模板型成员函数
- 模板型成员类型
- 模板型模板参数
- 典型模板错误
- 嵌套依赖
- 依赖模板参数访问成员函数模板
- 子类模板访问基类模板
- 类模板中的成员虚函数
类模板
类模板的声明
形式
template<tyepname 类型形参1,....>
class 类模板名 {...} ;
示例
template<typename A, typename B>class CMath (
public:
A m_a;
B func() {...}
}
在类模板外实现成员函数
语法
template<typename 类型形参1,...>
返回值类型 类模板名<类型形参1,...>::函数名(调用形参1,....){
函数体实现;
}
示例
template< typename A, typename B> B CMath<A,B>::func(){
...
}
// 类模板
#include <iostream>
using namespace std;
template<typename T>
class CMath{
public:
CMath(const T& t1, const T& t2):m_t1(t1),m_t2(t2){}
/* T add(){
return m_t1 + m_t2;
}*/
T add();
private:
T m_t1;
T m_t2;
};
template<typename T>
T CMath<T>::add(){
return m_t1 + m_t2;
}
// class CMath<int>{ ... } // 编译器实例化出的类
// class CMath<double>{ ... }
// class CMath<string>{ ... }
int main( void ) {
int nx = 10, ny = 20;
CMath<int> m1(nx,ny); // 类模板的实例化 并使用类模板的实例定义对象
cout << m1.add() << endl;
double dx = 1.23, dy = 4.56;
CMath<double> m2(dx,dy);
cout << m2.add() << endl;
string sx = "Hello," , sy = "world!";
CMath<string> m3(sx,sy);
cout << m3.add() << endl;
return 0;
}
类模板的使用
使用类模板必须对类模板进行实例化 (产生真正的类)
类模板本身并不代表一个确定的类型( 即不能用于定义对象) ,只有通过类型实参实例化成真正的类后才具备类的语义(即可以定义对象)。
示例
CMath<int,double> math;
- 类模板被实例化时类模板中的成员函数并没有实例化, 成员函数只有在被调用时才会被实例化 (即产生真正成员函数)
- 注意: 成员虚函数除外
- 某些类型虽然并没有提供类模板所需要的全部功能但照样可以实例化类模板,只要不调用那些未提供功能的成员函数即可
类模板的静态成员
- 类模板中的静态成员即不是每个对象拥有一份,也不是类模板拥有一份
- 而应该是由类模板实例化出的每一个真正的类各有一份
- 且为该实例化类定义的所有对象共享
类模板的递归实例化
由类模板实例化产生的类也可以用来实例化类模板自身,这种做法称之为类模板的递归实例化
通过这种方法可以构建空间上具有递归特性的数据结构 例如:多维数组
Array<Array<int> >
// 类模板的递归实例化
#include <iostream>
#include <iomanip>
using namespace std;
template<typename T>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
int main( void ) {
Array< Array<int> > m; // 递归实例化
for(int i=0;i<10;i++)
for(int j=0;j<10;j++)
m[i][j] = 10*i+j;
for(int i=0;i<10;i++){
for(int j=0;j<10;j++)
cout << setfill('0') << setw(2) << m[i][j] << ' ';
cout << endl;
}
/*
Array<int> a;
for(int i=0;i<10;i++)
a[i] = 10+i;
for(int i=0;i<10;i++)
cout << a[i] << ' ';
cout << endl;
*/
return 0;
}
类模板扩展
数值型的模板参数
类模板的模板形参并不限于类型参数,普通数值也可以作为模板的参数。
- 非类型模板参数只能是数值类型,且只能是整数
- 非类型模板参数也可以有缺省值
// 数值型模板参数
#include <iostream>
#include <iomanip>
using namespace std;
template<typename T,size_t S=15>class Array{ // 数值型模板参数:类型只能是整型
public:
T& operator[](size_t i){
return m_arr[i];
}
size_t size(){
return S;
}
private:
T m_arr[S];
};
int main( void ) {
Array<int/*,20*/> a;
for(int i=0;i<a.size();i++)
a[i] = 10+i;
for(int i=0;i<a.size();i++)
cout << a[i] << ' ';
cout << endl;
return 0;
}
模板型成员变量
成员变量,但其类型是由一个类模板实例化的未知类,称之为模板型成员变量。
// 模板型成员变量
#include <iostream>
#include <iomanip>
using namespace std;
template<typename T>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
template<typename D>class Sum{
public:
Sum(Array<D>& a):m_a(a){}
D add(){
D s = 0;
for(int i=0;i<10;i++)
s+=m_a[i];
return s;
}
private:
Array<D> m_a; // 模板型成员变量
};
int main( void ) {
Array<int> a;
for(int i=0;i<10;i++)
a[i] = 10+i;
Sum<int> s(a);
cout << s.add() << endl;
return 0;
}
模板型成员函数
类模板的成员函数模板。
在类外实现
// 模板型成员函数
#include <iostream>
using namespace std;
template<typename T>class CMath{
public:
CMath(const T& t1, const T& t2):m_t1(t1),m_t2(t2){};
/* template<typename D>void func(){ // 模板型成员函数
cout << "CMath<T>::func<D>()" << endl;
}*/
template<typename D> void func(D d); // 声明
T add();
private:
T m_t1;
T m_t2;
};
// 定义
template<typename T>
T CMath<T>::add(){
cout << "add:"<<m_t1 + m_t2 << endl;
}
template<typename T> template<typename D>
void CMath<T>::func(D d){
cout << "CMath<T>::func<D>()"<< d << endl;
}
int main( void ) {
CMath<int> m(1,2);
m.add();
m.func<double>(1.23);
return 0;
}
模板型成员类型
类模板中嵌套的类模板
// 模板型成员类型
#include <iostream>
using namespace std;
template<typename X>class A{
public:
template<typename Y>class B{
public:
template<typename Z>class C; // 声明
};
};
// 定义class C
template<typename X>
template<typename Y>
template<typename Z>class A<X>::B<Y>::C{
public:
template<typename T>void foo(X x,Y y ,Z z,T t){
cout << "A<X>::B<Y>::C<Z>::foo<D>():"<< x<< y<< z<< t << endl;
}
};
int main( void ) {
A<int>::B<double>::C<int> c;
c.foo<short>(1,2,3,4);//A<X>::B<Y>::C<Z>::foo<D>():1234
return 0;
}
模板型模板参数
类模板的模板形参也可以是类模板,可以有缺省值.
template<typename T> class Array{....};
template< template<typename D> typename C=Array >
class Sum{
。。。
}
// 模板型模板参数
#include <iostream>
#include <iomanip>
using namespace std;
template<typename T>class Array{
public:
T& operator[](size_t i){
return m_arr[i];
}
private:
T m_arr[10];
};
template<typename D,template<typename M>typename C=Array>class Sum{
public:
Sum(C<D>& a):m_a(a){}
D add(){
D s = 0;
for(int i=0;i<10;i++)
s+=m_a[i];
return s;
}
private:
C<D> m_a;
};
int main( void ) {
Array<int> a;
for(int i=0;i<10;i++)
a[i] = 10+i;
Sum<int/*,Array*/> s(a);
cout << s.add() << endl;
return 0;
}
典型模板错误
嵌套依赖
由于模板要经过两次编译,在第一次编译模板的代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此编译器看到使用这样的标识符声明变量时会报告错误这就叫嵌套依赖。
解决办法:
- 在类型形参的前面增加一个 typename 标识符,意在告诉编译器其后是一个类模板的嵌套使用。
// 嵌套依赖
#include <iostream>
#include <iomanip>
using namespace std;
class A{
public:
class B{
public:
void foo(){
cout << "A::B::foo()" << endl;
}
};
};
template<typename T>void Func(){
// T::B b; // error:嵌套依赖
/*class|*/typename T::B b;
b.foo();
}
int main( void ) {
Func<A>();
return 0;
}
依赖模板参数访问成员函数模板
利用未知类定义的对象来访问成员函数模板时,编译器在第一次编译时无法解析成员函数模板的类型参数列表的<>而报告编译错误。
解决办法
- 在成员函数模板之前增加template关键字,意在告诉编译器其后是一个函数模板实例,编译器就可以正确理解<>了
// 依赖模板参数访问成员函数模板
#include <iostream>
#include <iomanip>
using namespace std;
class A{
public:
template<typename T>void foo(){
cout << "A::foo<T>()" << endl;
}
};
template<typename T>void Func(){
T t;
// t.foo<int>(); // 依赖模板参数访问成员函数模板
t.template foo<int>();
}
int main( void ) {
return 0;
}
子类模板访问基类模板
在子类模板中访问基类模板的成员,编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索
解决办法
- 在子类模板中可以通过使用作用域限定符或显式使用this指针
// 子类模板访问基类模板
#include <iostream>
#include <iomanip>
using namespace std;
// 基类模板
template<typename T>class Base{
public:
int m_i;
void foo(){
cout << "Base<T>::foo()" << endl;
}
};
// 2. 全局域
//int m_i;
//void foo(){}
// 子类模板
template<typename T,typename D>class Derived:public Base<T>{
public:
void bar(){
// 使用类名限定 --> 未知类型的调用
Base<T>::m_i = 100;
Base<T>::foo();
// 显式使用this调用
this->m_i = 200;
this->foo();
}
// 1. 子类模板内部
// int m_i;
// void foo(){}
};
int main( void ) {
Derived<int,double>d;
d.bar();
return 0;
}
类模板中的成员虚函数
类模板中的普通成员函数可以是虚函数
- 即可以为类定义成员虚函数,和普通类的成员虚函数一样,类模板的成员虚函数也可以表现出多态性
类模板中的成员函数模板不可以是虚函数 - 根据成员虚函数的多态机制,需要一个虚函数表 (表中保存成员虚函数的入口地址),而这个表是编译器在实例化类模板时就产生,类的成员函数模板的实例化(即产生真正的函数实体)需要编译器处理完调用后才会完成,这时才出现成员虚函数的地址。
// 类模板中的成员虚函数
#include <iostream>
#include <iomanip>
using namespace std;
// 基类模板
template<typename T>class Base{
public:
virtual void foo(){ // 普通成员函数可以是虚函数
cout << "Base<T>::foo()" << endl;
}
// virtual template<typename D>void Func(){ } // error:成员函数模板不可以定义为虚函数
};
// 子类模板
template<typename T,typename D>class Derived:public Base<T>{
public:
void foo(){
cout << "Derived<T,D>::foo()" << endl;
}
};
int main( void ) {
Derived<int,double> d;
Base<int>* pBase = &d;
pBase->foo(); // 并且可以表现出多态
return 0;
}
总结:成员函数模板的延迟编译,阻碍了虚函数表的静态构建。