模板(Template)指C++程序设计设计语言中采用类型作为参数的程序设计,支持通用程序设计。C++ 的标准库提供许多有用的函数大多结合了模板的观念,如STL以及IO Stream。使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
一、函数模板
在c++入门中,很多人会接触swap(int&, int&)这样的函数类似代码如下:
1 void swap(int&a , int& b) { 2 int temp = a; 3 a = b; 4 b = temp; 5 }
但是如果是要支持long,string,自定义class的swap函数,代码和上述代码差不多,只是类型不同,这个时候就是我们定义swap的函数模板,就可以复用不同类型的swap函数代码,函数模板的声明形式如下:
template<class 形参名, class 形参名,......> 返回类型 函数名(参数列表) { } template<typename 形参名, typename 形参名,......> 返回类型 函数名(参数列表) { }
swap函数模板的声明和定义代码如下:
1 //method.h 2 #pragma once 3 template <class T> void swap(T& t1, T& t2) 4 { 5 T tmp; 6 tmp = t1; 7 t1 = t2; 8 t2 = tmp; 9 }
上述是模板的声明和定义了,那模板如何实例化呢,模板的实例化是编译器做的事情,与程序员无关,那么上述模板如何使用呢,代码如下:
1 //main.cpp 2 #include <stdio.h> 3 #include "method.h" 4 int main() { 5 //模板方法 6 int num1 = 1, num2 = 2; 7 swap<int>(num1, num2); 8 printf("num1:%d, num2:%d ", num1, num2); 9 return 0; 10 }
可以注意到,我将函数模板的声明和定义都放在了头文件中,一般来说,函数的声明放在头文件,其定义通常是放在源文件中的,这样在包含头文件时,可以有效避免出现重定义error。但模板的使用,需要实例化,而当实例化一个模板时,编译器必须看到模板确切的定义,而不仅仅是它的声明。(这一部分更多的解释可参考http://blog.csdn.net/lichengyu/article/details/6792135)
二、类模板
考虑我们写一个简单的栈的类,这个栈可以支持int类型,long类型,string类型等等,不利用类模板,我们就要写三个以上的stack类,其中代码基本一样,通过类模板,我们可以定义一个简单的栈模板,再根据需要实例化为int栈,long栈,string栈。
1 //stack.h 2 #pragma once 3 template<class T> class Stack { 4 public: 5 Stack(); 6 ~Stack(); 7 void push(T t); 8 T pop(); 9 bool isEmpty(); 10 private: 11 T* m_pT; 12 int m_maxSize; 13 int m_size; 14 }; 15 16 //同样,也把类模板中函数的定义写在.h文件中 17 18 //Define of Construct 19 template<class T> Stack<T>::Stack() 20 { 21 m_maxSize = 100; 22 m_size = 0; 23 m_pT = new T[m_maxSize]; 24 } 25 26 //Define of Destructor 27 template<class T> Stack<T>::~Stack() 28 { 29 delete[] m_pT; 30 } 31 32 //Define of push() 33 template<class T> void Stack<T>::push(T t) 34 { 35 m_size++; 36 m_pT[m_size - 1] = t;//数组下标从0开始 37 } 38 39 //Define of pop() 40 template<class T> T Stack<T>::pop() 41 { 42 T t = m_pT[m_size - 1]; 43 m_size--; 44 return t; 45 } 46 47 //Define of isEmpty(); 48 template<class T> bool Stack<T>::isEmpty() 49 { 50 return m_size == 0; 51 }
上述定义了一个类模板--栈,这个栈很简单,只是为了说明类模板如何使用而已,最多只支持100个元素入栈,使用示例如下:
1 #include <stdio.h> 2 #include "stack.h" 3 4 int main() 5 { 6 Stack<int> intStack; 7 intStack.push(1); 8 intStack.push(2); 9 intStack.push(3); 10 11 while (!intStack.isEmpty()) 12 { 13 printf("num:%d ", intStack.pop()); 14 } 15 return 0; 16 17 } 18 //num:3 19 //num:2 20 //num:1
三、模板参数
模板可以有类型参数,也可以有非类型参数(即内置类型参数),也可以有模板参数。
上述类模板的栈有一个限制,就是最多只能支持100个元素,我们可以使用模板参数配置这个栈的最大元素数,如果不配置,就设置默认最大值为100,代码如下:
1 //stack.h 2 #pragma once 3 //模板参数中使用内置的类型参数int,增加栈的最大容纳元素数 4 template<class T,int maxsize=100> class Stack { 5 public: 6 Stack(); 7 ~Stack(); 8 void push(T t); 9 T pop(); 10 bool isEmpty(); 11 private: 12 T* m_pT; 13 int m_maxSize; 14 int m_size; 15 }; 16 17 //同样,也把类模板中函数的定义写在.h文件中 18 19 //Define of Construct 20 template<class T,int maxsize> Stack<T,maxsize>::Stack() //注意Stack<T,maxsize>中maxsize不要漏了 21 { 22 m_maxSize =maxsize; 23 m_size = 0; 24 m_pT = new T[m_maxSize]; 25 } 26 27 //Define of Destructor 28 template<class T,int maxsize> Stack<T,maxsize>::~Stack() 29 { 30 delete[] m_pT; 31 } 32 33 //Define of push() 34 template<class T,int maxsize> void Stack<T,maxsize>::push(T t) 35 { 36 m_size++;//增加元素的个数 37 m_pT[m_size - 1] = t;//入栈,因为数组下标从0开始,所以-1 38 } 39 40 //Define of pop() 41 template<class T,int maxsize> T Stack<T,maxsize>::pop() 42 { 43 T t = m_pT[m_size - 1]; 44 m_size--; 45 return t; 46 } 47 48 //Define of isEmpty(); 49 template<class T,int maxsize> bool Stack<T,maxsize>::isEmpty() 50 { 51 return m_size == 0; 52 }
使用示例如下:
1 #include <stdio.h> 2 #include "stack.h" 3 4 int main() 5 { 6 const int maxsize = 1024; 7 Stack<int,1024> intStack; 8 for (int i = 0; i < maxsize; i++) 9 intStack.push(i); 10 11 while (!intStack.isEmpty()) 12 { 13 printf("num:%d ", intStack.pop()); 14 } 15 return 0; 16 17 } 18 //num:1023 19 //num:1022 20 //num:1021 21 //... 22 //... 23 //num:0
注:上述代码中template<class T,int maxsize=100> class Stack{};中int maxsize就是非类型的模板参数。有以下几点需要注意的:
1、非类型参数在模板定义的内部是常量值,也就是说非类型参数在模板的内部是常量;
2、非类型模板的参数只能是整型,指针和引用,像double,String,String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的;
3、调用非类型模板形参的实参必须是一个常量表达式,即它必须能在编译时计算出结果;
4、任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
5、全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参;
6、sizeof表达式的结果是一个常量表达死,也能用作非类型模板形参的实参。