模板的参数
模板是C++支持参数化多态的工具,模板的形参有三种类型:类型参数、非类型参数、模板形参。接下来就针对这三种模板形参进行详细讲解。
1、类型参数
在前面我们所涉及的模板参数都是由typename或者class标记,以这两个关键字标记的的模板参数就称为类型模板参数(type template parameter),类型模板参数是我们使用模板的主要目的。例如下列模板声明:
template<typename T>
T add(T t1, T t2);
其中,T就是一个类型形参,类型形参的名字由用户自行确定,表示的是一个未知类型,模板类型形参可以作为类型说明符用在模板中的任何地方,与内置类型说明符或类类型说明符的使用方式完全相同。我们可以为模板定义多个类型模板参数,也可以为类型模板参数指定默认值,示例代码如下所示:
template<typename T, typename U = int>
class A
{
public:
void func(T, U);
};
在上述代码中,把U默认设置成为int类型,类模板类型形参和函数的默认参数一样,如果有多个形参,则第一个形参设定了默认值之后的所有模板形参都要设定默认值。
关于类型参数前面我们已经用到很多,在以后程序中也会经常用到,读者在以后编程中可多加练习。
2、非类型参数
模板的非类型参数也就是内置类型形参,例如定义如下模板:
template<typename T, int a>
class A
{
//……
};
其中int a就是非类型的模板形参。非类型模板形参相当于为函数模板或类模板预定义一些常量,在生成模板实例时,也要求必须以常量,即编译期已知的值为非类型模板参数赋值。非类型模板形参只可以是整型、枚举、指针和引用类型,例如double不可以是非类型形参,但double&、double*这样的对象引用或指针是正确的。
相对于常量,非类型模板参数的灵活之处在于:模板中声明的常量,在模板的所有实例中都具有相同的值,而非类型模板参数则对于在不同的模板实例中拥有不同的值来满足不同的需求。例如要定义一个常量数组,如果已知要用到一个大小为10的数组,则可以将数组长度定义为10,代码如下所示:
template<typename T>
class Array
{
static const unsigned size = 10;
T arr[size]; //数组大小为定义好的常量
};
但如果需要多个大小不一的数组时,将数组大小定义为常量便无法满足要求了,此时就可以将数组长度定义为数组类模板的成员变量,在实例化时自动赋值,具体代码如例1所示。
例1
1 #include <iostream>
2 using namespace std;
3 template<typename T, unsigned size> //非类型模板参数unsigned size
4 class Array{
5 T arr[size];
6 public:
7 T& operator[](unsigned i)
8 {
9 if (i >= size)
10 cout << "out of the bound" << endl;
11 else
12 return arr[i];
13 }
14 };
15 int main()
16 {
17 Array<char, 5> arr1; //定义一个长度为5的char类型数组
18 Array<int, 10> arr2; //定义一个长度为10的int数组
19 arr1[0] = 'A';
20 cout << arr1[0] << endl;
21 for (int i = 0; i < 10; i++)//为int类型数组arr2赋值并输出
22 arr2[i] = i + 1;
23 for (int i = 0; i < 10; i++)
24 cout << arr2[i] << " ";
25 cout << endl;
26 system("pause");
27 return 0;
28 }
运行结果如图1所示。
图1 例1运行结果
从例1中看到,实例化类模板Array时,分别定义了长度为5和10的两个数组,解决了常量参数只能固定大小的问题,可见当需要为同一算法或类定义不同常量时,最适合用非类型模板参数实现。
当使用非类型模板参数时,有以下几点需要注意:
(1)调用非类型模板形参的实参必须是一个常量表达式,即必须能在编译时计算出结果。
(2)任何局部对象、局部变量及它们的地址都不是一个常量表达式,不能用作非类型模板形参的实参,全局指针类型、全局变量、全局对象也不是常量表达式,也不能用作非类型模板形参的实参。
(3)sizeof()表达式结果是一个常量表达式,可以用作非类型模板形参的实参。
(4)非类型模板形参一般不用于函数模板。例如函数模板:
template<typename T, int a>
void func(T t);
如果有func(2)调用,则会出现错误,因为编译器无法确定a的值。对于这种模板函数可以用显式模板实参来解决,如func<int,3>(2)调用。
3、模板形参
模板形参,顾名思义就是模板的参数是另一个模板,其声明格式如下所示:
template<typename T, template<typename U,typename Z> class A>
class Class
{
A<T,T> a;
};
上述代码中声明的第二个模板参数就是一个类模板,注意只有类模板可以作为模板参数,参数声明中的关键字class是必需的。
模板参数使用的时候与一般参数没什么区别,不要拘泥于它的语法实现,只要记住可以使用模板作为模板的一个参数即可。