首页 > 编程笔记

C++模板类(类模板)的定义和使用

类也可以像函数一样被不同的类型参数化,如 STL 中的 vector 容器就是典型的例子,使用 vector 不需要关心容器中的数据类型,就可以对数据进行操作。

类模板定义与实例化

函数可以定义函数模板,同样地,对于类来说,也可以定义一个类模板。

类模板是针对成员数据类型不同的类的抽象,它不是一个具体实际的类,而是一个类型的类,一个类模板可以生成多种具体的类。

类模板的定义格式如下所示:

template<typename 类型占位符>
class 类名
{
}

类模板中的关键字含义与函数模板相同。需要注意的是,类模板的模板参数不能为空。一旦声明类模板,就可以用类模板的参数名声明类中的成员变量和成员函数,即在类中使用数据类型的地方都可以使用模板参数名来声明。

定义类模板示例代码如下所示:
template<typename T>
class A
{
public:
    T a;
    T b;
    T func(T a, T b);
};
上述代码中,在类 A 中声明了两个T类型的成员变量 a 和 b,还声明了一个返回值类型为 T 并带两个 T 类型参数的成员函数 func()。

定义了类模板就要使用类模板创建对象以及实现类中的成员函数,这个过程其实也是类模板实例化的过程,实例化出的具体类称为模板类。

如果用类模板创建类的对象,例如,用上述定义的类模板 A 创建对象,则在类模板 A 后面加上一个 <>,并在里面表明相应的类型,示例代码如下所示:
A<int> a;
这样类 A 中凡是用到模板参数的地方都会被int类型替换。如果类模板有多个模板参数,创建对象时,多个类型之间要用逗号分隔开。

例如,定义一个有两个模板参数的类模板 B,然后用 B 创建类对象,示例代码如下所示:
template<typename T1, typename T2>
class B
{
public:
    T1 a;
    T2 b;
    T1 func(T1 a, T2& b);
};
B<int,string> b;   //创建模板类B<int,string>的对象b
使用类模板时,必须要为模板参数显式指定实参,不存在实参推演过程,也就是说不存在将整型值 10 推演为 int 类型再传递给模板参数的过程,必须要在 <>中指定 int 类型,这一点与函数模板不同。

【示例1】下面通过案例演示类模板的实例化,C++ 代码如下:
#include<iostream>
using namespace std;
template< typename T>   //类模板的定义
class Array
{
private:
    int _size;
    T* _ptr;
public:
    Array(T arr[], int s);
    void show();
};
template<typename T>   //类模板外定义其成员函数
Array<T>::Array(T arr[], int s)
{
    _ptr = new T[s];
    _size = s;
    for (int i=0;i<_size; i++)
    {
        _ptr[i]=arr[i];
    }
}
template<typename T>   //类模板外定义其成员函数
void Array<T>::show()
{
    for(int i=0;i<_size;i++)
        cout<<*(_ptr + i)<<" ";
    cout<<endl;
}
int main()
{
    char cArr[] = { 'a', 'b', 'c', 'd', 'e' };
    Array<char> a1(cArr, 5);   //创建类模板的对象
    a1.show();
    int iArr[10] = { 1, 2, 3, 4, 5, 6 };
    Array<int> a2(iArr, 10);
    a2.show();
    return 0;
}
运行结果:

a b c d e
1 2 3 4 5 6 0 0 0 0

示例分析:
需要注意的是,类模板在实例化时,带有模板参数的成员函数并不会跟着实例化,这些成员函数只有在被调用时才会被实例化。

类模板的派生

类模板和普通类一样也可以继承和派生,以实现代码复用。

类模板的派生一般有三种情况:类模板派生普通类、类模板派生类模板、普通类派生类模板。这三种派生关系可以解决很多实际问题。

下面针对这三种派生关系进行讲解。

1) 类模板派生普通类

在 C++ 中,可以从任意一个类模板派生一个普通类。在派生过程中,类模板先实例化出一个模板类,这个模板类作为基类派生出普通类。

类模板派生普通类的示例代码如下所示:
template<typename T>
class Base   //类模板Base
{
private:
    T x;
    T y;
public:
    Base();
    Base(T x, T y);
    Base getx();
    Base gety();
    ~ Base();
};
class Derive:public Base<double>   //普通类Derive公有继承类模板Base
{
private:
    double num;
public:
    Derive(double a, double b, double c):num(c), Base<double>(a, b){}
};
在上述代码中,类模板 Base 派生出了普通类 Derive,其实在这个派生过程中类模板 Base 先实例化出了一个 double 类型的模板类,然后由这个模板类派生出普通类Derive,因此在派生过程中需要指定模板参数类型。

