一、模板的思维导图
二、模板简介
首先我提一个问题,什么是泛型编程?
泛型编程:是编写与类型无关的逻辑代码,是代码复用的一种手段。模板是泛型编程的基础。
模板又可以分为下面两个:
范型,也就是任何类型,也就是不依赖于具体的数据类型通常,我们的操作都是都是依赖于具体的数据类型的,比如
这个add函数依赖于int类型,你只能以int类型调用(即使是flaot,也被转为了int),得到的结果也只能是int。
而范型依靠C++的模板技术,可以支持任意数据类型,比如,
模板的优缺点:
【优点】 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。 增强了代码的灵活性。
【缺点】 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
三、函数模板
函数模板:代表了一个函数家族,这个函数与类型无关,但是在使用的时候被参数化,根据实参类型产生相应的一个函数的特定类型模板。
模板函数的格式
上图中定义模板关键字是template<typename T>
其中T是模板形参的名字,可以任意命名,typename是用来定义模板参数关键字,也可以使用class。建议尽量使用typename。
模板是一个蓝图, 它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
模板在这个过程中被编译了两次:
①实例化之前,检查模板代码本身,查看是否出现语法错误。
②在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用。
现在我们再来思考一个问题,模板是否会像普通的函数一样隐式转化呢
应对此上问题我们采用显示转化如:add<double>(1, 1.3)如此便能解决问题。
同时编译器只会执行两种转化
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指 针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
四、模板参数
模板参数有两种类型参数:模板参数和调用参数。
模板参数名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则。
我们来看下面代码:
注:1、模板形参明在同一模板形参列表中只能使用一次
2、所有模板形参前面必须加上class或者typename关键字修饰
3、在函数模板的内部不能指定缺省的模板实参
int add(int a,int b)
{
return a+b;
}
缺点就是只要有新类型出现,我们就要重新添加相应的函数,同时代码的复用率不高。这个add函数依赖于int类型,你只能以int类型调用(即使是flaot,也被转为了int),得到的结果也只能是int。
而范型依靠C++的模板技术,可以支持任意数据类型,比如,
template <class T>
T add(T a,T b)
{
return a + b;
}
这个函数,你就可以用int,float,甚至string进行实例化,然后进行调用,这就不依赖于具体数据类型了。
模板的优缺点:
【优点】 模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。 增强了代码的灵活性。
【缺点】 模板让代码变得凌乱复杂,不易维护,编译代码时间变长。 出现模板编译错误时,错误信息非常凌乱,不易定位错误。
三、函数模板
函数模板:代表了一个函数家族,这个函数与类型无关,但是在使用的时候被参数化,根据实参类型产生相应的一个函数的特定类型模板。
模板函数的格式
上图中定义模板关键字是template<typename T>
其中T是模板形参的名字,可以任意命名,typename是用来定义模板参数关键字,也可以使用class。建议尽量使用typename。
模板是一个蓝图, 它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产生模板特定类型的过程称为函数模板实例化。
模板在这个过程中被编译了两次:
①实例化之前,检查模板代码本身,查看是否出现语法错误。
②在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化类型不支持某些函数调用。
现在我们再来思考一个问题,模板是否会像普通的函数一样隐式转化呢
应对此上问题我们采用显示转化如:add<double>(1, 1.3)如此便能解决问题。
同时编译器只会执行两种转化
1、const转换:接收const引用或者const指针的函数可以分别用非const对象的引用或者指针来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指 针转换。数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
四、模板参数
模板参数有两种类型参数:模板参数和调用参数。
模板参数名字只能在模板形参之后到模板声明或定义的末尾之间使用,遵循名字屏蔽规则。
我们来看下面代码:
#include <iostream>
using namespace std;
typedef int T;
template<typename T>
void FunTest(T t)
{
cout << "t Type:" << typeid(t).name() << endl;
}
T gloab;
int main()
{
FunTest(20);
cout << "gloab Type:" << typeid(gloab).name() << endl;
system("pause");
return 0;
}
类型形参转换(类型不确定)
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
①const 转换,接收const引用或者const指针的函数可以分别用非const对象的引用或指针来调用。
#include <iostream>
using namespace std;
template<class T>
void FunTest(const T t)
{
int _t = 2;
t = &_t;
cout << "t = " << *t <<endl;
}
int main()
{
int a = 1;
int *pa = &a;
FunTest(pa);
system("pause");
return 0;
}
此时我们将FunTest函数中的const去掉会发现:
②数组或函数到指针的转换,如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换,数组实参将当做指向其第一个元素的指针,函数实参当做指向函数类型的指针。
#include <iostream>
using namespace std;
template<class T, class U>
void FunTest(T t, U u)
{
cout << typeid(t).name() << endl;
cout << typeid(u).name() << endl;
}
void FunArray()
{
int a = 10;
}
int main()
{
int array[10] = { 0 };
FunTest(array, FunArray);
system("pause");
return 0;
}
非类型形参转换(类型确定)
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。
例如数组长度:
#include <iostream>
using namespace std;
template<class T,int N>
void FunTest(T (&_array)[N])
{
for (int idx = 0; idx < N; ++idx)
{
_array[idx] = 0;
}
}
int main()
{
int a[5];
char b[5];
FunTest(a);
FunTest(b);
system("pause");
return 0;
}
注:1、模板形参明在同一模板形参列表中只能使用一次
2、所有模板形参前面必须加上class或者typename关键字修饰
3、在函数模板的内部不能指定缺省的模板实参