模板是C++支持参数化多态的工具使用模板 用户为类或者函数声明 一般模式使得类中的某些数据成员或者成员函数的参数 返回值取得任意类型

模板是一种对类型进行参数化的工具

通常有两种形式函数模板和类模板

函数模板针对仅参数类型不同的函数

类模板针对仅数据成员和成员函数类型不同的类

使用模板的目的就是能够让程序员编写与类型无关的代码 比如编写了一个交换两个整型int 类型的swap函数这个函数就只能实现int 型对double字符这些类型无法实现要实现这些类型的交换就要重新编写另一个swap函数 使用模板的目的就是要让这程序的实现与类型无关比如一个swap模板函数即可以实现int 型又可以实现double型的交换 模板可以应用于函数和类 下面分别介绍

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


一 函数模板通式

1 函数模板的格式

template 返回类型 函数名(参数列表){ 函数体}

其中template和class是关见字class可以用typename 关见字代替在这里typename 和class没区别括号中的参数叫模板形参模板形参和函数形参很相像模板形参不能为空 一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数即可以在该函数中使用内置类型的地方都可以使用模板形参名 模板形参需要调用该模板函数时提供的模板实参来初始化模板形参一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例 比如swap的模板函数形式为:

template void swap(T& a, T& b){}

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

2 注意对于函数模板而言不存在 h(int,int) 这样的调用不能在函数调用的参数中指定模板形参的类型对函数模板的调用应使用实参推演来进行即只能进行 h(2,3) 这样的调用或者int a, b; h(a,b)

函数模板的示例演示将在下文中涉及


二 类模板通式

1 类模板的格式为

template class 类名{ ... };

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

template class A{public: T a; T b; T hy(T c, T &d);};

在类A中声明了两个类型为T的成员变量a和b还声明了一个返回类型为T带两个参数类型为T的函数hy

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

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

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

template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体}

比如有两个模板形参T1T2的类A中含有一个void h()函数则定义该函数的语法为

template void A::h(){} 

注意当在类外面定义类的成员时template后面的模板形参应与要定义的类的模板形参一致

5 再次提醒注意模板的声明或定义只能在全局命名空间或类范围内进行 即不能在局部范围函数内进行比如不能在main函数中声明或定义一个模板


三 模板的形参

有三种类型的模板形参类型形参非类型形参和模板形参

1 类型形参

1.1 类型模板形参类型形参由关见字class或typename后接说明符构成template void h(T a){};其中T就是一个类型形参类型形参的名字由用户自已确定 模板形参表示的是一个未知的类型 模板类型形参可作为类型说明符用在模板中的任何地方与内置类型说明符或类类型说明符的使用方式完全相同即可以用于指定返回类型变量声明等

作者原版1.2 不能为同一个模板类型形参指定两种不同的类型比如templatevoid h(T a, T b){}语句调用h(2, 3.2)将出错因为该语句给同一模板形参T指定了两种类型第一个实参2把模板形参T指定为int而第二个实参3.2把模板形参指定为double两种类型的形参不一致会出错 针对函数模板

作者原版1.2针对函数模板是正确的但是忽略了类模板 下面将对类模板的情况进行补充

本人添加1.2补充版针对于类模板 当声明类对象为A a比如templateT g(T a, T b){}语句调用a.g(2, 3.2)在编译时不会出错但会有警告因为在声明类对象的时候已经将T转换为int类型而第二个实参3.2把模板形参指定为double在运行时会对3.2进行强制类型转换为3 当声明类的对象为A a,此时就不会有上述的警告因为从intdouble是自动类型转换

演示示例

TemplateDemo.h

#ifndef TEMPLATE_DEMO_HXX#define TEMPLATE_DEMO_HXXtemplate<class T> class A{public:T g(T a,T b);A();};#endif TemplateDemo.cpp#include<iostream.h>#include "TemplateDemo.h"template<class T> A<T>::A(){}template<class T> T A<T>::g(T a,T b){return a+b;}void main(){A<int> a;cout<<a.g(2,3.2)<<endl;}

编译结果

--------------------Configuration: TemplateDemo - Win32 Debug--------------------Compiling...TemplateDemo.cppG:C++CDaimaTemplateDemoTemplateDemo.cpp(12) : warning C4244: 'argument' : conversion from 'const double' to 'int', possible loss of dataTemplateDemo.obj - 0 error(s), 1 warning(s)

