美文网首页
【C++拾遗】详解C++中的函数模板

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

作者: 超级超级小天才 | 来源:发表于2019-08-12 21:19 被阅读0次

    这是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-温故知新】四函数/函数模板

    相关文章

      网友评论

          本文标题:【C++拾遗】详解C++中的函数模板

          本文链接:https://www.haomeiwen.com/subject/gevpjctx.html