这是C++类一些重点难点知识笔记的第 五 篇,同专题的其他文章可以移步:https://www.jianshu.com/nb/39156122
函数模板的作用是允许我们使用泛型定义函数,然后根据具体的数据类型替换泛型。通过将类型作为参数传递给模板,可以是编译器生成该类的函数。
比如要交换两个变量的值,这时候我们写好了一个交换两个int值的函数,又需要一个交换两个double值的函数,我们需要将第一个函数重复一遍,然后再将其中的int替换成double,是非常复杂的。尤其是当一些算法可以应用于很多数据类型时,我们无法对每一种数据类型都写一套算法函数,这就时函数模板解决的问题。
模板函数的定义与使用
我们可以先定义一个模板,然后用具体的数据类型替换,像下边这样:
template<typename T>;
void swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
要建立一个模板,必须先使用template
语法来定义一个模板变量T,这里也可以用旧的声明方式:
template<class T>;
很多代码库都使用class
开发的,这样的上下文中,两个关键词完全相同,如果不考虑向后兼容并且不介意输入长单词的话,使用typename
而不使用class
调用模板函数时,和其他函数类似,编译器会根据我们传入的函数类型自行生成一个对应的函数,这个函数我们是看不到的,但是编译器会直接在背后生成并为我们使用好,例如调用下边的语句时:
int i = 1;
int j = 2;
swap(i, j);
这是,编译器会自动生成一个函数:使用int代替所有的T,从而完成相关的逻辑,这个函数我们看不到,但是没问题
void swap(int &a, int &b)
{
int temp;
temp = a;
a = b;
b = temp;
}
模板函数的重载
模板函数也是可以重载的,虽然泛型使我们合并解决了很多问题,但是有些数据类型是无法合并的。比如交换变量值的函数,我们可以将上述逻辑同时用于int、double、char、string等,但是却无法用于数组,因为数组需要将每个元素交换,于是可以使用函数模板的重载:
template<typename T>;
void swap(T &a, T &b)
{
T temp;
temp = a;
a = b;
b = temp;
}
void swap(T []a, T[]b, int n)
{
T temp;
for(int i = 0; i < n; i++)
{
temp = a[i];
a[i] = b[i];
b[i] = temp;
}
}
这样就可以用同名函数swap交换任意数据类型和数组类型了。
函数模板的局限性
定义模板函数要考虑好这个函数应用的数据类型的范围,有些操作对于数据类型是很有局限性的,比如 a>b 这种操作,只能用于 int、double 等,string和char显然无法使用
显式具体化
假如定义了一个结构:
struct myStruct
{
int myInt;
double myDouble;
}
这时我们同样可以通过上面的swap函数交换两个struct的值,因为结构体是可以直接被赋给另一个结构体的。但是如果我们只想交换两个结构体的myInt变量,而不想交换myDouble变量呢,就无法使用上述模板函数了,但是由于这种情况下我们传入的参数还是和上述模板函数相同的(两个待交换的T),所以重载无法达到这个愿望。这就用到了显式具体化(explicit specialization)。
显示具体化是我们可以提供一个具体化的函数定义,其中包含这个特殊的处理情况下的代码,当编译器找到了与函数调用匹配的具体化定义时,就不再使用模板,而是用该具体化的定义:
- 对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及它们的重载函数四个版本
- 显式具体化的原型和定义以
<template>
打头,并通过名称来指出类型 - 非模板函数优先于具体化和常规模板,具体化模板优先于常规模板
例如对于刚才的结构体,下面是用于交换逻辑的非模板函数、模板函数和具体化函数的原型:
// 非模板函数 #1
void swap(myStruct &, myStruct &);
// 模板函数 #2
template<typename T>
void swap(T &, T &);
// 模板具体化函数 #3
template <> void swap<myStruct>(myStruct &, myStruct &);
在上述三个函数原型同时存在时,#1优先于#3优先于#2。在具体化函数模板中,可以省略函数名后的<myStruct>
,因为参数类型已经表明了这是一个myStruct的具体化函数:
template <> void swap(myStruct &, myStruct &);
实例化与具体化
当我们定义了一个模板函数swap后,通过调用时传入了两个int值可以使得编译器在后台自动为我们实例化了一个int类型的函数,这个函数是编译中产生的,所以我们看不到,但是它确实是产生了,这个过程成为隐式实例化。
同时我们也可以进行显式实例化(explicit instantiation),即可以直接命令编译器创建特定的实例,比如一个处理int的swap函数,只需要这样声明它:
template void swap<int>(int, int);
这是要和显示具体化(explicit specialization)区分开的,具体化是在template
后还需要加一个<>
:
template < > void swap<int>(int, int);
template < > void swap(int, int);
在template
后有无<>
是区分显示具体化和显示实例化的重要标志。
通常的,隐式实例化、显式实例化和显示具体化都被称为具体化(specialization)
转载请注明出处,本文永久更新链接:https://blogs.littlegenius.xin/2019/08/12/【C-温故知新】四函数/函数模板
网友评论