2) 类模板派生类模板

类模板也可以派生出一个新的类模板,它和普通类之间的派生几乎完全相同。但是,派生类模板的模板参数受基类模板的模板参数影响。

例如,由类模板 Base 派生出一个类模板 Derive,示例代码如下:
template<typename T>
class Base
{
public:
    T _a;
public:
    Base(T n):_a(n) {}
    T get() const { return _a; }
};
template<typename T, typename U>
class Derive:public Base<U>
{
public:
    U _b;
public:
    Derive(T t, U u):Base<T>(t), _b(u) {}
    U sum() const { return _b + U(Base::get()); }
};
上述代码中,类模板 Derive 由类模板 Base 派生,Derive 的部分成员变量和成员函数类型由类模板 Base 的参数 U 确定,因此 Derive 仍然是一个模板。

类模板派生类模板技术可以用来构建类模板的层次结构。

3) 普通类派生类模板

普通类也可以派生类模板,普通类派生类模板可以把现存类库中的类转换为通用的类模板,但在实际编程中,这种派生方式并不常用,本文只对它作一个简单示例,读者只需要了解即可。

普通类派生类模板示例代码如下所示:
class Base
{
    int _a;
public:
    Base(int n):_a(n){}
    int get() const {return _a;}
};
template<typename T>
class Derive: public Base
{
    T _b;
public:
    Derive(int n, T t):Base(n), _b(t){}
    T sum() const {return _b + (T)get();}
};
在上述代码中,类 Base 是普通类,类模板 Derive 继承了普通类 Base。利用这种技术,程序设计者能够从现存类中创建类模板,由此可以创建基于非类模板库的类模板。

类模板与友元函数

在类模板中声明友元函数有三种情况:非模板友元函数、约束模板友元函数和非约束模板友元函数。

接下来,将针对这三种友元函数进行详细讲解。

1) 非模板友元函数

非模板友元函数就是将一个普通函数声明为友元函数。例如,在一个类模板中声明一个友元函数,示例代码如下:
template<typename T>
class A
{
    T _t;
public:
    friend void func();
};
在类模板 A 中,将普通函数 func() 声明为友元函数,则 func() 函数是类模板 A 所有实例的友元函数。上述代码中,func() 函数为无参函数。

除此之外,还可以将带有模板类参数的函数声明为友元函数,示例代码如下:
template<typename T>
class A
{
    T _t;
public:
    friend void show(const A<T>& a);
};
在上述代码中,show() 函数并不是函数模板,只是有一个模板类参数。

调用带有模板类参数的友元函数时,友元函数必须显式具体化,指明友元函数要引用的参数的类型,例如:
void show(const A<int>& a);
void show(const A<double>& a);
上述代码中,模板参数为 int 类型的 show() 函数是 A<int> 类的友元函数,模板参数为 double 类型的 show() 函数是 A<double> 类的友元函数。

【示例2】下面通过案例演示非模板友元函数的用法,C++ 代码如下:
#include<iostream>
using namespace std;
template<typename T>
class A
{
    T _item;
    static int _count;   //静态变量
public:
    A(const T& t) :_item(t){ _count++; }
    ~A(){ _count--; }
    friend void func();   //无参友元函数func()
    friend void show(const A<T>& a);   //有参友元函数show()
};
template<typename T>
int A<T>::_count = 0;   //初始化静态变量
void func()   //func()函数实现
{
    cout<<"int count:"<<A<int>::_count<<";";
    cout<<"double count:"<<A<double>::_count<<";"<<endl;
}  
//模板参数为int类型
void show(const A<int>& a){cout<<"int:"<<a._item<<endl;}
void show(const A<double>& a){cout<<"double:"<<a._item<<endl;}
int main()
{
    func();   //调用无参友元函数
    A<int> a(10);   //创建int类型对象
    func();
    A<double> b(1.2);
    show(a);   //调用有参友元函数
    show(b);
    return 0;
}
运行结果:

int count:0;double count:0;
int count:1;double count:0;
int:10
double:1.2

示例分析:

2) 约束模板友元函数

约束模板友元函数是将一个函数模板声明为类的友元函数。

函数模板的实例化类型取决于类模板被实例化时的类型,类模板实例化时会产生与之匹配的具体化友元函数。

在使用约束模板友元函数时,首先需要在类模板定义的前面声明函数模板。例如,有两个函数模板声明,示例代码如下:
template<typename T>
void func();
template<typename T>
void show(T& t);
声明函数模板之后,在类模板中将函数模板声明为友元函数。

