• C++函数模板


    12.1 C++函数模板

    12.1.1 函数模板定义

    函数模板是泛型函数描述;也就是说,它根据泛型类型定义了一个函数,特定类型(如int或double)可以被替换。通过将类型作为参数传递给模板,可以使编译器为该特定类型生成一个函数。因为模板允许您根据泛型类型而不是特定类型编程,所以这个过程有时被称为泛型编程。因为类型是由参数表示的,所以模板特性有时被称为参数化类型。

    //理论上来说typename和class都是可以的,但是考虑到向后兼容性和敲出更短的词汇,建议使用class
    template <typename AnyType>
    void Swap(AnyType &a, AnyType &b)
    {
        AnyType temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    template <class AnyType>
    void Swap(AnyType &a, AnyType &b)
    {
        AnyType temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    12.1.2 函数模板举例

    12.1.2.1 code
    // funtemp.cpp -- using a function template
    #include 
    // function template prototype
    template <typename T> // or class T
    void Swap(T &a, T &b);
    int main()
    {
    	using namespace std;
        int i = 10;
        int j = 20;
        cout << "i, j = " << i << ", " << j << ".\n";
        cout << "Using compiler-generated int swapper:\n";
        Swap(i,j); // generates void Swap(int &, int &)
        cout << "Now i, j = " << i << ", " << j << ".\n";
        double x = 24.5;
        double y = 81.7;
        cout << "x, y = " << x << ", " << y << ".\n";
        cout << "Using compiler-generated double swapper:\n";
        Swap(x,y); // generates void Swap(double &, double &)
        cout << "Now x, y = " << x << ", " << y << ".\n";
        // cin.get();
        return 0;
    }
    // function template definition
    template <typename T> // or class T
    void Swap(T &a, T &b)
    {
        T temp; // temp a variable of type T
        temp = a;
        a = b;
        b = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    当函数调用到下面这句时,系统自动生成函数void Swap(int &a, int &b),将其存储在函数的位置,你不会看到它,但是系统会自动生成。

    Swap(i,j); // generates void Swap(int &, int &)
    
    • 1
    12.1.2.2 运行结果
    i, j = 10, 20.
    Using compiler-generated int swapper:
    Now i, j = 20, 10.
    x, y = 24.5, 81.7.
    Using compiler-generated double swapper:
    Now x, y = 81.7, 24.5.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    12.1.3 函数模板注意事项

    一般情况下函数模板放到头文件中。

    12.1.4 重载的模板

    算法不同且参数个数or种类(数组、结构体、class)不同时,由于算法不同,单靠模板就无法解决全部问题,因此需要函数模板重载。

    举例:

    12.1.4.1 code
    // twotemps.cpp -- using overloaded template functions
    #include 
    template <typename T> // original template
    void Swap(T &a, T &b);
    template <typename T> // new template
    void Swap(T *a, T *b, int n);
    void Show(int a[]);
    const int Lim = 8;
    int main()
    {
        using namespace std;
        int i = 10, j = 20;
        cout << "i, j = " << i << ", " << j << ".\n";
        cout << "Using compiler-generated int swapper:\n";
        Swap(i,j); // matches original template
        cout << "Now i, j = " << i << ", " << j << ".\n";
        int d1[Lim] = {0,7,0,4,1,7,7,6};
        int d2[Lim] = {0,7,2,0,1,9,6,9};
        cout << "Original arrays:\n";
        Show(d1);
        Show(d2);
        Swap(d1,d2,Lim); // matches new template
        cout << "Swapped arrays:\n";
        Show(d1);
        Show(d2);
        // cin.get();
        return 0;
    }
    template <typename T>
    void Swap(T &a, T &b)
    {
        T temp;
        temp = a;
        a = b;
        b = temp;
    }
    template <typename T>
    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;
        }
    }
    void Show(int a[])
    {
        using namespace std;
        cout << a[0] << a[1] << "/";
        cout << a[2] << a[3] << "/";
        for (int i = 4; i < Lim; i++)
        cout << a[i];
        cout << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    12.1.4.2 运行结果
    i, j = 10, 20.
    Using compiler-generated int swapper:
    Now i, j = 20, 10.
    Original arrays:
    07/04/1776
    07/20/1969
    Swapped arrays:
    07/20/1969
    07/04/1776
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    12.1.5 函数模板局限性

    对于这个模板
    template <class T> // or template 
    void f(T a, T b)
    {...}
    
    如果a和b是数组或结构体或class,那么下面的语句是不允许的:
    a = b;
    if (a > b)
    T c = a*b;
    在这种情况下就要重载操作符才可以实现,这个内容会在后面介绍到。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    12.1.6 显式具体化

    算法不同且参数个数种类相同

    假设定义了如下的结构体:

    struct job
    {
        char name[40];
        double salary;
        int floor;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    假设定义了job的a,b变量,想要交换两者的salary和floor但不交换name,由于其参数数据类型和下面这个函数一致,所以无法使用重载函数模板解决问题,而要寻找别的办法—他就叫显式具体化。

    template <typename T>
    void Swap(T &a, T &b)
    {
        T temp;
        temp = a;
        a = b;
        b = temp;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    12.1.6.1 第三代具体化 (ISO/ANSI C++标准)

    1.对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。

    2.显式具体化的原型和定义应以template<>打头,并通过名称来指出类型。

    3.具体化优先于常规函数,非模板函数优先于具体化和常规函数。

    12.1.6.2 显式具体化举例
    //这两个都行
    template <> void Swap(job &, job &); // simpler form
    template <> void Swap<job>(job &j1, job &j2);
    
    • 1
    • 2
    • 3
    12.1.6.2.1 code
    // twoswap.cpp -- specialization overrides a template
    #include 
    template <typename T>
    void Swap(T &a, T &b);
    struct job
    {
        char name[40];
        double salary;
        int floor;
    };
    
    // explicit specialization
    template <> void Swap<job>(job &j1, job &j2);
    void Show(job &j);
    int main()
    {
        using namespace std;
        cout.precision(2);
        cout.setf(ios::fixed, ios::floatfield);
        int i = 10, j = 20;
        cout << "i, j = " << i << ", " << j << ".\n";
        cout << "Using compiler-generated int swapper:\n";
        Swap(i,j); // generates void Swap(int &, int &)
        cout << "Now i, j = " << i << ", " << j << ".\n";
        job sue = {"Susan Yaffee", 73000.60, 7};
        job sidney = {"Sidney Taffee", 78060.72, 9};
        cout << "Before job swapping:\n";
        Show(sue);
        Show(sidney);
        Swap(sue, sidney); // uses void Swap(job &, job &)
        cout << "After job swapping:\n";
        Show(sue);
        Show(sidney);
        // cin.get();
        return 0;
    }
    
    template <typename T>
    void Swap(T &a, T &b) // general version
    {
        T temp;
        temp = a;
        a = b;
    	b = temp;
    }
    
    // swaps just the salary and floor fields of a job structure
    template <> void Swap<job>(job &j1, job &j2) // specialization
    {
        double t1;
        int t2;
        t1 = j1.salary;
        j1.salary = j2.salary;
        j2.salary = t1;
        t2 = j1.floor;
        j1.floor = j2.floor;
        j2.floor = t2;
    }
    
    void Show(job &j)
    {
        using namespace std;
        cout << j.name << ": $" << j.salary
        << " on floor " << j.floor << endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    12.1.6.2.2 运行结果
    i, j = 10, 20.
    Using compiler-generated int swapper:
    Now i, j = 20, 10.
    Before job swapping:
    Susan Yaffee: $73000.60 on floor 7
    Sidney Taffee: $78060.72 on floor 9
    After job swapping:
    Susan Yaffee: $78060.72 on floor 9
    Sidney Taffee: $73000.60 on floor 7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    12.1.7 实例化和具体化

    12.1.7.1 实例化

    编译器使用模板为特定类型生成函数定义时,得到的是函数实例。

    12.1.7.2 隐式实例化

    函数调用Swap(i,j) 导致编译器使用int类型生成一个Swap()实例。模板不是函数定义,但是使用int类型的模板实例是函数定义。

    程序根据数据类型推断要生成哪种函数定义,所以叫隐式实例化。

    12.1.7.3 显式实例化
    12.1.7.3.1 格式

    命令编译器创建特定的实例化。

    template void Swap<int>(int, int); // explicit instantiation
    //Use the Swap() template to generate a function definition for the int type.
    
    • 1
    • 2
    12.1.7.3.2 显式实例化与显式具体化

    比较与显式具体化的不同在于省略了<>:显式具体化在警告编译器不要使用函数模板为生成函数定义,取而代之的是,使用专门为int类型显式地定义地函数定义。

    template <> void Swap<int>(int &, int &); // explicit specialization
    template <> void Swap(int &, int &); // explicit specialization
    
    • 1
    • 2

    注意事项:试图在同一个文件中使用同一种类型地显式实例化和显式具体化将出错。

    12.1.7.3.3 使用函数创建显式实例化

    在程序中使用函数来创建显式实例化:

    template <class T>
    T Add(T a, T b) // pass by value
    {
    	return a + b;
    }
    ...
    int m = 6;
    double x = 10.2;
    cout << Add<double>(x, m) << endl; // explicit instantiation
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这个模板与Add(x, m)不匹配,因为该模板要求两个函数参数的类型相同。但通过使用Add(x, m),可强制为double类型实例化,并将参数m强制转换为double类型,以便与函数Add(double, double) 的第二个参数匹配。

    显式实例化有什么作用:就是编辑时可检查,出现错误能够及时修改。

    12.1.7.4 显式具体化、隐式实例化、显式实例化举例
    ...
    template <class T>
    void Swap (T &, T &); // template prototype
    template <> void Swap<job>(job &, job &); // explicit specialization for job
    int main(void)
    {
        template void Swap<char>(char &, char &); // explicit instantiation for char
        short a, b;
        ...
        Swap(a,b); // implicit template instantiation for short
        job n, m;
        ...
        Swap(n, m); // use explicit specialization for job
        char g, h;
        ...
        Swap(g, h); // use explicit template instantiation for char
        ...
    }
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    12.1.8 编译器选择使用哪个函数版本?

    12.1.8.1 重载解析
    • 1.创建候选函数列表,其中包含与被调用函数名称相同的函数和模板。
    • 2.使用候选函数列表创建可行函数列表,这些都是参数数目正确的函数,为此有一个隐式转换序列,其中包括实参类型与相应的形参类型完全匹配的情况。比如使用float参数的函数调用可以将该参数转换为double,从而与double形参匹配,而模板可以为float生成一个实例。
    • 3.确定是否有最佳的可行函数。如果有,则使用它,否则该函数调用出错。

    Eg:为下面这个函数调用匹配函数:

    may('B'); // actual argument is type char
    
    • 1

    可选项有下面这些:

    void may(int); // #1
    float may(float, float = 3); // #2
    void may(char); // #3
    char * may(const char *); // #4
    char may(const char &); // #5
    template<class T> void may(const T &); // #6
    template<class T> void may(T *); // #7
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    首先是要排除4和7的,因为类型不匹配。

    其他的优先级从好到坏的排序按以下规则:

    1. 完全匹配,但常规函数优先于模板。
    2. 提升转换(int和short自动转换为int,float自动转换为double)
    3. 标准转换(int转换为char,long转换为double)
    4. 用户定义的转换(类声明中定义的转换)

    Function #1比Function #2要好因为char-to-int is a promotion,但是char-to-float is a standard conversion.Functions #3, #5, and #6比Functions #1 or #2要好,因为Functions #3, #5, and #6是绝对匹配的。Both #3 and #5 are better than #6 because #6 is a template.

    通常有两个完全匹配是一种错误,目前函数3,5无法确定谁胜出,因此要更深入讨论。

    12.1.8.2 完全匹配和最佳匹配

    Type (argument-list)表示实参的函数名与用作形参的函数指针只要返回类型和参数列表相同,就是匹配的。

    img

    //针对这个:
    struct blot {int a; char b[10];};
    blot ink = {25, "spots"};
    ...
    recycle(ink);
    //这些都是exact matches
    void recycle(blot); // #1 blot-to-blot
    void recycle(const blot); // #2 blot-to-(const blot)
    void recycle(blot &); // #3 blot-to-(blot &)
    void recycle(const blot &); // #4 blot-to-(const blot &)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
      1. 指向非const数据的指针和引用优先与非const指针和引用参数匹配。也就是说,如果只定义了函数#3和#4是完全匹配的,则将选择#3,因为ink没有被声明为const.
      1. const和非const之间的区别只适用于指针和引用指向的数据。也就是说,如果只定义了#1和#2,则将出现二义性错误。
      1. 一个是非模板函数,一个是模板函数;非模板函数优先于模板函数(包括显式具体化)。
      1. 如果两个完全匹配的函数都是模板函数,则较具体的模板函数优先;显式具体化优先于模板隐式生成的具体化。
    struct blot {int a; char b[10];};
    template <class Type> void recycle (Type t); // template
    template <> void recycle<blot> (blot & t); // specialization for blot
    ...
    blot ink = {25, "spots"};
    ...
    recycle(ink); // use specialization
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    术语最具体化并不一定意味着明确的具体化;更普遍的是,它表明,当编译器推断出什么类型时,发生的转换更少。

    template <class Type> void recycle (Type t); // #1
    template <class Type> void recycle (Type * t); // #2
    
    • 1
    • 2

    对于

    struct blot {int a; char b[10];};
    blot ink = {25, "spots"};
    ...
    recycle(&ink); // address of a structure
    
    • 1
    • 2
    • 3
    • 4

    函数1(解释为recycle(blot *))和函数2(解释为recycle(blot *))都可以,recycle(blot *) and recycle(blot *)。我们认为函数2更具体,因为它发生的转换更少。

    12.1.8.3 寻找最具体化template—部分排序规则举例
    12.1.8.3.1 code
    // tempover.cpp -- template overloading
    #include 
    template <typename T> // template A
    void ShowArray(T arr[], int n);
    template <typename T> // template B
    void ShowArray(T * arr[], int n);
    struct debts
    {
        char name[50];
        double amount;
    };
    int main()
    {
        using namespace std;
        int things[6] = {13, 31, 103, 301, 310, 130};
        struct debts mr_E[3] =
        {
            {"Ima Wolfe", 2400.0},
            {"Ura Foxe", 1300.0},
            {"Iby Stout", 1800.0}
        };
        double * pd[3];
        // set pointers to the amount members of the structures in mr_E
        for (int i = 0; i < 3; i++)
       		pd[i] = &mr_E[i].amount;
        cout << "Listing Mr. E's counts of things:\n";
        // things is an array of int
        ShowArray(things, 6); // uses template A
        cout << "Listing Mr. E's debts:\n";
        // pd is an array of pointers to double
        ShowArray(pd, 3); // uses template B (more specialized)
        return 0;
    }
    template <typename T>
    void ShowArray(T arr[], int n)
    {
        using namespace std;
        cout << "template A\n";
        for (int i = 0; i < n; i++)
        	cout << arr[i] << ' ';
        cout << endl;
    }
    template <typename T>
    void ShowArray(T * arr[], int n)
    {
        using namespace std;
        cout << "template B\n";
        for (int i = 0; i < n; i++)
        	cout << *arr[i] << ' ';
        cout << endl;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    12.1.8.3.2 运行结果
    Listing Mr. E's counts of things:
    template A
    13 31 103 301 310 130
    Listing Mr. E's debts:
    template B
    2400 1300 1800
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    12.1.8.3.3 重载解析总结

    重载解析将寻找最匹配的函数。如果只存在一个这样的函数,则选择它;如果存在多个这样的函数,则其中只有一个是非模板函数,则选择该函数;如果存在多个适合的函数,且他们都为模板函数,则其中有一个函数比其他函数更具体,则选择该函数。如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他函数更具体,则函数调用将是不确定的,因此是错误的;当然,如果不存在匹配的函数,则也是错误。

    12.1.8.4 自己决定

    就是引导编译器做出我需要的决定。下面这种代码是允许的,让我有点意外。

    12.1.8.4.1 code
    // choices.cpp -- choosing a template
    #include 
    template<class T> // or template 
    T lesser(T a, T b) // #1
    {
        return a < b ? a : b;
    }
    int lesser (int a, int b) // #2
    {
        a = a < 0 ? -a : a;
        b = b < 0 ? -b : b;
        return a < b ? a : b;
    }
    int main()
    {
        using namespace std;
        int m = 20;
        int n = -30;
        double x = 15.5;
        double y = 25.9;
        cout << lesser(m, n) << endl; // use #2
        cout << lesser(x, y) << endl; // use #1 with double
        cout << lesser<>(m, n) << endl; // use #1 with int
        cout << lesser<int>(x, y) << endl; // use #1 with int
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    12.1.8.4.2 运行结果
    20
    15.5
    -30
    15
    
    D:\Prj\C++\Template_Makeing_Your_Own_Choice\Debug\Template_Makeing_Your_Own_Choice.exe (进程 9904)已退出,代码为 0。
    要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。
    按任意键关闭此窗口. . .
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

  • 相关阅读:
    同三维T80004EHL-W-4K30 4K HDMI编码器,支持WEBRTC协议
    亚马逊云科技Build On学习心得
    C语言达到什么水平才能从事单片机工作
    SpringBoot笔记:SpringBoot集成MyBatis实战
    VSCode打开 C(嵌入式) 工程的一些记录
    初识manim
    集合—Vector底层结构和源码分析
    四十五、ORM相关
    SVN+Gitee配置版本控制库
    4.TCP UDP简单介绍
  • 原文地址:https://blog.csdn.net/weixin_44410704/article/details/127983116