在《函数探幽》一文中总结了函数模板的相关的知识。我们大概知道模板提供了泛型编程的便利,也即我们可以用通用的类型来定义表征一系列类型,比如函数的形参。对于类,我们也可以设计一种通用的类型的类来实现代码的重用。本文将总结类模板的相关知识。
1.定义类模板
与模板函数类似,模板类也要以下面的代码开头:
template <class T>
这与函数模板的功能一样,也即声明T为一种通用类型。当然,这里的class并不是说后面的T必须为一个类,class可以换为typename。这里我们设计一个复数类。复数类的实部和虚部可以是整型和浮点型数据,因此模板可以很好的满足应用需求。
可以将类的成员变量和成员函数都声明为模板接口。比如Complex的实部和虚部,以及它重载四则运算的函数接口。但注意,在接口的是西安时(方法定义时)要在每个函数前面都要添加template <class T>,且要在类的作用域标识符前添加<T>.同时,不能将模板成员函数放在独立的实现文件中,所以通常我们把他们和类的声明一起写在头文件中。其中,若函数的实现是以内联函数的形式出现,则不必再函数前重复添加template <class T>。
2.使用模板类
与模板函数一样,再具体使用模板类时需进行实例化。也即使用具体的类型来替换泛型名。如:
Complex<int> iComplex(1,2);
Complex<float> fComplex(3.5,2.2);
其实际过程时用int或float替换类定义中所有的T。就如函数模板,类的模板也可以隐式实例化,显式实例化和显式具体化,相关的内容请参考函数探幽中的总结。
3.模板类与友元
我们想为Complex重载<<运算符以打印复数,鉴于之前的知识,我们知道需要通过友元来重载<<运算符。自然的想法是在类中添加声明:
friend std::ostream & operator<<(std::ostream & os, const Complex & c);//复数打印
具体定义为:
template <class T>
std::ostream & operator<<(std::ostream & os, const Complex<T> & c)
{
os << c.a << "+" << c.b <<"i";
return os;
}
如果你尝试这么做了,但是发现编译报错了。问题在于并没有完成具体化工作。我们需要修改的工作只需要在友元函数的声明处添加template <class T>模板头,也即将友元函数声明为函数模板,这样在具体化过程中即每个函数的具体化都是每个具体化的友元。这被称作模板类的非约束友元函数。
四、源码
#ifndef TEMPLATE_H
#define TEMPLATE_H
#include <iostream>
template <class T>
class Complex
{
private:
T a; //实部
T b; //虚部
public:
//构造函数
Complex(T a = 0, T b = 0);
//接口函数
T Real(){return a;}; //内联函数不必用template <class T>
T Imag(){return b;}; //内联函数不必用template <class T>
Complex operator+(Complex &c); //运算符重载
//友元重载<<
template <class T> friend std::ostream & operator<<(std::ostream & os, const Complex & c);//复数打印
};
//构造函数 需要有模板头
template <class T>
Complex<T>::Complex(T a, T b)
{
this->a = a;
this->b = b;
}
//运算符重载
template <class T>
Complex<T> Complex<T>::operator+(Complex<T> &c)
{
Complex<T> tmp(this->a+c.a, this->b+c.b);
return tmp;
}
//显示 重载<< 注意也需使用模板头
template <class T>
std::ostream & operator<<(std::ostream & os, const Complex<T> & c)
{
os << c.a << "+" << c.b <<"i";
return os;
}
#endif