C++的类模板为生成通用的类声明提供了一种更好的方法。模板提供参数化类型,即能够将类型名作为参数传递给接收方来建立类或者函数。
一、定义类模板
#include <iostream>
#include <string>
using namespace std;
template <class T1,class T2>
class Pair
{
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k,T2 v):key(k),value(v) { };
bool operator < (const Pair<T1,T2> & p) const;
};
template<class T1,class T2>
bool Pair<T1,T2>::operator < (const Pair<T1,T2> & p) const
//Pair的成员函数 operator <
{ //"小"的意思就是关键字小
return key < p.key;
}
int main()
{
Pair<string,int> student("Tom",19); //实例化出一个类 Pair<string,int>
cout << student.key << " " << student.value;
return 0;
}
不能将模板成员函数放在独立的实现文件中(以前,C++提供了关键字export,让您能够将模板成员函数放在独立的实现文件中,但是支持的编译器不多,C++11不再使用这样的关键字),由于模板不是函数,不能单独编译,模板必须与特定的模板实例化请求一起使用,为此,最简单的方法是将所有模板信息放在一个头文件中,并在要使用这些模板的文件中包含该头文件。
仅在程序包含模板并不能生成模板类,而必须请求实例化,为此,需要声明一个类型为模板类的对象,方法是使用所需的具体类型替换泛型名。
二、深入探讨模板类
指针栈
可以将内置类型或类对象用作类模板的类型,指针可以嘛?答案是可以,可以创建指针栈,但是如果不对程序做重大修改,将无法很好的工作,编译器可以创建类,但是使用效果就因人而异了。
数组模板示例和非类型参数
模板常用作容器类,这是因为类型参数的概念非常适合于将相同的存储方案用于不同的类型。确实,为容器类提供可重用代码是引入模板的主要动机,所以我们来看看另一个例子,深入探讨模板设计和使用的其他几个方面。具体地说,将探讨一些非类型(或表达式)参数以及如何使用数组来处理继承族。
首先介绍一个允许指定数组大小的简单数组模板。一种方法是在类中使用动态数组和构造函数参数来提供元素数目,最后一个版本的Stack模板采用的就是这种方法。另一种方法是使用模板参数来提供常规数组的大小,C++11新增的模板array就是这样做的。
#ifndef ARRAYTP_H_
#define ARRAYTP_H_
#include <iostream>
#include <cstdlib>
template<class T, int n>
class ArrayTP {
private:
T at[n];
public:
ArrayTP() {};
explicit ArrayTP(const T &v);
virtual T &operator[](int i);
virtual T operator[](int i) const;
};
template<class T, int n>
ArrayTP<T, n>::ArrayTP(const T &v) {
for (int i = 0; i < n; i++) {
at[i] = v;
}
}
template<class T, int n>
T &ArrayTP<T, n>::operator[](int i) {
if (i < 0 || i >= n) {
std::cerr << i << "is out of range\n" << std::endl;
std::exit(EXIT_FAILURE);
}
return at[i];
}
template<class T, int n>
T ArrayTP<T, n>::operator[](int i) const {
if (i < 0 || i >= n) {
std::cerr << i << "is out of range\n" << std::endl;
std::exit(EXIT_FAILURE);
}
return at[i];
}
#endif
关键字class(或在这种上下文中等价的关键字typename)指出T为类型参数,int 指出n的类型为int。这种参数(指定特殊的类型而不是用作泛型名)称为非类型(non-type)或表达式(expression)参数。假设有下面的声明:
ArrayTP<double, 10> arrayTp(3.14);
这将创建一个存储double类型对象,编译器使用double替换了T,使用10替换了n。
表达式参数有一些显示,表达式参数可以使整型,枚举,引用或者指针。因此double n是不合法的,但是double *pm是合法的,另外,模板代码不能修改参数的值,也不能使用参数的地址,所以在这个模板中不能使用n++或者&n这类的表达式,另外,实例化模板时,用作表达式参数的值必须是常量表达式。
表达式参数的柱要求但是每种数组大小都将生成自己的模板,也就是说,下面的声明将生成两个独立的类声明:
ArrayTP<double, 10> arrayTp1(3.14);
ArrayTP<double, 12> arrayTp2(3.14);
模板多功能性
可以将用于常规类的技术用于模板类。模板类可用作基类,也可用作组件类,还可用作其他模板的类型参数。例如,可以使用数组模板实现栈模板,也可以使用数组模板来构造数组——数组元素是基于栈模板的栈。
也可以递归的使用模板
ArrayTP<Array<int,5>,10> twodee;
与之等价的常规数组声明如下
int twodee[10][5];
默认参数
木板的另一项新特性是,可以为类型参数提供默认值。
template <class T1, class T2 = int>
class Topo {
...
}
三、模板的具体化
类模板与函数模板很相似,因为可以有隐式实例化、显式实例化和显式具体化,它们统称为具体化(specialization)。模板以泛型的方式描述类,而具体化是使用具体的类型生成类声明。
3.1、隐式实例化
到目前为止,本章所有的模板示例使用的都是隐式实例化(implicit instantiation),即它们声明一个或多个对象,指出所需的类型,而编译器使用通用模板提供的处方生成具体的类定义:
ArrayTP <int,100> stuff;
编译器在需要对象之前,不会生成类的隐式实例化:
ArrayTP <double,100> *tp;
tp = new ArrayTP <double,100>;
第二条语句导致编译器生成类定义,并根据该定义创建一个对象。
3.2、显式实例化
当使用关键字template 并指出所需类型来声明类时,编译器将生成类声明的显式实例化( explicit instantiation)。声明必须位于模板定义所在的名称空间中。例如,下面的声明将ArrayTP<string,100>声明为一个类;
template class ArrayTP <int,100>;
在这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明(包括方法定义)。和隐式实例化一样,也将根据通用模板来生成具体化。
3.3、显式具体化
显式具体化(explicit specialization)是特定类型(用于替换模板中的泛型)的定义。有时候,可能需要在为特殊类型实例化时,对模板进行修改,使其行为不同。在这种情况下,可以创建显式具体化。
假设模板使用>运算符来对值进行比较,对于数字这管用,如果T表示一种类,则只要定义了T::operator>()方法,这也管用;但是T如果是由const char *表示的字符串,这将不管用,此时可以采用具体化方案,提供一个显示模板具体化,这将采用为具体类型定义的模板而不是为泛型定义的模板,当具体化模板和通用模板都与实例化请求匹配时,编译器将使用具体化版本。
具体化类模板定义格式
template <> class Classname<specialized-type-name> {...};
早期的编译器可能只能识别早期的格式,这种格式不支持前缀
class Classname<specialized-type-name> {...};
使用新的表示法提供一个专供const char*使用的模板,可以使用类似于下面的代码
template <> class ArrayTp<chonst char*> {
...
};
3.4、部分具体化
C++还允许部分具体化(partial specialization),即部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型
// 通用模板
template <class T1,class T2> class Pair {...}
// 部分具体化
template <class T1> class Pair<T1,int> {...}
关键字template后面的<>声明是没有被具体化的类型参数。因此,上述第二个声明将T2具体化为int,但T1保持不变,注意,如果指定所有的类型,则<>内将为空,这将导致显式具体化。
tempplate <> class Pair<int,int> {...}
如果有多个模板可以选择,编译器将使用具体化程度最高的模板。
3.5、成员模板
模板可用作结构、类、或模板类的成员,要实现完全STL的设计,必须使用这项特性。
#ifndef ARRAYTP_H_
#define ARRAYTP_H_
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
class beta {
private:
template<typename V>
class hold {
private:
V val;
public:
hold(V v = 0) : val(v) {}
void show() const { cout << val << endl; }
V value() const { return val; }
};
hold<T> q;
hold<int> n;
public:
beta(T t, int i) : q(t), n(i) {}
template<typename U>
U blab(U u, T t) { return (n.value() + q.value()) * u / t; }
void show() const {
q.show();
n.show();
}
};
#endif
int main() {
beta<double> guy(3.5,3);
guy.show();
cout << "U was set to int" << endl;
cout << guy.blab(10,2.3) << endl;
cout << "U was set to double" << endl;
cout << guy.blab(10.0,2.3) << endl;
// 我们也可以显式的转化
cout << guy.blab<int>(10.0,2.3) << endl;
}
四、将模板用作参数
您知道,模板可以包含类型参数如typename T和非类型参数 int n,模板还可以本身就是模板的参数,这种参数是模板新增的特性,用于实现STL。
template <template <typename T> class Thing>
模板参数是template < typename T > class Thing,其中template < typename T > class是类型 Thing是参数,这意味着什么呢,假设有下面的声明
Crab<King> legs;
为了使上面的声明被接受,模板参数King必须是一个模板类,其声明与模板参数Thing的声明匹配
template <typename T>
class King {...}
五、模板别名
如果能为类型指定别名,将很方便,在模板设计中尤其如此。可使用typedef 为模板具体化指定别名:
typedef std::array<double,12> arrd;
typedef std::array<int,12> arri;
typedef std::array<std::string,12> arrs;
arrd gallons;
arri days;
arrs months;
C++11新增了意向功能,使用模板提供一系列别名。
template <typename T>
using arrtype = std::array<T,12>;
这将arrtype定义为一个模板别名,可使用它来指定类型,如下所示:文章来源:https://www.toymoban.com/news/detail-623505.html
arrtype<double> gallons;
arrtype<int> days;
arrtype<std::string> months;
C++11允许将语法using=用于非模板,用于非模板时这种语法与常规typedef等价文章来源地址https://www.toymoban.com/news/detail-623505.html
到了这里,关于c++中的类模板的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!