函数模板
函数模板:template <>,<>是模板类型参数列表,<>里面放着类型,它是由typename/class定义而来。但是我们在定义时一般不会用class来定义,因为这会使我们很容易误认为这时一个类模板。它是在编译阶段处理的,也就是说在编译阶段会将代码展开,将typedef T替换成我们需要的类型,所以这其实是一个类型重定义的过程。
模板函数与函数模板
函数模板是一个模板类型,而模板函数是指一个函数。模板函数是函数模板在调用点实例化后生成的。读起来有点绕口,所以用一段代码来说明:
template<typename T>
bool Max(T a, T b)//函数模板
{
return a > b;
}
int main()
{
Max<int>(10, 20);//在调用点代码展开,类型重定义,生成模板函数
/*
bool Max(int a,int b)
{
return a > b;
}
*/
Max<double>(10.1, 11.2);
/*
bool Max(double a,double b)
{
return a > b;
}
*/
return 0;
}
函数模板的实参推演
在调用点没有给出类型的时候,系统会通过实参推演帮我们给出在一趟调用过程中
需要注意的是:
1.一个模板类型参数只能用一个类型来替换,即函数的二义性。
2.函数必须有实参才能推演
模板的特例化
模板的特例话分为完全特例化和部分特例化,其中函数模板只支持全特化,类模板两种都支持。当模板的版本针对某个类型不满足逻辑需求时,就需要针对这个类型来进行特殊的处理。例如在比较大小时,传进两个char*类型的参数。但是在比较时我们获取的时字符串的地址,实际上是再拿两个地址作比较。所以针对这个类型就需要进行完全特例化。
#include <iostream>
template<typename T>
bool compare(T a, T b)
{
std::cout << "模板版本" << std::endl;
return a > b;
}
template<>
bool compare<const char*>(const char* a, const char* b)
{
std::cout << "特例化版本" << std::endl;
return strcmp(a, b) > 0;
}
bool compare(const char* a, const char* b)
{
std::cout << "普通函数版本" << std::endl;
return strcmp(a, b) > 0;
}
int main()
{
compare("gg", "mm");
return 0;
}
那么,在函数模板版本,函数特例化版本,普通函数版本中,它们的调动优先级是则么样的呢?笔者通过上面代码的实验,最终得出了结论:普通函数版本(一级)<<函数特例化版本(二级)<<函数模板版本(三级)
模板的类型参数
可以通过tyoename class来定义(处理机制一样)
存在的原因:误以为class是一个类类型,实际上是一个万能类型
模板的非类型参数
<>中可以存放类型参数和非类型参数
比如:template <ytypename T,int len>,但是要注意len是常量,不能是浮点型和类类型
函数的默认值
函数获取默认值的三种方法
1.调用点<>直接获取
2.实参推演
3.默认值
优先级:调用点<<实参推演<<默认值
函数默认值的赋值规则是自右向左,模板默认值的赋值规则是随意的。
接受不明确类型的返回值
auto rt 来定义一个自动变量接受一个不明确类型的返回值
auto:通过右操作数来自适应调节类型
模板的重载
普通函数版本、模板版本,和模板特例化版本都是可以共存的。
bool compare<const char*>(const char* a, const char* b)
{
std::cout << "特例化版本" << std::endl;
return strcmp(a, b) > 0;
}
bool compare(const char* a, const char* b)
{
std::cout << "普通函数版本" << std::endl;
return strcmp(a, b) > 0;
}
上面的代码中,特例化版本的声明是:compare<const char*>(const char*,const char*);普通函数版本的声明是:compare(const char*,const char*).它们的形参是不同的,所以可以共存。
模板的显示实例化
当我们把一个函数的声明和调用点写在一个源文件,将函数的定义写在另一个源文件中时,会编译失败,因为在定义点没有去调用它。解决的方法:在定义点中写入声明即可。例如在调用点写入int sum(int a,int b);这时我们在主函数去调用整形的sum函数时编译通过,但是仅限于整形。当写入其他类型时依旧会失败。
所以我们一般会将模板的定义写在头文件中,这样当我们需要时,直接引入头文件即可。
类模板
类模板与模板类的关系和函数模板和模板函数的关系是一样的,都需要在调用点将模板实例化。
#include <iostream>
template<typename T>
class Node
{
public:
Node<T>(T val=T())
:mdata(val),pnext(NULL)
{}
/*
Node<int>(int val=int())
:mdata(val),pnext(NULL)
*/
private:
T mdata;
Node<T>* pnext;
/*
int mdata;
Node<int>* pnext;
*/
};
int main()
{
Node<int> node(10);
return 0;
}
在模板下生成的类,它里面的私有范围和公有范围都是在模板中,而我们的类模板中其实主要针对的是里面的私有变量。。所以其他函数中本来是不需要加上模板类型的,但是为了美观来说,还是建议除了构造和析构函数以外都因该加上模板类型参数
实参推演
类模板是不支持实参推演的,必须明确的给出类型
模板的实例化
类模板的实例化是选择性实例化,只有调用其中的函数时才会实例化
标题
模板的类型参数列表:
1.类型参数(如:typename T)
2.非类型参数(如:int len)
3.模板参数(将一个模板作为形参传给另一个模板)
类模板的拷贝构造函数的模板
假设现在生成了一个类模板,在调用点实例化了一个int类型的模板类:list c1。这时再在调用点实例化一个double类型的模板类:list c2 = c1。将会报错,这实际上是一个拷贝构造函数,但是将int类型的模版拷贝到一个double类型的模版中实例化显然是不现实的。因为我们知道在类模版中拷贝构造函数(假设是一个链表类):list(list* const this,const list&rhs),这时this指针指向的是double类型,rhs指向的是int类型。
template<typename E>
list(const list<E>& rhs)//list(list<T>* const this,const list<E>&rhs)
{
phead = new Node();//让头指针只想一个新的节点(重新构建一个链表)
list<E>::Node* pCur = rhs.phead->pnext;//让新节点指向原来头结点的next域
Node* ptail = phead;//让尾节点和头结点指向同一个位置
while (pCur != NULL)//当原来的节点不为空时
{
Node* pnewnode = new Node(pCur->mdata);//申请一个新节点,将旧结点中的数据放到新节点的数据域
ptail->pnext = pnewnode;//尾指针的next域指向新开辟的节点
ptail = ptail->pnext;//往后走
pCur = pCur->pnext;//旧节点往后走
}
}
类模版的特例化
特例化分为完全特例化和部分特例化。
完全特例化:在几百个函数中如果有一两个函数的实例化不能通过模版来正确的处理,就需要为其做一个完全特例化版本,比如说让两个类型的值相加,那么字符串就没办法来实例化。完全特例化版本如下:
template<>
class sum<char*,char*>
{
public:
sum(const char* a, const char* b)
:ma(a),mb(b)
{}
char* add()
{
char* tmp = new char[strlen(ma) + strlen(mb) + 1]();
strcpy_s(tmp, strlen(ma) + 1, ma);
strcat(tmp, mb);
return tmp;
}
private:
const char* ma;
const char* mb;
};
部分特例化:针对指针类型的偏特化:
template<typename T1,typename T2>
class sum<T1*, T2*>
{
public:
sum(T1* a, T2* b)
:ma(a),mb(b)
{}
T1 add()
{
std::cout << "sum<T1*,T2*>" << std::endl;
return *ma + *mb;
}
private:
T1* ma;
T2* mb;
};