zoukankan      html  css  js  c++  java
  • C++解析(26):函数模板与类模板

    0.目录

    1.函数模板

    2.类模板

    3.小结

    1.函数模板

    1.1 函数模板与泛型编程

    C++中有几种交换变量的方法?
    交换变量的方法——定义宏代码块 vs 定义函数

    • 定义宏代码块
      1. 优点:代码复用,适合所有的类型
      2. 缺点:编译器不知道宏的存在,缺少类型检查
    • 定义函数
      1. 优点:真正的函数调用,编译器对类型进行检查
      2. 缺点:根据类型重复定义函数,无法代码复用

    C++中有没有解决方案集合两种方法的优点

    泛型编程的概念——不考虑具体数据类型的编程方式

    函数模板:

    • 一种特殊的函数可用不同类型进行调用
    • 看起来和普通函数很相似,区别是类型可被参数化

    函数模板的语法规则:

    • template关键字用于声明开始进行泛型编程
    • typename关键字用于声明泛型类型

    函数模板的使用:

    • 自动类型推导调用
    • 具体类型显示调用

    示例——使用函数模板:

    #include <iostream>
    
    using namespace std;
    
    template < typename T >
    void Swap(T& a, T& b)
    {
        T c = a;
        a = b;
        b = c;
    }
    
    template < typename T >
    void Sort(T a[], int len)
    {
        for(int i=0; i<len; i++)
        {
            for(int j=i; j<len; j++)
            {
                if( a[i] > a[j] )
                {
                    Swap(a[i], a[j]);
                }
            }
        }
    }
    
    template < typename T >
    void Println(T a[], int len)
    {
        for(int i=0; i<len; i++)
        {
            cout << a[i] << ", ";
        }
        
        cout << endl;
    }
    
    int main()
    {
        int a[5] = {5, 3, 2, 4, 1};
        
        Println(a, 5);
        Sort(a, 5);
        Println(a, 5);
        
        string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};
        
        Println(s, 5);
        Sort(s, 5);
        Println(s, 5);
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    5, 3, 2, 4, 1, 
    1, 2, 3, 4, 5, 
    Java, C++, Pascal, Ruby, Basic, 
    Basic, C++, Java, Pascal, Ruby,
    

    函数模板深入理解:

    • 编译器从函数模板通过具体类型产生不同的函数
    • 编译器会对函数模板进行两次编译
      1. 对模板代码本身进行编译
      2. 对参数替换后的代码进行编译

    注意事项:

    • 函数模板本身不允许隐式类型转换
      1. 自动推导类型时,必须严格匹配
      2. 显示类型指定时,能够进行隐式类型转换

    示例——编译器从函数模板通过具体类型产生不同的函数:

    #include <iostream>
    
    using namespace std;
    
    template < typename T >
    void Swap(T& a, T& b)
    {
        T c = a;
        a = b;
        b = c;
    }
    
    typedef void(FuncI)(int&, int&);
    typedef void(FuncD)(double&, double&);
    
    int main()
    {
        FuncI* pi = Swap; // 编译器自动推导 T 为 int
        FuncD* pd = Swap; // 编译器自动推导 T 为 double
        
        cout << "pi = " << reinterpret_cast<void*>(pi) << endl;
        cout << "pd = " << reinterpret_cast<void*>(pd) << endl;
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    pi = 0x40091e
    pd = 0x40094a
    

    可以看到,编译器通过函数模板产生了两个地址不同的实实在在的函数!

    1.2 多参数函数模板

    多参数函数模板——函数模板可以定义任意多个不同的类型参数

    对于多参数函数模板:

    • 无法自动推导返回值类型
    • 可以从左向右部分指定类型参数

    示例——多参数函数模板:

    #include <iostream>
    
    using namespace std;
    
    template 
    < typename T1, typename T2, typename T3 >
    T1 Add(T2 a, T3 b)
    {
        return static_cast<T1>(a + b);
    }
    
    int main()
    {
        // T1 = int, T2 = double, T3 = double
        int r1 = Add<int>(0.5, 0.8);
    
        // T1 = double, T2 = float, T3 = double
        double r2 = Add<double, float>(0.5, 0.8);
    
        // T1 = float, T2 = float, T3 = float
        float r3 = Add<float, float, float>(0.5, 0.8);
    
        cout << "r1 = " << r1 << endl;     // r1 = 1
        cout << "r2 = " << r2 << endl;     // r2 = 1.3
        cout << "r3 = " << r3 << endl;     // r3 = 1.3
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    r1 = 1
    r2 = 1.3
    r3 = 1.3
    

    1.3 函数重载遇上函数模板

    函数重载遇上函数模板会发生什么?

    函数模板可以像普通函数一样被重载:

    • C++编译器优先考虑普通函数
    • 如果函数模板可以产生一个更好的匹配,那么选择模板
    • 可以通过空模板实参列表限定编译器只匹配模板

    示例——重载函数模板:

    #include <iostream>
    
    using namespace std;
    
    template < typename T >
    T Max(T a, T b)
    {
        cout << "T Max(T a, T b)" << endl;
        
        return a > b ? a : b;
    }
    
    int Max(int a, int b)
    {
        cout << "int Max(int a, int b)" << endl;
        
        return a > b ? a : b;
    }
    
    template < typename T >
    T Max(T a, T b, T c)
    {
        cout << "T Max(T a, T b, T c)" << endl;
        
        return Max(Max(a, b), c);
    }
    
    int main()
    {
        int a = 1;
        int b = 2;
        
        cout << Max(a, b) << endl;          // 普通函数 Max(int, int)
        cout << Max<>(a, b) << endl;        // 函数模板 Max<int>(int, int)
        cout << endl;
        
        cout << Max(3.0, 4.0) << endl;      // 函数模板 Max<double>(double, double)
        cout << Max(5.0, 6.0, 7.0) << endl; // 函数模板 Max<double>(double, double, double)
        cout << endl;
        
        cout << Max('a', 100) << endl;      // 普通函数 Max(int, int)
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    int Max(int a, int b)
    2
    T Max(T a, T b)
    2
    
    T Max(T a, T b)
    4
    T Max(T a, T b, T c)
    T Max(T a, T b)
    T Max(T a, T b)
    7
    
    int Max(int a, int b)
    100
    

    2.类模板

    2.1 类模板

    在C++中是否能够将泛型的思想应用于类

    类模板:

    • 一些类主要用于存储和组织数据元素
    • 类中数据组织的方式和数据元素的具体类型无关
    • 如:数组类,链表类,Stack类,Queue类,等

    C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能。

    C++中的类模板:

    • 以相同的方式处理不同的类型
    • 在类声明前使用template进行标识
    • <typename T>用于说明类中使用的泛指类型 T

    类模板的应用:

    • 只能显示指定具体类型,无法自动推导
    • 使用具体类型<Type>定义对象

    • 声明的泛指类型 T 可以出现在类模板的任意地方
    • 编译器对类模板的处理方式和函数模板相同
      1. 从类模板通过具体类型产生不同的类
      2. 在声明的地方对类模板代码本身进行编译
      3. 在使用的地方对参数替换后的代码进行编译

    示例——类模板:

    #include <iostream>
    
    using namespace std;
    
    template < typename T >
    class Operator
    {
    public:
        T add(T a, T b) { return a + b; }
        T minus(T a, T b) { return a - b; }
        T multiply(T a, T b) { return a * b; }
        T divide(T a, T b) { return a / b; }
    };
    
    string operator-(string& l, string& r)
    {
        return "Minus";
    }
    
    int main()
    {
        Operator<int> op1;
        
        cout << op1.add(1, 2) << endl;
        
        Operator<string> op2;
        
        cout << op2.add("Hello", "World") << endl;
        cout << op2.minus("Hello", "World") << endl;
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    3
    HelloWorld
    Minus
    

    类模板的工程应用:

    • 类模板必须在头文件中定义
    • 类模板不能分开实现在不同的文件中
    • 类模板外部定义的成员函数需要加上模板<>声明

    示例——模板类的工程应用:

    // Operator.h
    #ifndef _OPERATOR_H_
    #define _OPERATOR_H_
    
    template < typename T >
    class Operator
    {
    public:
        T add(T a, T b);
        T minus(T a, T b);
        T multiply(T a, T b);
        T divide(T a, T b);
    };
    
    template < typename T >
    T Operator<T>::add(T a, T b)
    {
        return a + b;
    }
    
    template < typename T >
    T Operator<T>::minus(T a, T b)
    {
        return a - b;
    }
    
    template < typename T >
    T Operator<T>::multiply(T a, T b)
    {
        return a * b;
    }
    
    template < typename T >
    T Operator<T>::divide(T a, T b)
    {
        return a / b;
    }
    
    #endif
    
    // test.cpp
    #include <iostream>
    #include "Operator.h"
    
    using namespace std;
    
    int main()
    {
        Operator<int> op1;
        
        cout << op1.add(1, 2) << endl;
        cout << op1.multiply(4, 5) << endl;
        cout << op1.minus(5, 6) << endl;
        cout << op1.divide(10, 5) << endl;
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    3
    20
    -1
    2
    

    2.2 多参数类模板与特化

    类模板可以定义任意多个不同的类型参数

    类模板可以被特化:

    • 指定类模板的特定实现
    • 部分类型参数必须显示指定
    • 根据类型参数分开实现类模板

    类模板的特化类型:

    • 部分特化——用特定规则约束类型参数
    • 完全特化——完全显示指定类型参数

    示例——类模板的特化:

    #include <iostream>
    
    using namespace std;
    
    template
    < typename T1, typename T2 >
    class Test
    {
    public:
        void add(T1 a, T2 b)
        {
            cout << "void add(T1 a, T2 b)" << endl;
            cout << a + b << endl;
        }
    };
    
    template
    < typename T1, typename T2 >
    class Test < T1*, T2* > // 关于指针的特化实现
    {
    public:
        void add(T1* a, T2* b)
        {
            cout << "void add(T1* a, T2* b)" << endl;
            cout << *a + *b << endl;
        }
    };
    
    template
    < typename T >
    class Test < T, T > // 当 Test 类模板的两个类型参数完全相同时,使用这个实现
    {
    public:
        void add(T a, T b)
        {
            cout << "void add(T a, T b)" << endl;
            cout << a + b << endl;
        }
        void print()
        {
            cout << "class Test < T, T >" << endl;
        }
    };
    
    template
    <  >
    class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时
    {
    public:
        void add(void* a, void* b)
        {
            cout << "void add(void* a, void* b)" << endl;
            cout << "Error to add void* param..." << endl;
        }
    };
    
    int main()
    {  
        Test<int, float> t1;
        Test<long, long> t2;
        Test<void*, void*> t3;
        
        t1.add(1, 2.5);
        cout << endl;
        
        t2.add(5, 5);
        t2.print();
        cout << endl;
        
        t3.add(NULL, NULL);
        cout << endl;
        
        Test<int*, double*> t4;
        int a = 1;
        double b = 0.1;
        
        t4.add(&a, &b);
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    void add(T1 a, T2 b)
    3.5
    
    void add(T a, T b)
    10
    class Test < T, T >
    
    void add(void* a, void* b)
    Error to add void* param...
    
    void add(T1* a, T2* b)
    1.1
    

    类模板特化注意事项:

    • 特化只是模板的分开实现
      1. 本质上是同一个类模板
    • 特化类模板的使用方式是统一的
      1. 必须显示指定每一个类型参数

    2.3 特化的深度分析

    类模板特化与重定义有区别吗?函数模板可以被特化吗?

    重定义和特化的不同:

    • 重定义
      1. 一个类模板和一个新类(或者两个类模板)
      2. 使用的时候需要考虑如何选择的问题
    • 特化
      1. 以统一的方式使用内模板和特化类
      2. 编译器自动优先选择特化类

    函数模板只支持类型参数完全特化:

    示例——函数模板完全特化与函数重载:

    #include <iostream>
    
    using namespace std;
    
    template
    < typename T >
    bool Equal(T a, T b)
    {
        cout << "bool Equal(T a, T b)" << endl;
        
        return a == b;
    }
    
    template
    < >
    bool Equal<double>(double a, double b)
    {
        const double delta = 0.00000000000001;
        double r = a - b;
        
        cout << "bool Equal<double>(double a, double b)" << endl;
        
        return (-delta < r) && (r < delta);
    }
    
    bool Equal(double a, double b)
    {
        const double delta = 0.00000000000001;
        double r = a - b;
        
        cout << "bool Equal(double a, double b)" << endl;
        
        return (-delta < r) && (r < delta);
    }
    
    int main()
    {  
        cout << Equal( 1, 1 ) << endl;
        cout << Equal( 0.001, 0.001 ) << endl;
        cout << Equal<>( 0.001, 0.001 ) << endl;
        
        return 0;
    }
    

    运行结果为:

    [root@bogon Desktop]# g++ test.cpp
    [root@bogon Desktop]# ./a.out 
    bool Equal(T a, T b)
    1
    bool Equal(double a, double b)
    1
    bool Equal<double>(double a, double b)
    1
    

    工程中的建议:
    当需要重载函数模板时,优先考虑使用模板特化当模板特化无法满足需求,再使用函数重载!

    3.小结

    • 函数模板是泛型编程在C++中的应用方式之一
    • 函数模板能够根据实参对参数类型进行推导
    • 函数模板支持显示的指定参数类型
    • 函数模板是C++中重要的代码复用方式
    • 函数模板通过具体类型产生不同的函数
    • 函数模板可以定义任意多个不同的类型参数
    • 函数模板中的返回值类型必须显示指定
    • 函数模板可以像普通函数一样被重载
    • 泛型编程的思想可以应用于类
    • 类模板以相同的方式处理不同类型的数据
    • 类模板非常适用于编写数据结构相关的代码
    • 类模板在使用时只能显示指定类型
    • 类模板可以定义任意多个不同的类型参数
    • 类模板可以被部分特化完全特化
    • 特化的本质是模板的分开实现
    • 函数模板只支持完全特化
    • 工程中使用模板特化代替类(函数)重定义
  • 相关阅读:
    正确使用 Volatile 变量
    什么叫持久化?
    大型J2EE项目中的Web容器集群–Nginx+Glasshfish+Memcached+ServletFilter
    REST
    Java多线程设计模式:wait/notify机制
    Java Persistence API (JPA) 的陷阱
    JDK1.5新特性介绍
    用Amazon EC2搭建免费WordPress博客及SSH
    PDF Split and Merge Basic 好用的PDF合并分割工具
    路威机器人
  • 原文地址:https://www.cnblogs.com/PyLearn/p/10093126.html
Copyright © 2011-2022 走看看