C++ 模板

模板是为了编写与类型无关的代码。比如编写了一个交换两个整型 int 类型的 swap 函数,如果针对 double 数据,则需要再实现一遍。而使用模板,则可以只编写一次。C++ 中的模板主要有函数模板类模板

1. 函数模板

1
2
3
4
5
template <typename 形参名,typename 形参名,......> 
返回类型 函数名(参数列表)
{
...
}

其中,typename 关键字可以用 class 代替,以下我们全部使用typename。<> 中的模板形参不能为空。

1
2
3
4
5
6
7
template <typename T> 
void swap(T& a, T& b){
T tmpT;
tmpT = t1;
t1 = t2;
t2 = tmpT;
}

当调用这样的模板函数时类型T就会被被调用时的类型所代替,比如 swap(a,b) 其中 a 和 b 是 int 型,这时模板函数 swap 中的形参 T 就会被int 所代替,模板函数就变为swap(int &a, int &b)。而当 swap(c,d) 其中 c 和 d 是 double 类型时,模板函数会被替换为 swap(double &a, double &b),这样就实现了函数的实现与类型无关的代码。在调用时,可以显式代入模板类型。

swap<int>(a, b);

不能为同一个模板类型形参指定两种不同的类型,比如

1
2
template<class T>
void h(T a, T b){},

语句调用 h(2, 3.2) 将出错,因为该语句给同一模板形参T指定了两种类型,第一个实参 2 把模板形参 T 指定为 int,而第二个实参 3.2 把模板形参指定为 double,两种类型的形参不一致,会出错。

2. 类模板

2.1 格式

1
2
3
4
template<class 形参名, class 形参名 …> 
class 类名 {
...
};

类模板和函数模板都是以 template 开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明。比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<class T> 
class A{
public:
T a; T b;
T fun(T c, T &d);
void h();
T fun2()
};

template<class T1, class T2>
class B{
public:
T1 fun();
};

2.2 类模板对象的创建

比如一个模板类 A,则使用类模板创建对象的方法为 A m;在类 A 后面跟上一个 <> 尖括号并在里面填上相应的类型,这样的话类 A 中凡是用到模板形参的地方都会被 int 所代替。当类模板有两个模板形参时创建对象的方法为 A<int, double> m;类型之间用逗号隔开。

对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如 A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: ‘a’ uses undefined class ‘A’),类模板形参不存在实参推演的问题。也就是说不能把整型值 2 推演为 int 型传递给模板形参。要把类模板形参调置为 int 型必须这样指定A m。

2.3 在类模板外部定义成员函数的方法为:

1
2
3
4
template<模板形参列表> 
函数返回类型 类名<模板形参名>::函数名(参数列表){
函数体
}
1
2
3
4
5
6
7
8
9
template<class T> 
void A<T>::h(){
...
}

template<class T1, class T2>
T1 B<T1,T2>::fun(){
...
}

!!! 模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。


1
2
3
4
5

template<class T>
class A {
T g(T a, T b){}
};

当我们声明类对象为:A <int> a,比如语句调用 a.g(2, 3.2) 在编译时不会出错,但会有警告,因为在声明类对象的时候已经将 T 转换为int类型,而第二个实参 3.2 把模板形参指定为 double,在运行时,会对 3.2 进行强制类型转换为 3。当我们声明类的对象为:A<double> a,此时就不会有上述的警告,因为从 int 到double是自动类型转换。

3. 非类型形参

非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中 int a 就是非类型的模板形参。

  • 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
  • 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
  • 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
  • 注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
  • 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
  • sizeof 表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
  • [重要] 当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如 template <class T, int a> class A{};如果有 int b,这时A<int, b> m;将出错,因为 b 不是常量,如果 const int b,这时 A<int, b> m;就是正确的,因为这时 b 是常量。
  • [重要] 非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){},若使用 h(2) 调用会出现无法为非类型形参 a 推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用 h<int, 3>(2) 这样就把非类型形参 a 设置为整数 3。

4. stack 例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// stack.h
#ifndef STACK_HXX
#define STACK_HXX

template <class T, int MAXSIZE>
class Stack {
private:
T elems[MAXSIZE]; // store all elements
int numElems; // number of elements

public:
Stack();
void push(T const &);
void pop();
T top() const;
bool empty() const{
return numElems == 0;
}
bool full() const{
return numElems == MAXSIZE;
}
};

template <class T, int MAXSIZE>
Stack<T, MAXSIZE> :: Stack() : numElems(0){
}

template <class T, int MAXSIZE>
void Stack<T,MAXSIZE> :: push (T const & elem){
if (Stack<T, MAXSIZE> ::full())
throw std::out_of_range("out of range");
elems[numElems] = elem;
++ numElems;
}

template <class T, int MAXSIZE>
void Stack<T,MAXSIZE> :: pop (){
if (numElems <= 0)
throw std::out_of_range("Pop: no element");
--numElems;
}

template <class T, int MAXSIZE>
T Stack<T,MAXSIZE> :: top() const{
if (numElems <= 0)
throw std::out_of_range("Top: no element");
else return elems[numElems - 1];
}

#endif
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// stack.cpp

#include <iostream>
#include <string>
#include <cstdlib>
#include "stack.h"

using namespace std;

int main(int argc, char const *argv[]){
/* code */
try {
Stack<int, 20> int20Stack; // create a stack whose size is 20
Stack<string, 40> stringStack; // create a stack whose size is 20
int20Stack.push(7);
cout << int20Stack.top() << endl;

stringStack.push("hello");
cout << stringStack.top() << endl; //hello
stringStack.pop();
stringStack.pop(); //Exception: Stack<>::pop<>: empty stack

return 0;
}

catch (exception const& ex) {
cerr << "Exception: " << ex.what() << endl;
return EXIT_FAILURE; // 退出程序且有ERROR标记
}
}