C++学习笔记(十三)——模板
模板是一种通用的描述机制,也就是说,使用模板允许使用通用类型来定义函数或类等,在使用时,通用类型可被具体的类型,如int、double甚至是用户自定义的类型来代替。模板引入一种全新的编程思维方式,称为“泛型编程”或“通用编程”。形象地说,把函数比喻为一个游戏过程,函数的流程就相当于游戏规则,在以往的函数定义中,总是指明参数是int型还是double型等等,这就像是为张三(好比int型)和李四(好比double型)比赛制定规则。可如果王五(char*型)和赵六(bool型)要比赛,还得提供一套函数的定义,这相当于又制定了一次规则,显然这是很麻烦的。模板的的引入解决了这一问题,不管是谁和谁比赛,都把他们定义成A与B比赛,制定好了A与B比赛的规则(定义了关于A和B的函数)后,比赛时只要把A替换成张三,把B替换成李四就可以了,大大简化了程序代码量,维持了结构的清晰,大大提高了程序设计的效率。该过程称为“类型参数化”。模板的引入使得函数定义摆脱了类型的束缚,代码更为高效灵活。C++中,通过下述形式定义一个模板:
template <class T>
或template<typename T>
。早期模板定义使用的是class,关键字typename是最近才加入到标准中的,相比class,typename更容易体现“类型”的观点,虽然两个关键字在模板定义时是等价的,但从代码兼容的角度讲,使用class较好一些。模板有函数模板和类模板之分。通过参数实例化构造出具体的函数或类,称为模板函数或模板类。
函数模板
函数模板的使用规则和普通函数是相同的,在使用函数模板之前,必须对函数模板进行声明,此声明必须在外部进行,也就是说不能在任何一个函数(包括main函数)中声明,声明的格式为:
template <class T1[,class T2,……]>
函数原型
和普通函数一样,如果在使用函数模板前对函数模板进行了定义,函数模板的声明可以省略。示例代码如下所示(代码13-1):
///
/// @file func_Template1.cc
/// @date 2019-02-25 08:53:11
///
#include <iostream>
using std::cout;
using std::endl;
//函数模板
//-------------------------
template <typename T>
T add(T x, T y)
{
cout << "add(T x, T y)" << endl;
return x+y;
}
template <typename T>
T add(T x, int y)
{
cout << "add(T x, int y)" << endl;
return x+y;
}
int main()
{
double dx = 12.2, dy = 23.3;
int ix = 2, iy = 3;
// cout << "ix + iy = " << add(ix,iy) << endl;//这段代码报错,报错信息的意思:不知道调用T add(T x, int y)还是T add(T x, T y)
cout << "dx + dy = " << add(dx,dy) << endl;
cout << "dx + iy = " << add(dx,iy) << endl;
return 0;
}
函数模板实际上不是个完整的函数定义,因为其中的类型参数还不确定,只是定义了某些类型的角色(或变量)在函数中的操作形式,因此,必须将模板参数实例化才能使用函数,用模板实例化后的函数也称为模板函数。分为隐式实例化和显式实例化,区分如下代码所示(代码13-2):
///
/// @file template_func3.cc
/// @author XuHuanhuan(1982299154@qq.com)
/// @date 2019-02-25 22:37:34
///
#include <iostream>
using std::cout;
using std::endl;
//函数模板的声明
//-------------------
template <typename T>
T max(T x, T y);
//----------------------
int main()
{
int ix = 2, iy = 3;
double dx = 2.34, dy = 3.45;
cout << max(ix, iy) << endl;//隐式实例化
cout << max<int>(ix, iy) << endl;//显式实例化
cout << max(dx, dy) << endl;//隐式实例化
cout << max<double>(dx,dy) << endl;//显式实例化
return 0;
}
template <typename T>
T max(T x, T y)
{
return (x > y)? x:y;
}
返回类型和函数的参数列表中不但可以包含类型参数还可以包含某类型的常量,在函数中可以使用模板参数表中的常量表达式,如:
template <typename T1,typename T2,int number> //number是一个int型的常量
double fun(T1 a,int b,T2 c)//为定义的函数模板
{
return a * (number + b) * c;//其中number可以作为一个int型常量来使用
}
//调用如下:
int main()
{
cout << fun<int, 3>(4,5) << endl; // fun<int, 3>(4,5,6)中,int说明T的类型,3制定了number的值,a=4,b=5,c=6;
}
函数模板支持重载,既可以模板之间重载(同名模板),也可以实现模板和普通函数间的重载,但模板的重载相比普通函数的重载要复杂一点,首先看一个例子:
template <typename T1,typename T2>
T1 Max(T1 a,T2 b){……}
与
template <typename T3,typename T4>
T3 Max(T3 c,T4 d){……}
看似不同的两个模板,仔细分析后发现,其本质是一样的,如果调用“Max(2,3.5);”,都实例化为“Max(int,double);”,会出现重复定义的错误。如上面(代码13-1)所示,在main函数体内批注的一行就出现了报错,这说明模板之间的重载是受到很大限制的。另外,仅仅依靠返回值不同的模板重载也是不合法的,如:
template <typename T1,typename T2>
T1 Greater(T1 a,T2 b){……}
与
template <typename T3,typename T4>
T3* Greater(T3 c,T4 d){……}
这两段代码就是不合法的。所以,==如果不是必须要求,函数模板之间最好不要重载。==函数模板与普通函数的重载相比普通函数之间的重载就有一点不同,函数模板与普通函数的重载存在优先级:一般函数优先于模板函数执行。
类模板
实例代码如下(代码13-3):
///
/// @file template_class1.cc
/// @author XuHuanhuan(1982299154@qq.com)
/// @date 2019-02-26 09:33:31
///
#include <iostream>
using std::cout;
using std::endl;
template <typename T, int NUM>//如果出现非类型参数,则必须是int型
class Stack
{
private:
T stack[NUM];
int point;
public:
Stack()
:point(-1)
{
}
bool isEmpty();//成员函数必须来类外定义
bool isFull();
bool push(const T &);
bool pop();
T retNum();
int & GetPos()
{
return point;
}
};
template <typename T, int NUM>
bool Stack<T,NUM>::isEmpty()
{
return point == -1;//空时为true
}
template <typename T, int NUM>
bool Stack<T, NUM>::isFull()
{
return point == NUM-1;//满时为true
}
template <typename T, int NUM>
bool Stack<T, NUM>::push(const T & hs)
{
++point;
if(point < NUM-1)
{
stack[point] = hs;
return true;
}else
{
stack[point] = hs;
cout << "栈已满" << endl;
return false;
}
}
template <typename T, int NUM>
bool Stack<T, NUM>::pop()
{
if(point > -1)
{
--point;
return true;
}else
{
cout << "栈为空" << endl;
return false;
}
}
template<typename T, int NUM>
T Stack<T,NUM>::retNum()
{
if(!isEmpty())
{
return stack[point];
}else{
cout << "栈为空" << endl;
T x;
return x;
}
}
int main()
{
Stack<double,10> sta1;
cout << "是否为空? " << sta1.isEmpty() << endl;
for(int idx = 0; idx < 10; idx++)
{
sta1.push(9.1*idx);
}
cout << "是否为空? " << sta1.isEmpty() << endl;
cout << "是否为满? " << sta1.isFull() << endl;
for(int idx = 11; idx > 0; --idx)
{
cout << "顶元素为: " << sta1.retNum() << endl;
sta1.pop();
}
return 0;
}
函数模板学会后,类模板的掌握也是水到渠成的事。代码13-3相对完整地实现了一个栈类Stack,列出了类模板和成员函数模板。模板的引入使得类的定义更为灵活,可以在类创建时指明其中的元素类型T,以及非类型常量NUM的大小,需要注意的是,不管是类定义还是成员函数定义,都要遵守模板的定义形式。==这就意味着模板成员函数必须来类外定义,因为模板的定义形式要求模板必须在全局变量区声明和定义,而不能在类内直接定义。==注意,类模板和成员函数模板不是传统意义上的类定义和成员函数定义,由类模板实例化而生成的具体类称为模板类。定义模板类对象的格式为:类模板名<T> 对象名; eg: Stack<int, 10> stack;
其中类模板参数可以给默认值,但函数模板不能给默认值。如果出现非类型参数,则必须是int型。