一、简介
该篇主要内容为介绍函数模板以及函数模板的基本用法。
二、编程环境
系统:Windows11
IDE:Visual Studio2022
项目名字:funcTemp
三、内容
1.基本范例
1.1函数模板的作用
举例说明:编程过程中,数据类型千奇百怪,仅表示数字的常见的就有int,float,double等多种类型。这些数字在数学中,都可以进行相加,相减,相乘,相除,多次方运算等等多种运算模式。但是在计算机中,并不能一概而论,每种不同的数据类型,计算机都很较真的做了区分,int和int进行计算,double和double进行计算。拿减法来说,就会产生以下的代码。
int Sub(int a, int b)
{
return a - b;
}
double Sub(double a, double b)
{
return a - b;
}
如上面代码所示,一个减法,却要写出两个函数,如果是更复杂的运算,函数内容更多,几十行甚至上百行代码,达成同一个目的,仅仅因为类型不同,就需要重写一遍函数,就会造成代码冗余,不好看,使得工程变得非常庞大。为了解决这个问题所以我们需要模板。
1.2函数模板的举例
接着上面的例子,减法运算,我们可以用一个函数解决
template
T Sub(T a, T b)
{
return a - b;
}
调用时,类型T会被编译器自动的,识别为所给参数的类型。
调用举例
int y = Sub(5, 3);
调用验证,使用VS自带的工具,搜索Developer Command Prompt for vs2022,打开后进入命令行,进入到工程编译的文件夹路径下。会生成一个funcTemp.obj的文件。使用dumpbin命令来验证调用过程中,编译器自动识别类型T的正确性。
dumpbin /all funcTemp.obj > funcTemp.txt
就会在该文件夹下生成一个funcTemp.txt文件。查看funcTemp.txt文件。搜索函数名,就可以看到,sub()函数,自动的把T类型识别为了int类型
COMDAT; sym= "int __cdecl Sub(int,int)" (??$Sub@H@@YAHHH@Z)
1.3函数模板说明
1.template :表示函数模板的开始,是C++中的关键字
2.typename:typename修饰的是类型T,语法规定的写法。也可以用class代替,不过我的习惯是函数模板中用typename,在类模板中用class。
3.T:T表示一个类型,可以是任意的,也可以是X,Y,L等等
4.函数体中,与普通函数类似,只需要主要如果是T类型的,就一定写T就好。
template
T Sub(T a, T b)
{
return a - b;
}
1.4多个类型的函数模板使用
上面介绍的内容中,T要么是指代了int要么是指代了double,如果是int-double呢,或者int+double+int呢。所以函数模板也可以“传入多个T类型”。简单的例子如下。
template
T Sub(T a, U b, K c)
{
return a - b - c;
}
2.模板函数的重载
普通函数可以重载,只要参数数量不同,参数类型不同,即可完成重载。与函数重载非常类似。下面用代码说明。
template
void myFunc(T a)
{
std::cout << "myFunc(T a) is done" << std::endl;
}
template
void myFunc(T* a)
{
std::cout << "myFunc(T* a) is done" << std::endl;
}
上面模板函数完成了重载,第一个传入的是T类型的参数,第二个传入的是T*类型参数。参数类型不同,就是一个重载的(模板)函数。
3.特化
3.1.全特化
模板函数属于是泛化,具有不确定性,广泛性的模式,与之相反的,全特化,就是确定的,指定的模式。
写一个模板函数
template
void myFunc(T* a , U& b)
{
std::cout << "myFunc(T* a , U& b) is done" << std::endl;
}
这里的T与U都是不确定的,在调用的时候,编译器自动编译确定类型。
下面写一个全特化版本
template<>
void myFunc(char* a , int& b)
{
std::cout << "myFunc(char* a , int& b) is done" << std::endl;
}
这里注意看,<>里面是空的!,函数体里确定了,第一个参数是char*类型,第二个参数是int& 类型。但是要知道的是,看起来挺像重载,但记住,全特化不等于重载。全特化只是实例化了一个函数模板。
3.2.偏特化
在模板函数中,偏特化并不方便实现,只能通过重载的方式来实现,但是很少会在实际开发中使用到,这里不过多说明(真的用不到,类里面才会比较方便使用)。
四、注意点
1.不要让模板函数有不确定的类型
注意看以下代码。
template
V Add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << Add(2 , 3.5) << std::endl;
return 0;
}
编译时会报错,原因是返回类型V并不能被编译器推断出来。改进的方法有两种,我比较倾向于第二种。
方法一:指定返回值类型,并且将返回值类型放到第一位,注意对比上面的代码,V被放到了第一位,调用的时候也显式的指出了V的类型。
template
V Add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << Add(2 , 3.5) << std::endl;
return 0;
}
方法二:使用auto关键字。这样返回值类型就不用管传入的类型,让编译器自己去推断。会更加方便,我比较倾向于这种方式。
template
auto Add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << Add(2 , 3.5) << std::endl;
return 0;
}
2.使用隐式转换,改变传入的参数
在一些开发过程中,传入的参数可能不是我们想要的参数,总会多一些烦人的小数点,我们可以用隐式转换,改变传入的参数类型,下面函数,传入的类型为double类型,我们不想要2.3,我们只要2,就必须显式的指出传入类型。
template < typename U>
auto MySquare(U a)
{
return a * a;
}
int main()
{
std::cout << MySquare(2.3) << std::endl;
std::cout << MySquare(2.3) << std::endl;
return 0;
}
3.函数调用的优先级
一个程序中,可以有函数模板,可以特化,可以有功能相同的普通函数,那么编译器会优先调用哪一个类型的函数呢?
//模板函数
template
void Temp(T a)
{
std::cout << "temp is run" << std::endl;
}
//全特化
template <>
void Temp(int a)
{
std::cout << "spacial temp is run" << std::endl;
}
//功能相同的普通函数
void Temp(int a)
{
std::cout << "general func is run" << std::endl;
}
int main()
{
Temp(8);
return 0;
}
看结果,是普通函数被调用了,我们把普通函数屏蔽了之后,是特化的模板函数被调用了。所以可以得出结论,函数调用的顺序是普通函数 > 特化的模板函数 > 泛化的模板函数
五、总结
模板函数的基础使用方法,基本上就这么多了,如果有错误欢迎指正。这是一些看王建伟老师编写的《C++新经典 模板与泛型编程》*一书过程中的理解。欢迎交流。