在写这篇文章的时候,我想到了一个很有趣的事情。
csdn,也提供了一些模板。
实际上,日常生活中我们经常会用到模板。
就比如我,在某团买团购后,都习惯给商家好评,然后就会参照某个模板来写,改变了商家的名字和一些商品而已。模板给了我们很大的便利,不用重复多次的写差不多的东西。而C++中,也提供了模板。
C++中的模板有两种:函数模板、类模板
函数模板
假设想编写一个函数对两个数进行相加,考虑到相加的类型可能会不同,第一次我可能会尝试利用重载定义多个函数。
//两个int类型相加
int Add(int x1, int x2)
{
return (x1 + x2);
}
//两个float类型相加
double Add(double x3, double x4)
{
return (x3 + x4);
}
int main(void)
{
int a = 11;
int b = 22;
double c = 3.14;
double d = 1.42;
int ret_int = Add(a, b);
int ret_double = Add(c, d);
return 0;
}
它们实现的都是相加的意图,虽然C++支持重载,但是仅仅只由于类型不同就要再写一份,这样的方法未免太笨拙了!可不可以提供一份模板,让编译器根据情况自己生成呢?答案是可以的。
【函数模板的含义】
函数模板代表了一个函数家族,它与类型独立(即与类型无关),在使用的时候被参数化,根据调用时的实参按照模板定义出特定的类型版本。
函数模板的格式为
template<typename T1, typename T2, ……,typename Tn>
或
template<class T1, class T2, ……, class Tn>
这里要注意的点:
- 后面没有分号
- <>里面为参数列表,使用逗号分隔
- 模板形参列表不能为空
//格式1:template<typename T1, typename T2, ……,typename Tn>
template<typename T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
//格式2:template<class T1, class T2, ……,class Tn>
template<class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
编译器会根据提供实参,按照模板定义出一份特定的函数。
函数模板是一个蓝图,它本身并不是函数,是编译器定义特定具体类型函数的模具。所以模板就是将应该我们做的重复的事情交给了编译器。
有一个有趣的点:如果写了一份模板,并且已经有了一份现成的,编译器会报错吗?如果正确,编译器会做何选择?
template<class T>
void Swap(T& a, T& b)
{
T tmp = a;
a = b;
b = tmp;
}
void Swap(int& a, int& b)
{
int tmp = a;
a = b;
b = tmp;
}
int main(void)
{
int i1 = 11;
int i2 = 22;
Swap(i1, i2);
return 0;
}
答案是,编译器并不会报错,它会使用现成的,而不是根据模板再定义出一份。
你可以想象:如果你是一个铁匠,此时此刻你需要一个扳手,你的身边恰好有一个合适的扳手和一个制作扳手的模具。你会选择用现成的,还是用模具造出一个扳手再拿来使用?毋庸置疑,选择现成的扳手才是此时此刻最佳的选择!
模板的实例化
在使用函数模板的时候,编译器会根据实际的模板实参,使用实参替代相应的模板形参产生编译该版本的函数,这就是模板的实例化。
模板参数实例化分为:隐式实例化和显示实例化
【隐式实例化】
隐式实例化,就是在实例化的过程中,让编译器根据实参自己推演模板参数的实际类型。
但是有时候却是一个问题,看看下面这段代码:
编译器会出现报错,这是因为“没有与参数列表匹配的 函数模板 “Add” 实例”,而且模板函数不允许自动类型转换。
这个情况下有两种解决方案:
- 一、在调用时强制类型转化
- 二、显示实例化
【显示实例化】
显式实例化,就是在实例化的过程中,显示地指定模板参数的类型,具体操作为在函数名后增加"<>"。“<>”里为指定的模板参数的实际类型
相较于强制类型转化,显示实例化更加地理想。
inline函数模板
函数模板也可以被inline修饰,声明为inline。
它的修饰方式与普通函数略为不同。
修饰方式:template <typename T> inline
//可被inline修饰,但是inline要放在模板参数列表之后,返回类型之前
template <class T> inline
T Add(const T& x, const T& y)
{
return x + y;
}
int main(void)
{
int a = 11;
double c = 3.14;
Add<int>(a, c);
return 0;
}
类模板
我假定你已经学习了函数模板……
同定义函数模板一样,也可以定义出类模板。
类模板定义的格式
//类模板定义格式
template <typename T1, typename T2, ……,typename Tn>
class 类模板名
{
//……
};
为了更好地说明类模板,我将参照标准程序库中的stack类,实现一个简单的版本my_stack类,my_stack这个类将支持不同类型的对象,意味着我们需要使用到类模板。
//类模板例子
template <typename T>
class my_stack
{
public:
my_stack()
:_arry(nullptr)
,_top(0)
,_capacity(0)
{
;
}
void push_my_satck(T push_val)
{
//定义
}
void pop_my_stack()
{
//定义
}
//返回栈顶元素
T top_my_stack()
{
//定义
}
//……
~my_stack()
{
free(_arry);
_arry = nullptr;
_top = _capactiy = 0;
}
private:
T* _arry;
int _top;
int _capacity;
};
这段程序中,我将类模板中的函数在类体内进行了定义。
而如果在类体外定义需要注意:类模板中的成员函数放在类体外定义时,需要加模板参数列表。
//如果在类体外定义push_my_stcak成员函数
//需要加模板参数列表
template <typename T>
void my_stack<T>::push_my_stack(T val)
{
//语句
}
类模板的实例化
和函数模板的实例化有些不同,类模板在创建对象的时候必须显示指定实参,才能实例化,类模板的名字不是真正的类,实例化的结果才是真正的类
//例如
my_stack<int> s1;
my_stack<char> s2;
my_stack<double> s3;