函数模板特化是 C++ 中一项非常重要的编程技巧。这篇文章,我们讲解下函数模板特化相关知识,主要讲解以下三个知识点:
- 函数模板特化概念
- 函数模板特化语法
- 函数模板特化和函数重载
以下代码运行环境为:win10 专业版 + vs2019 社区版。
- 函数模板特化概念 {#title-0}
在 C++ 中,由于数据类型的不同,我们通常要编写函数重载,使具有相同逻辑的代码适应不同的数据类型。请看下面的示例代码:
bool is_equal(int a, int b)
{
return (a == b);
}
bool is_equal(double a, double b)
{
return (a == b);
}
bool is_equal(char a, char b)
{
return (a == b);
}
bool is_equal(int *a, int *b)
{
return (*a == *b);
}
这种场景下,明显代码量较大,模板技术能够使用更少的代码来解决此问题,请看下面使用函数模板的示例代码:
template<class T>
bool is_equal(T a, T b)
{
return (a == b);
}
我们可以使用虚拟类型来代替具体的类型,从而得到一个泛化的函数,该函数可以针对 int、double、char 类型进行正确的比较。
虽然函数模板非常优秀,但是,如果 T 的类型是 int* 类型,根据我们函数模板的实现规则(a == b)就变成了两个指针地址之间的比较,而不是将指针指向数据的比较。
此时,我们应该怎么解决该问题呢?
我们就需要针对该模板提供一个针对指针类型的特殊版本,这就叫做函数模板特化。
- 函数模板特化语法 {#title-1}
我们如何针对某一个函数模板编写一个特化版本呢?请看下面的示例代码:
template<class T>
bool is_equal(T a, T b)
{
return (a == b);
}
template<>
bool is_equal<int*>(int *a, int *b)
{
return (*a == *b);
}
第 2 行的函数模板叫做泛化版本,针对大部分的数据类型。
第 8 行的函数模板叫做特化版本,针对某一具体的数据类型。
注意:函数模板特化版本的模板参数为空参数列表(一对尖括号,其中什么都不填写),并且函数名后面要使用尖括号写上该特化版本是针对哪种数据类型的特化版本。
请继续看下面的代码:
template<class T>
bool is_equal(T a, T b)
{
return (a == b);
}
template<>
bool is_equal<int*>(int *a, int *b)
{
return (*a == *b);
}
void test()
{
int a = 10, b = 20;
is_equal(a, b);
int* p1 = &a, * p2 = &b;
is_equal(p1, p2);
}
当发生第 16 行的函数调用时,会调用泛化版本的 is_equal 函数。
当发生第 19 行的函数调用时,会调用特化版本的 is_equal 函数。
- 函数模板特化和函数重载 {#title-2}
我们知道函数模板和普通函数可以以重载的形式共存,如下代码所示:
template<class T>
bool is_equal(T a, T b)
{
return (a == b);
}
bool is_equal(int *a, int *b)
{
return (*a == *b);
}
同学可能开始疑问了,函数模板和普通函数重载,不是优先调用普通函数。如果普通函数不能产生更好匹配,才会调用函数模板。这也可以解决 int* 类型的比较,为什么还要写一个函数模板特化呢?或者说,此处的函数模板特化和普通函数重载有什么区别呢?
普通函数重载:即使该函数不会被调用,代码仍然会存在一份函数示例。也就是说,从始至终,我们都没有进行 int* 类型的数据比较,该函数的代码也会一直存在。
函数模板特化:我们知道函数模板只有被调用时才会产生函数示例,所以,只有特化版本的函数模板被调用时,编译器才会生成具体的函数实例。
但是,从语法简洁上来说,函数重载形式确实比函数模板特化更加简洁。另外,对于类模板而言,是不存在重载的概念,所以针对某些需要特殊处理的数据类型只能通过类模板特化。
至此,关于函数模板特化的知识点讲解完毕,希望对你有帮助!