在声明友元函数时,函数模板要实现具体化,即函数模板的模板参数要与类模板的模板参数保持一致,以便类模板实例化时产生与之匹配的具体化友元函数。示例代码如下所示:
template<typename U>   //类模板的定义
class A
{
…   //其他成员
    friend void func<U>();   //声明无参友元函数func()
    friend void show<>(A<U>& a);   //声明有参友元函数show()
…   //其他成员
};
在上述代码中,将函数模板 func() 与 show() 声明为类的友元函数,在声明时,func() 与 show() 的模板参数受类模板 A 的模板参数约束,与类模板的模板参数相同。

当生成 A<int> 模板类时,会生成与之匹配的 func<int>() 函数和 show<int>() 函数作为友元函数。

需要注意的是,在上述代码中,func() 函数模板没有参数,必须使用 <> 指定具体化的参数类型。show() 函数模板有一个模板类参数,编译器可以根据函数参数推导出模板参数,因此 show() 函数模板具体化中<>可以为空。

【示例3】下面通过案例演示约束模板友元函数的用法,C++ 代码如下:
#include<iostream>
using namespace std;
template<typename T>   //声明函数模板func()
void func();
template<typename T>   //声明函数模板show()
void show(T& t);
template<typename U>   //类模板的定义
class A
{
private:
    U _item;
    static int _count;
public:
    A(const U& u):_item(u){_count++;}
    ~A(){_count--;}
    friend void func<U>();   //声明友元函数func()
    friend void show<>(A<U>& a);   //声明友元函数show()
};
template<typename T>
int A<T>::_count = 0;
template<typename T>   //函数模板func()的定义
void func()
{
    cout<<"template size:"<<sizeof(A<T>)<<";";
    cout<<"template func():"<<A<T>::_count<<endl;
}
template<typename T>   //函数模板show()的定义
void show(T& t){cout<< t._item<<endl;}
int main()
{
    func<int>();   //调用int类型的函数模板实例,int类型,其大小为4字节
    A<int> a(10);   //定义A<int>类对象a
    A<int> b(20);   //定义A<int>类对象b
    A<double> c(1.2);   //定义A<double>类对象c
    show(a);   //调用show()函数,输出类对象a的值
    show(b);   //调用show()函数,输出类对象b的值
    show(c);   //调用show()函数,输出类对象c的值
    cout<<"func<int>output:\n";
    func<int>();   //运行到此,已经创建了两个int类型对象
    cout<<"func<double>()output:\n";
    func<double>();
    return 0;
}
运行结果:

template size:4;template func():0
10
20
1.2
func<int>output:
template size:4;template func():2
func<double>()output:
template size:8;template func():1

示例分析:

3) 非约束模板友元函数

非约束模板友元函数是将函数模板声明为类模板的友元函数,但函数模板的模板参数不受类模板影响,即友元函数模板的模板参数与类模板的模板参数是不同的。

声明非约束模板友元函数示例代码如下所示:
template<typename T>
class A
{
    template<typename U, typename V>
    friend void show(U& u, V& v);
};
在上述代码中,类模板 A 将函数模板 show() 声明为友元函数,但 show() 的模板参数 U、V 不受类模板 A 的模板参数 T 影响,则函数模板 show() 就是类模板 A 的非约束友元函数。

函数模板 show() 的每个模板函数都是类模板 A 每个模板类的友元函数。

【示例4】下面通过案例演示非约束模板友元函数的用法,C++ 代码如下:
#include<iostream>
using namespace std;
template<typename T>   //定义类模板A
class A
{
private:
    T _item;
public:
    A(const T& t) :_item(t){}
    template<class U, class V>   //声明非约束模板友元函数
    friend void show(U& u, V& v);
};
template<typename U, typename V>   //函数模板show()的定义
void show(U& u, V& v){cout<<u._item<<","<<v._item<<endl;}
int main()
{
    A<int> a(10);   //定义A<int>类对象a
    A<int> b(20);   //定义A<int>类对象b
    A<double> c(1.2);   //定义A<int>类对象c
    cout<<"a,b: ";  
    show(a, b);   //调用show()函数,传入对象a、b作为实参
    cout<<"a,c:";
    show(a, c);   //调用show()函数,传入对象a、c作为实参
    return 0;
}
运行结果:

a,b: 10,20
a,c:10,1.2

示例分析:
由此可知,非约束模板友元函数的模板参数与类模板的模板参数不相关,它可以接受任何类型的参数。

优秀文章