运行结果 5 

 

从上面的测试示例中可以看出并非作者原作中的那么严密此处仅是本人跟人测试结果请大家本着实事求是的态度自行验证

2 非类型形参

2.1 非类型模板形参模板的非类型形参也就是内置类型形参template class B{};其中int a就是非类型的模板形参

2.2 非类型形参在模板定义的内部是常量值也就是说非类型形参在模板的内部是常量

2.3 非类型模板的形参只能是整型指针和引用像doubleString, String **这样的类型是不允许的 但是double &double *对象的引用或指针是正确的

2.4 调用非类型模板形参的实参必须是一个常量表达式即他必须能在编译时计算出结果

2.5 注意任何局部对象局部变量局部对象的地址局部变量的地址都不是一个常量表达式都不能用作非类型模板形参的实参 全局指针类型全局变量全局对象也不是一个常量表达式不能用作非类型模板形参的实参

2.6 全局变量的地址或引用全局对象的地址或引用const类型变量是常量表达式可以用作非类型模板形参的实参

2.7 sizeof表达式的结果是一个常量表达式也能用作非类型模板形参的实参

2.8 当模板的形参是整型时调用该模板时的实参必须是整型的且在编译期间是常量比如template class A{};如果有int b这时A m;将出错因为b不是常量如果const int b这时A m;就是正确的因为这时b是常量

2.9 非类型形参一般不应用于函数模板中比如有函数模板template void h(T b){}若使用h(2)调用会出现无法为非类型形参a推演出参数的错误对这种模板函数可以用显示模板实参来解决如用h(2)这样就把非类型形参a设置为整数3 显示模板实参在后面介绍

2.10 非类型模板形参的形参和实参间所允许的转换
1 允许从数组到指针从函数到指针的转换 如template class A{}; int b[1]; A m;即数组到指针的转换
2 const修饰符的转换 如template class A{}; int b; A<&b> m;   即从int *到const int *的转换
3 提升转换 如template class A{}; const short b=2; A m; 即从short到int 的提升转换
4 整值转换 如template class A{};   A<3> m; 即从int 到unsigned int的转换
5 常规转换

非类型形参演示示例1

由用户自己亲自指定栈的大小并实现栈的相关操作

TemplateDemo.h

#ifndef TEMPLATE_DEMO_HXX#define TEMPLATE_DEMO_HXXtemplate<class T,int MAXSIZE> class Stack{//MAXSIZE由用户创建对象时自行设置private:T elems[MAXSIZE]; // 包含元素的数组int numElems; // 元素的当前总个数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(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]; // 返回最后一个元素}#endif

TemplateDemo.cpp

#include<iostream.h>#include <iostream>#include <string>#include <cstdlib>#include "TemplateDemo.h"int main(){try {Stack<int,20> int20Stack; // 可以存储20个int元素的栈Stack<int,40> int40Stack; // 可以存储40个int元素的栈Stack<std::string,40> stringStack; // 可存储40个string元素的栈// 使用可存储20个int元素的栈int20Stack.push(7);std::cout << int20Stack.top() << std::endl; //7int20Stack.pop();// 使用可存储40个string的栈stringStack.push("hello");std::cout << stringStack.top() << std::endl; //hellostringStack.pop();stringStack.pop(); //Exception: Stack<>::pop<>: empty stackreturn 0;}catch (std::exception const& ex) {std::cerr << "Exception: " << ex.what() << std::endl;return EXIT_FAILURE; // 退出程序且有ERROR标记}}

2012102509100898-.jpg

非类型形参演示示例2

TemplateDemo01.h

#ifndef TEMPLATE_DEMO_O1#define TEMPLATE_DEMO_01template<typename T> class CompareDemo{public:int compare(const T&, const T&);};template<typename T>int CompareDemo<T>::compare(const T& a,const T& b){if((a-b)>0)return 1;else if((a-b)<0)return -1;elsereturn 0;}#endif

TemplateDemo01.cpp

#include<iostream.h>#include "TemplateDemo01.h"void main(){CompareDemo<int> cd;cout<<cd.compare(2,3)<<endl;}

运行结果-1 

TemplateDemo01.cpp

#include<iostream.h>#include "TemplateDemo01.h"void main(){CompareDemo<double> cd;cout<<cd.compare(3.2,3.1)<<endl;}

运行结果 1 

 

