本章知识点:(不按顺序讲解)
- 模板的类型参数
- 模板的非类型参数
- 模板的实参推演
- 模板函数的重载
以上为本文讲解的内容
- 模板的特例化(专用化)
https://blog.csdn.net/KingOfMyHeart/article/details/98503430 - 模板的实例化:显式实例化 和 隐式实例化
https://blog.csdn.net/KingOfMyHeart/article/details/98481514
1.模板的类型参数:
定义模板参数列表:
template <typename T,class E> //类型由typename或者class指定;
T 和 E是类型参数,将来专门接收各种各样的类型.
2.函数模板与模板函数:
2.1 函数模板:即函数的模板;
//这是一个函数模板
template <typename T>
bool compare(T a,T b)
{
sdssds
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
int main()
{
return 0;
}
上述代码中,我在windows上进行编译,可以编译通过
在g++下编译不通过,主要原因:
程序在编译的时候 windows下VS编译器只会检查函数模板的原型是都正确,而不会去检查函数体是都正确(windows下,linux下会检测出错:未定义的"sdssds").
模板原型:
template <typename T>
bool compare(T a,T b)
2.2 模板函数:通过函数模板实例化出的特定类型的函数代码
template <typename T>
bool compare(T a,T b)
{
//sdssds
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
int main()
{
compare(100,200);
compare<double>(99.9,88.0);
compare<int>(77,666);
return 0;
}
compare(100,200):在调用点调用时会生成下面这样一份代码,其中类型推导为int.
bool compare(int a,int b)
{
//sdssds
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
语法上来讲,编译器编译时实际上对上面生成的这份代码进行编译
想一下,如果我们不把sdssds这些未定义的字符串进行注释,在windows上编译可以通过吗?
bool compare(int a,int b)
{
sdssds
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
compare< double >(99.9,88.0):同样的也会生成一份代码,其中类型是调用者指定的double.
相当于将double传递给我们模板的参数列表中的T.
bool compare(double a,double b)
{
//sdssds
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
compare< int >(77,666):此时不会再生成int型的函数代码,因为上面已经有一份了.
3.模板的实参推演:
template <typename T>
bool compare(T a,T b)
{
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
int main()
{
compare(100,200);
return 0;
}
在上述代码中,我们在实例化函数时,并没有指定具体的类型,但是编译并没有出错
这是因为编译器会根据我们实际传递的实参的类型推导出来了一份int类型的代码
这个过程我们称为模板实参推演.
但是,实际上并不推荐大家使用编译器的实参推演,因为有的时候会出错
当实参中没有参数类型怎么办;
case 1:
temlate<typename T>
T fun()
{
T a;
return a;
}
上面说过,编译器生成什么类型的代码的时候是根据调用点的 类型参数决定的
但是此时调用的时: fun();
这样并没有给模板参数列表中的T进行赋值,也没有实参的传递,这样编译器就没法推导出究竟是什么类型.
1.cc:13:6: error: no matching function for call to ‘fun()’
fun();
^
1.cc:6:3: note: candidate: template<class T> T fun()
T fun()
^
1.cc:6:3: note: template argument deduction/substitution failed:
1.cc:13:6: note: couldn't deduce template parameter ‘T’
fun();
case 2 :
temlate<typename T>
T fun(int a,int b)
{
T a;
return a;
}
即使你给该模板传递了两个实参,但是这个两个实参与类型T没有半毛钱的关系
编译器依然推导不出来.
1.cc: In function ‘int main()’:
1.cc:13:12: error: no matching function for call to ‘fun(int, int)’
fun(100,99);
^
1.cc:6:3: note: candidate: template<class T> T fun(int, int)
T fun(int a,int b)
^
1.cc:6:3: note: template argument deduction/substitution failed:
1.cc:13:12: note: couldn't deduce template parameter ‘T’
fun(100,99);
case 3:
template <typename T>
bool compare(T a,T b)
{
cout<<typeid(T).name()<<endl;
cout<<typeid(a).name()<<endl;
cout<<typeid(b).name()<<endl;
return a > b;
}
int main()
{
compare(100,220.0);
return 0;
}
第一个参数是int,第二个参数是浮点型,可是我们模板的参数列表中只有一个
这样就产生二义性了,T == int 还是T == double呢.
deduced conflicting types for parameter ‘T’ (‘int’ and ‘double’)
compare(100,220.0);
除非增加一种模板类型参数
template <typename T,typename E>
bool compare(T a,E b)
{
//sdssds
cout<<typeid(T).name()<<endl; //int
cout<<typeid(a).name()<<endl; //int
cout<<typeid(b).name()<<endl; //double
return a > b;
}
实例化时:
compare<int>(100,99.9); //此时 T==int E== double
综上所述,我们还是多敲几个代码,老老实实的将需要实例化的类型传递给模板.
4.模板的"重载":
#include <iostream>
using namespace std;
template <typename T>
bool compare(T a,T b)
{
cout<<"template<typename T>"<<endl;
return a > b;
}
//模板的特例话
template<>
bool compare<int>(int a,int b)
{
cout<<"template<> function"<<endl;
return a>b;
}
//普通函数
bool compare(int a,int b)
{
cout<<"nomal function"<<endl;
return a>b;
}
int main()
{
int a =10;
int b = 100;
compare(a,b);//不指定参数列表
return 0;
}
输出:
nomal function
我们发现模板,模板的特例化以及普通的函数可以共存,调用时的优先顺序如下:
- 普通函数最优先:因为最轻量,不需要实例化代码;
- 其次是特例化函数;
- 最后才考虑根据函数模板去实例化一份代码.
严格意义上这不叫重载,因为重载一般指的是同一作用域下,函数名相同参数列表不同的函数,所以我在这里加个双引号.
5.模板的非类型参数:
#include <iostream>
using namespace std;
#include <cstdlib>
//template<typename T,int SIZE = 99> 可以给一个默认值
template<typename T,int SIZE>
void mysort(T array[])
{
int i = 0;
int j = 0;
T temp = T(); //零初始化 零构造
for (i = 0; i<SIZE-1;++i){
for(j= 0;j<SIZE-i-1;++j){
if(array[j] > array[j+1])
{
temp = array[j];
array[j] = array[j+1];
array[j+1] = temp;
}
}
}
}
int main()
{
int array[10];
for (int i = 0; i < 10;++i)
{
array[i] = rand()%100+1;
}
mysort<int,10>(array);
//调用的时候SIZE当做模板的非类型参数而不是函数的参数
//这就是函数的非类型参数
for (int i = 0; i < 10;++i)
{
cout<<array[i]<<" ";
}
cout<<endl;
return 0;
}
非类型参数的特点:
- 模板非类型参数本身就是常量,所以我们使用的时候不能去修改它的值;
- 模板非类型参数可以在参数列表中给定一个默认值;
- 当然我们不能用变量给模板费类型参数传值;
- 不能用浮点数作为非类型参数,整数类型以及整数类型的指针和引用是可以的.
错误实例:
int size = 100;
mysort<int,size>(array); //size是个变量
const int size = 100;//这样就是OK的
总结几点:
- 函数模板不编译,只有函数模板被实例化生成一份模板函数代码时,编译器才会去编译这份刚生成的代码;
- 模板节省的是开发者写的代码,但是不会节省编译器编译的代码;
- 模板本身是不可以被调用的,我们调用的是实例化以后的模板函数.