【C++拾遗】详解C++中的函数模板

函数模板的作用是允许我们使用泛型定义函数,然后根据具体的数据类型替换泛型。通过将类型作为参数传递给模板,可以是编译器生成该类的函数。

比如要交换两个变量的值,这时候我们写好了一个交换两个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-温故知新】四函数/函数模板

发布了44 篇原创文章 · 获赞 46 · 访问量 9167

猜你喜欢

转载自blog.csdn.net/qq_38962621/article/details/99340751
今日推荐