TemplateDemo01.h

#ifndef TEMPLATE_DEMO_O1#define TEMPLATE_DEMO_01template<typename T> class CompareDemo{public:int compare(T&, T&);};template<typename T>int CompareDemo<T>::compare(T& a,T& b){if((a-b)>0)return 1;else if((a-b)<0)return -1;elsereturn 0;}#endif

TempalteDemo01.cpp

#include<iostream.h>#include "TemplateDemo01.h"void main(){CompareDemo<int> cd;int a=2,b=3;cout<<cd.compare(a,b)<<endl;}

非类型形参演示示例3

TemplateDemo02.cpp

#include<iostream.h>template<typename T>const T& max(const T& a,const T& b){return a>b ? a:b;}void main(){cout<<max(2.1,2.2)<<endl;//模板实参被隐式推演成doublecout<<max<double>(2.1,2.2)<<endl;//显示指定模板参数 cout<<max<int>(2.1,2.2)<<endl;//显示指定的模板参数会将函数函数直接转换为int }

运行结果

2012102515230328-.jpg

cout<(2.1,2.2)<

此语句会出现警告

--------------------Configuration: TemplateDemo02 - Win32 Debug--------------------Compiling...TemplateDemo02.cppG:C++CDaimaTemplateDemo02TemplateDemo02.cpp(11) : warning C4244: 'argument' : conversion from 'const double' to 'const int', possible loss of dataG:C++CDaimaTemplateDemo02TemplateDemo02.cpp(11) : warning C4244: 'argument' : conversion from 'const double' to 'const int', possible loss of dataTemplateDemo02.obj - 0 error(s), 2 warning(s)

四 类模板的默认模板类型形参

1 可以为类模板的类型形参提供默认值但不能为函数模板的类型形参提供默认值 函数模板和类模板都可以为模板的非类型形参提供默认值

2 类模板的类型形参默认值形式为template class A{};为第二个模板类型形参T2提供int型的默认值

3 类模板类型形参默认值和函数的默认参数一样如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值比如templateclass A{};就是错误的因为T1给出了默认值而T2没有设定

4 在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型 比如template class A{public: void h();}; 定义方法为template void A::h(){}

定义类模板类型形参

演示实例1

TemplateDemo.h

#ifndef TEMPLATE_DEMO_HXX#define TEMPLATE_DEMO_HXXtemplate<class T> class A{public:T g(T a,T b);A();};#endif

TemplateDemo.cpp

#include<iostream.h>#include "TemplateDemo.h"template<class T> A<T>::A(){}template<class T> T A<T>::g(T a,T b){return a+b;}void main(){A<int> a;cout<<a.g(2,3)<<endl;}

运行结果 5

类模板的默认模板类型形参示例1

TemplateDemo03.h

#ifndef TEMPLATE_DEMO_03#define TEMPLATE_DEMO_03//定义带默认类型形参的类模板 这里把T2默认设置为int型 template<class T1,class T2=int> class CeilDemo{public:int ceil(T1,T2);};//在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型 template<class T1,class T2>int CeilDemo<T1,T2>::ceil(T1 a,T2 b){return a>>b;}#endif

TemplateDemo03.cpp

#include<iostream.h>#include "TemplateDemo03.h"void main(){CeilDemo<int> cd;cout<<cd.ceil(8,2)<<endl;}

运行结果  2 

在类模板的外部定义类中的成员时template 后的形参表应省略默认的形参类型如果没有省略不会出现编译错误而是提出警告

--------------------Configuration: TemplateDemo03 - Win32 Debug--------------------Compiling...TemplateDemo03.cppg:c++cdaimatemplatedemo03templatedemo03.h(12) : warning C4519: default template arguments are only allowed on a class template; ignoredTemplateDemo03.obj - 0 error(s), 1 warning(s)

原作者类模板类型形参默认值和函数的默认参数一样如果有多个类型形参则从第一个形参设定了默认值之后的所有模板形参都要设定默认值比如templateclass A{};就是错误的因为T1给出了默认值而T2没有设定

类模板的默认模板类型形参示例2

TemplateDemo03.h

#ifndef TEMPLATE_DEMO_03#define TEMPLATE_DEMO_03template<class T1=int,class T2,class T3>

0篇笔记写笔记

尊贵的董事大人

英文标题不为空时 视为本栏投稿


需要关键字 描述 英文标题


X