C++函数模板
第一节-函数模板
- 模板的定义是以
template 关键字开头; - 类型模板参数T前面用
typename 来修饰,所以,遇到typename 就该知道其后面跟的是一个类型。typename 可以被class取代,但此处的class并没有“类”; - 类型模板参数
T(代表是一个类型) 以前前面的修饰符typename/class都用<>括起来 - T这个名字可以换成任意其他标识符,对程序没有影响。用T只是一种编程习惯。
例子:
namespace _nmsp1
{
int Sub(int tv1, int tv2)
{
return tv1 - tv2;
}
float Sub(float tv1, float tv2)
{
return tv1 - tv2;
}
}
使用函数模板:
namespace _nmsp1
{
template <typename T>
T Sub(T tv1, T tv2)
{
return tv1 - tv2;
}
}
实例化
实例化:在编译器编译的时候,用具体的类型来代替”类型模板参数“的过程(有人也叫代码生成器)
VS上面通过dumpbin命令查看.obj文件可以查看到
.obj文件的格式一般会被认为是一种COFF----通用对象文件格式(Common Object File Format)。
Linux可以看.o文件
int Sub<int>(int,int)
double Sub<double>(double,double)
实例化之后的函数名分别叫做Sub<int>和Sub<double>
通过函数模板实例化之后的函数名包含三部分:
- a)模板名;
- b)后面跟着一对<>;
- c)<>中间是一个具体类型。
? 编译期间->实例化具体的sub函数
? 在编译阶段,编译器就会查看函数模板的函数体部分,来确定能否针对该类型string进行Sub函数模板的实例化。
在编译阶段,编译器需要能够找到函数模板的函数体部分。
在工程中,需要将函数模板包括头和体都要包含在头文件中;
模板参数的推断
-
常见的参数推断 namespace _nmsp1
{
template <typename T,typename U,typename V>
V Add(T tv1, U tv2)
{
return tv1 + tv2;
}
template <typename V,typename T,typename U>
V Add1(T tv1, U tv2)
{
return tv1 + tv2;
}
template <typename T, typename U>
auto Add2(T tv1, U tv2)
{
return tv1 + tv2;
}
template <typename T, typename U>
auto Add3(T tv1, U tv2)-> decltype(tv1 + tv2)
{
return tv1 + tv2;
}
}
int main()
{
cout << _nmsp1::Add(15, 17,8) << endl;
cout << _nmsp1::Add<int, double, double>(15, 17.8);
cout << _nmsp1::Add1<double>(15, 17.8);
cout << _nmsp1::Add2(15, 17.8) << endl;
cout << _nmsp1::Add3(15, 17.8) << endl;
return 0;
}
-
各种推断的比较以及空模板参数列表的推断
- 自动推断
- 指定类型模板参数,优先级比自动推断高
- 指定空模板参数列表<>:作用就是请调用mydouble函数模板而不是调用普通的mydouble函数。
namespace _nmsp1
{
template <typename T>
T mydouble(T tmpvalue)
{
return tmpvalue * 2;
}
double mydouble(double tmpvalue)
{
return tmpvalue * 2;
}
}
int main()
{
cout << _nmsp1::mydouble(15) << endl;
int result2 = _nmsp1::mydouble<int>(16.9);
auto result3 = _nmsp1::mydouble<>(16.9)
auto result4 = _nmsp1::mydouble(16.9);
auto result5 = _nmsp1::mydouble<>(16.9);
reutrn 0;
}
重载
-
函数(函数模板)名字相同,但是参数数量或者参数类型上不同。 -
函数模板和函数也可以同时存在,此时可以把函数看成是一种重载,当普通函数和函数模板都比较合适的时候,编译器会优先选择普通函数来执行。 -
如果选择最合适(最特殊)的函数模板/函数,编译器内部有比较复杂的排序规则,规则也在不断更新。
namespace _nmsp1
{
template<typename T>
void myfunc(T tmpvalue)
{
cout << "myfunc(T tmpvalue)执行了" << endl;
}
template<typename T>
void myfunc(T* tmpvalue)
{
cout << "myfunc(T* tmpvalue)执行了" << endl;
}
void myfunc(int tmpvalue)
{
cout << "myfunc(int tmpvalue)执行了" << endl;
}
}
int main()
{
_nmsp1::myfunc(12);
char*p = nullptr;
_nmsp1::myfunc(p);
_nmsp1::mufunc(12.1);
}
特化
泛化(泛化版本):大众化的,常规的。常规情况下,写的函数模板都是泛化的函数模板。
特化(特化版本):往往代表着从泛化版本中抽出来的一组子集。
实例化:tfunc<const char*, int>
namespace _nmsp2
{
template <typename T,typename U>
void tfunc(T& tmprv, U& tmprv2)
{
cout << "tfunc泛化版本" << endl;
cout << tmprv << endl;
cout << tmprv2 << endl;
}
}
int main()
{
const char* p = "I love China!";
int i = 12;
_nmsp2::tfunc(p, i);
return 0;
}
- 全特化:就是把tfunc这个泛化版本中的所有模板参数都用具体的类型来代替构成的一个特殊的版本(全特化版本);
? 全特化实际上等价于实例化一个函数模板,并不等价于一个函数重载。
void tfunc<int ,double>(int& tmprv, double& tmprv2){......}
void tfunc(int& tmprv, double& tmprv2) { ...... }
? 编译器考虑的顺序:优先选择普通函数,然后才会考虑函数模板的特化版本,最后才会考虑函数模板的泛化版本。
namespace _nmsp2
{
template <typename T,typename U>
void tfunc(T& tmprv, U& tmprv2)
{
cout << "tfunc泛化版本" << endl;
cout << tmprv << endl;
cout << tmprv2 << endl;
}
template <>
void tfunc<int ,double>(int& tmprv, double& tmprv2)
{
cout << "---------------begin------------" << endl;
cout << "tfunc<int,double>特化版本" << endl;
cout << tmprv << endl;
cout << tmprv2 << endl;
cout << "---------------end------------" << endl;
}
void tfunc(int& tmprv, double& tmprv2)
{
cout << "---------------begin------------" << endl;
cout << "tfunc普通函数" << endl;
cout << "---------------end------------" << endl;
}
}
int main()
{
int k = 12;
double db = 15.8;
_nmsp2::tfunc(k, db);
return 0;
}
namespace _nmsp2
{
template <typename T,typename U>
void tfunc(T& tmprv, U& tmprv2)
{
cout << "tfunc泛化版本" << endl;
cout << tmprv << endl;
cout << tmprv2 << endl;
}
template <typename U>
void tfunc(double& tmprv, U& tmprv2)
{
cout << "---------------begin------------" << endl;
cout << "类似于tfunc<double, U>偏特化的tfunc重载版本" << endl;
cout << tmprv << endl;
cout << tmprv2 << endl;
cout << "---------------end------------" << endl;
}
template <typename T, typename U>
void tfunc(const T& tmprv, U& tmprv2)
{
cout << "tfunc(const T& tmprv, U& tmprv2)重载版本" << endl;
}
}
int main()
{
const int k2 = 12;
_nmsp2::tfunc(k2, db);
return 0;
}
后续讲解类模板时,对于类模板,还是存在模板参数范围上的偏特化以及数量上的偏特化,后续会详细讲解。
缺省参数
namespace _nmsp1
{
int mf(int tmp1, int tmp2)
{
return 1;
}
int mf2(int tmp1, int tmp2)
{
return 10;
}
typedef int(*FunType)(int, int);
template <typename T,typename F = FunType>
void testfunc(T i, T j, F funcpoint = mf)
{
cout << funcpoint(i, j) << endl;
}
template <typename T = int, typename U>
void testfunc2(U m)
{
T tmpvalue = m;
cout << tmpvalue << endl;
}
}
int main()
{
_nmsp1::testfunc(15, 16, _nmsp1::mf2);
_nmsp1::testfunc2(12);
return 0;
}
非类型模板参数
-
基本概念
-
前面的函数模板涉及到 模板参数都是 “类型 模板参数”需要用typename(class)来修饰。 -
模板参数还可以是 “非类型模板参数(普通的参数)” -
c++17开始,支持非类型模板参数为auto类型。 指定非类型模板参数的值时,一般给的都是常量。 因为编译器在编译的时候就要能够确定非类型模板参数的值。并不是任何类型的参数都可以作为非类型模板参数。 int类型可以,但double,float或者类类型string等等可能就不可以,*不过double 这种指针类型可以。 一般允许做非类型模板参数的类型如下:可能不断增加
- 整型或者枚举型;
- 指针类型;
- 左值引用类型;
- auto或者decltype(auto);
- 可能还有其他类型,请自行总结;
namespace _nmsp2
{
template < typename T, typename U ,int val = 100>
auto Add(T tv1,U tv2)
{
return tv1 + tv2 + val;
}
template <double *p>
void mft()
{
cout << "mft()执行了" << endl;
}
double g_d = 1.3;
template <typename T, typename int value>
auto Add2()
{
return 100;
}
}
int main()
{
cout << _nmsp2::Add<float, float>(22.3f, 11.8f) << endl;
cout << _nmsp2::Add<float, float, 800>(22.3f, 11.8f) << endl;
return 0;
}
-
比较奇怪的语法
- 不管类型还是非类型模板参数,如果代码中没有用到,则参数名可以省略。
- 类型前面可以增加一个
typename 修饰以明确标识一个类型,一般跟模板有关的类型名前面是需要typename
namespace _nmsp2
{
template <typename T, typename int value>
auto Add2()
{
return 100;
}
}
|