C++模板
模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。
模板是一种对类型进行参数化的工具;
通常有两种形式:函数模板和类模板;
函数模板针对仅参数类型不同的函数;
类模板针对仅数据成员和成员函数类型不同的类。
使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。
注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
函数模板
1.注意:对于函数模板而言不存在 h(int,int) 这样的调用,不能在函数调用的参数中指定模板形参的类型,对函数模板的调用应使用实参推演来进行,即只能进行 h(2,3) 这样的调用,或者int a, b; h(a,b)。
函数模板的示例演示将在下文中涉及!
类模板
2、类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A<int> m;在类A后面跟上一个<>尖括号并在里面填上相应的类型,这样的话类A中凡是用到模板形参的地方都会被int 所代替。当类模板有两个模板形参时创建对象的方法为A<int, double> m;类型之间用逗号隔开。
3、对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的(编译错误:error C2079: ‘a‘ uses undefined class ‘A<int>‘),类模板形参不存在实参推演的问题。也就是说不能把整型值2推演为int 型传递给模板形参。要把类模板形参调置为int 型必须这样指定A<int> m。
4、在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
template<class T1,class T2> void A<T1,T2>::h(){}。
注意:当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致。
5、再次提醒注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。
模板形参
1.类型形参
-
- 类型模板形参:类型形参由关见字class或typename后接说明符构成,如template<class T> void h(T a){};其中T就是一个类型形参,类型形参的名字由用户自已确定。
- (针对函数模板)不能为同一个类型模板形参指定不同的类型:template<class T>void h(T a, T b){},语句调用h(2, 3.2)将出错,前一个是int类型,后一个是double
- (针对类模板):A<int> a,比如template<class T>T g(T a, T b){},语句调用a.g(2, 3.2)在编译时不会出错,但会有警告。
- 示例:
#include<iostream> using namespace std; template <class T> class A{ public : T g(T a,T b); A(); }; template <class T> A<T>::A(){}; template <class T> T A<T>::g(T a,T b){ return a+b; } int main() { A <int> a; cout<<a.g(2,3.2)<<endl; } //此外,这里的输出结果是5.
-
非类型形参
- 2.1 、非类型模板形参:模板的非类型形参也就是内置类型形参,如template<class T, int a> class B{};其中int a就是非类型的模板形参。
2.2、 非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
2.3、 非类型模板的形参只能是整型,指针和引用,像double,String, String **这样的类型是不允许的。但是double &,double *,对象的引用或指针是正确的。
2.4、 调用非类型模板形参的实参必须是一个常量表达式,即他必须能在编译时计算出结果。
2.5 、注意:任何局部对象,局部变量,局部对象的地址,局部变量的地址都不是一个常量表达式,都不能用作非类型模板形参的实参。全局指针类型,全局变量,全局对象也不是一个常量表达式,不能用作非类型模板形参的实参。
2.6、 全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
2.7 、sizeof表达式的结果是一个常量表达式,也能用作非类型模板形参的实参。
2.8 、当模板的形参是整型时调用该模板时的实参必须是整型的,且在编译期间是常量,比如template <class T, int a> class A{};如果有int b,这时A<int, b> m;将出错,因为b不是常量,如果const int b,这时A<int, b> m;就是正确的,因为这时b是常量。
2.9 、非类型形参一般不应用于函数模板中,比如有函数模板template<class T, int a> void h(T b){},若使用h(2)调用会出现无法为非类型形参a推演出参数的错误,对这种模板函数可以用显示模板实参来解决,如用h<int, 3>(2)这样就把非类型形参a设置为整数3。显示模板实参在后面介绍。
2.10、 非类型模板形参的形参和实参间所允许的转换
1、允许从数组到指针,从函数到指针的转换。如:template <int *a> class A{}; int b[1]; A<b> m;即数组到指针的转换
2、const修饰符的转换。如:template<const int *a> class A{}; int b; A<&b> m; 即从int *到const int *的转换。
3、提升转换。如:template<int a> class A{}; const short b=2; A<b> m; 即从short到int 的提升转换
4、整值转换。如:template<unsigned int a> class A{}; A<3> m; 即从int 到unsigned int的转换。
5、常规转换。 - 示例实现:
#include <iostream> #include<iostream> #include<string> #include <cstdlib> using namespace std; template <class T ,int MAXSIZE>class Stack{ private : T elems[MAXSIZE];//包含元素的数组 int numElems;//元素的当前总个数 public: Stack(); void push(T const&); void pop(); T top()const; bool empty()const{ return numElems==MAXSIZE; } 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(numElems==MAXSIZE){ // throw std::out_of_range("Stack<>::push(): stack is full"); } elems[numElems]=elem;//附加元素 ++numElems;//增加元素的个数 } template <class T ,int MAXSIZE> void Stack<T,MAXSIZE>::pop(){ if(numElems<=0){ // throw std::out_of_range("Stack<>::pop(): empty stack"); } --numElems;//减少元素的个数 } template <class T ,int MAXSIZE> T Stack<T,MAXSIZE>::top()const{ if (numElems <= 0) { // throw std::out_of_range("Stack<>::top(): empty stack"); } return elems[numElems-1]; // 返回最后一个元素 } int main(){ try{ Stack <int,20> stack_int_20; Stack<int ,40> stack_int_40; Stack<std::string,40> stack_string_40; stack_int_20.push(7); cout<<stack_int_20.top()<<endl; stack_int_20.pop(); stack_string_40.push("付志强"); cout<<stack_string_40.top()<<endl; stack_string_40.pop(); stack_string_40.pop(); return 0; } catch(std::exception const& ex) { std::cerr << "Exception: " << ex.what() << std::endl; return EXIT_FAILURE; // 退出程序且有ERROR标记 } } //输出结果是7,付志强
- 示例2实现
1 #include<iostream> 2 using namespace std; 3 4 template <typename T> class CompareDemo{ 5 public: 6 int compare(const T&,const T&); 7 }; 8 template <typename T> 9 int CompareDemo<T>::compare(const T&a,const T&b){ 10 if((a-b)>0) 11 return 1; 12 else if((a-b)<0) 13 return -1; 14 else 15 return 0; 16 } 17 18 //改动的版本 19 template <typename T> class CompareDemo02{ 20 public: 21 int compare( T&, T&); 22 }; 23 template <typename T> 24 int CompareDemo02<T>::compare( T&a, T&b){ 25 if((a-b)>0) 26 return 1; 27 else if((a-b)<0) 28 return -1; 29 else 30 return 0; 31 } 32 33 //示例三 34 template <typename T> 35 const T& max(const T &a,const T&b){ 36 return a>b?a:b; 37 } 38 39 int main(){ 40 //Demo01 41 CompareDemo<int> cd; 42 //这个因为参数是const,所以必须是常量值传递进去 43 cout<<cd.compare(2,3)<<endl; 44 //输出结果-1; 45 46 CompareDemo<double> cb; 47 cout<<cb.compare(3.1,3.2)<<endl; 48 //运行结果-1 49 50 //Demo02 51 CompareDemo02<int >cc; 52 int a=2,b=3;//这个传递进去的必须是常量值,但是a,b都不是const,所以必须先赋值 53 cout<<cc.compare(a,b)<<endl; 54 55 cout<<max(2.1,2.2)<<endl;//模板实参被隐式的推演成double 56 cout<<max<double>(2.1,2.2)<<endl;//显示的指定模板参数 57 cout<<max<int>(2.1,2.2)<<endl;//显示的指定模板参数,会将函数直接转换为int 58 59 }
四、类模板的默认模板类型形参
1、可以为类模板的类型形参提供默认值,但不能为函数模板的类型形参提供默认值。函数模板和类模板都可以为模板的非类型形参提供默认值。2、类模板的类型形参默认值形式为:template<class T1, class T2=int> class A{};为第二个模板类型形参T2提供int型的默认值。
3、类模板类型形参默认值和函数的默认参数一样,如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值,比如template<class T1=int, class T2>class A{};就是错误的,因为T1给出了默认值,而T2没有设定。
4、在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型。比如template<class T1, class T2=int> class A{public: void h();}; 定义方法为template<class T1,class T2> void A<T1,T2>::h(){}。
-
- 示例: