持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第25天,点击查看活动详情
类模板
- 编译器可以根据仅一次声明来产生许多逻辑基本相同、且有细微不同的类的声明
- 类模板用于
类模板和函数模板一样都是产生对象类型的配方,编译器类模板定义来生成逻辑相似的一系列类的定义。这里所谓相似就是可以将类作为接口来聚为一类,或者具有相同行为。我们同一个有理数类来举例说明一下,那么什么是有理数呢,有理数就是可以表示为两个整数比的数 可以表示为 或者 0.75
class Rational{
public:
Rational();
Rational(long n);
Rational(long n,long d);
Rational &operator+=(Rational const&ro);
Rational &operator-=(Rational const&ro);
private:
void reduce();
long num, den;
};
复制代码
可能根据不同精度,我们可需要定义多个有理数类,可以通过名字加以区分,然后通过 copy 来实现多个不同精度的 Rational 类
- Rational(int 类型)
- Short_Rational
- Long_Long_Rational
template<typename T>
class Rational{
public:
Rational();
Rational(T n);
Rational(T n,T d);
Rational &operator+=(Rational const&ro);
Rational &operator-=(Rational const&ro);
private:
void reduce();
long num, den;
};
复制代码
我们找到需要类之间不同之处为精度,所以就可以将需要这一些列类之间细微差异用泛型来解决,也就是用 T
来解决。
编译器会在用户只用模板,也就是调用Rational<T>
时给出 T
的指定类型时来实例化出一个根据指定类型参数来实例化一个类的声明,听起来有些要绕嘴。
从类模板实例化出类定义可以看做实例化的类,例如 Rational<long>
就是一个实例化的类的类型名称。
Rational<int> r1{2}//2/1;
复制代码
也可以 typedef
或者 using
为 Rational<int>
定义别名,这样可以避免写很长类型名称
typedef Rational<int> irat;
using irat = Rational<int>
复制代码
typedef Rational<int> irat;
irat r1{2};
复制代码
template<typename T>
class Rational{
public:
Rational():num(0),den(0){};
Rational(T n):num(n),den(1){};
Rational(T n,T d):num(n),den(d){};
Rational &operator+=(Rational const&ro);
Rational &operator-=(Rational const&ro);
private:
void reduce();
long num, den;
};
复制代码
容器类模板
类模板一个主要应用场景,就是容器,其实容器是一个持有其他类型,也就是特定类型对象集合的对象。例如
list<T>
是一个有类型为T
元素组成的链表vector<T>
长度可变的类型为T
元素组成集合set<T>
类型为T
的元素组成有序集合
这些都是容器,不同类型容器提供访问其元素的不同规则,那么容器通常并不会对其持有元素类型做限定,那么显而易见容器就是很好的类模板应用的场景。
list<Rational<int> > ratios;
复制代码
在早期版本我们需要 list<Rational<int> > ratios;
需要在右侧 2 个 >
大于号之间留有空格避免与 >>
操作符产生歧义,不过现在的 c++ 版本已经支持了 >>
这样写,也是没有问题的。
成员函数
对于类模板,可以在类模板中声明成员函数,关于成员函数定义可以类模板声明中定义,也可以在类模板外进行声明 如果要在类外来定义类成员函数时候,需要定义完整,也就是需要template<typename T>
这样形式,如果在类内就不要这样来定义,关于在类内方式参见之间的代码
template<typename T>
Rational<T>::Rational():num(0),den(0){};
template<typename T>
Rational<T>::Rational(T n):num(n),den(1){};
template<typename T>
Rational<T>::Rational(T n,T d):num(n),den(d){};
复制代码
template<typename T>//T 作用域开始
class Rational{
public:
Rational();
...
};//T 作用域结束
复制代码
所以在类外定义成员函数时,因为超出了 T
的作用域所以编译器并不知道 T
代表什么,所以还需要在成员函数前定义 template<typename T>
不知道你有没有注意到Rational<T>::Rational
在两个冒号前的 Rational<T>
是带有 <T>
而后面 Rational
是没有带有 <T>
的。
这是因为如果在类模板 Rational<T>
的作用域中,可以只使用 Rational
也是可以的
Rational<T>
Rational
以上 2 种方式都是 OK 的
template<typename T>
class Rational{
public:
Rational<T>();
Rational<T>(T n);
Rational<T>(T n,T d);
Rational &operator+=(Rational<T> const&ro);
Rational &operator-=(Rational<T> const&ro);
private:
void reduce();
long num, den;
};
复制代码
那么在类外生命成员函数时,C 作用域起始于 C::
而解释成员函数体结束。
template<typename T>
Rational<T> &
Rational<T>::operator+=(Rational const&ro){
}
复制代码
其中::
表示又重新进入类的作用域,所以随后的Rational
不用带有<T>
也是没有问题的。
接下来我们再来看例子
template<typename T>
Rational<T>::Rational(T n):num(n),den(1){};
复制代码
在上面代码中,对于带有一个参数的构造函数Rational
是不允许带有类型参数变量T
的。还有对于自定义析构函数(non-travial)析构函数
template<typename T>
Rational<T>::~Rational(){};
复制代码
也是不允许写成 ~Rational<T>(){}
,对于这些特殊例子,也不用话费精力去记住那些情况可以写成 C<T>
或者 C
,哪些情况只能写成 C
。我们只要在类模板C<T>
的作用域范围内,将C<T>
都统一写成C
即可。