【C++ 系列笔记】04 C++ 泛型编程

泛型编程

模板泛型的实现,泛型模板的思想。

泛型的本质是类型的参数化。

函数模板

函数模板

  • 基本语法

    template<class T>
    void function(T param1, T param2){
        // ...
    }
    
    template<typename T>
    void function(T param1, T param2){
        // ...
    }
    

    <class T><typename T>没有区别。

    注意!template<class/typename T>声明仅对紧跟着的代码块有效。

  • 调用方式

    • 自动类型推导

      int a; int b;
      
      function(a, b);
      // 没问题
      
      int a; char b;
      
      function(a, b);
      // 报错
      
    • 显示指定类型

      function<int>(a, b);
      

    注意!不传参时必须显式指定类型,看下面的例子。

    template<class T>
    void function();
    
    int main(){
        function();// 此时编译器无法自动推导,必须显式指定
    }
    
  • 函数模板与普通函数的区别

    • 类型检查

      模板函数类型检查更严格,不允许隐式转换,例子:

      template<class T>
      T templateFun(T param1, T param2);
      
      int function2(int param1, int param2);
      
      int main(){
          int a; char b;
      	// 调用模板函数不允许隐式转换
          templateFun(a, c); // 报错
          
      	// 调用普通函数会发生隐式转换
          function2(a, c); // 没问题
      }
      

      不过,右值传参是允许隐式转换的,例如:

      templateFun(1, 2); // 这是合法的
      
  • 调用顺序

    重载时,优先调用普通函数

    template<class T>
    T function(T param1, T param2);
    
    int function(int param1, int param2);
    
    int main(){
        int a; int b;
        function(a, b); // 报错
    }
    

    如果想要强制调用模板函数,可以显式指定空模板参数

    function<>(a, b);
    

    如果模板函数拥有更好的参数匹配,则会调用模板函数

    template<class T>
    T function(T param1, T param2);
    
    int function(int param1, int param2);
    
    int main(){
        char a; char b;
        // 模板函数拥有更少的隐式转换,所以这里会匹配模板函数
        function(a, b);
    }
    
  • 模板函数允许重载

    template<class T>
    T function(T param1, T param2);
    
    template<class T>
    T function(T param1, T param2, T param3);
    

模板实现机制

  • 函数模板通过具体类型产生不同的函数

  • 二次编译:

    首先检测函数模板的语法错误并编译。然后生成对应的模板函数,再次编译。

模板具体化

  • 局限性

    函数模板具有一定的局限性,

    例如算数运算:

    对于数组,不存在 arr1 + arr2 这种语法。

    若是对象 obj1 + obj2,则需要提供相应的重载函数。

  • 解决

    函数模板的具体化:

    template<class T>
    void function(T a){
        // ... 模板实现
    }
    
    // 具体化
    template<> void function<Type>(Type& a){
        // ... 该模板函数的具体实现
    }
    

    即针对某些特定类型进行重载。

类模板

  • 基本语法

    template<class type1, class type2>
    class Type {
       private:
        type1 __a;
        type2 __b;
       public:
        Type(type1 a, type2 b);
    }
    

    注意!与函数模板不同,实例化对象时必须显式指定数据类型。

    Type<int, char> obj(1, '2');
    
  • 默认参数

    类模板可以使用类型的默认参数。

    template<class type1, class type2 = char>
    

    当然,函数模板也可以使用默认参数,只不过没什么用。

  • 其余与函数模板基本相同

类模板的类外实现

代码:

template<class type1, class type2>
class Type {
   private:
    type1 __a;
    type2 __b;
   public:
    Type(type1 a, type2 b);
    void method(type1 a);
};

// 类外实现
template<class type1, class type2>
Type<type1, type2>::Type(type1 a, type2 b){
    // ...
}

template<class type1, class type2>
void Type<type1, type2>::method(type1 a){
    // ...
}

类模板的分文件编写

首先要了解一点,类模板的方法函数在编译阶段不会生成,他在运行时生成。

我们调库的一般方式是这样的:

#include"Type.h"
int main(){
    Type obj;
    obj.method();
}

会报错,无法解析的外部命令,即在链接阶段找不到对应的函数去调用。

因为此时还未创建具体的类模板方法函数,当然找不到。

此时可以这样做:

#include"Type.cpp"
int main(){
    Type obj;
    obj.method();
}

不过,正常情况下,我们一般对类模板不做分文件编写,将声明和实现放在一个 .h文件中。

类模板实例做函数参数

模板实例内部的数据类型不确定,想作为参数传递,可以在参数列表里直接指定,或作为一个函数模板去定义。

  • 指定类型

    指定只允许传入此参数类型的实例:

    void function(Type<int, string> obj);
    
  • 模板函数

    允许此模板类的任何实例传入:

    template<class T1, class T2>
    void function(Type<T1, T2> obj);
    
  • 完全抽象

    其实这个方法不用说也都知道:

    template<class T>
    void function(T obj);
    

类模板与友元

  • 类内实现

    注意!虽然是类内实现,但其作用域仍在全局。

    template<class type1, class type2>
    class Type {
        friend void function(Type<type1, type2>& obj){
            // ...
        }
       private:
        type1 __a;
        type2 __b;
    };
    
  • 类外实现

    首先看错误实现:

    template<class type1, class type2>
    class Type {
        friend void function(Type<type1, type2>& obj);
       private:
        type1 __a;
        type2 __b;
    };
    
    template<class type1, class type2>
    void function(Type<type1, type2>& obj){
            // ...
    }
    

    此时若调用该函数,则报错无法解析的命令,即没有找到该函数的实现。

    由于类内声明是一个普通函数,而类外实现是一个函数模板,而当参数列表相同时,编译器会优先调用普通函数,从而导致了找不到函数实现,链接出错。

    两种方法:

    • 声明模板函数

      此时将类内的友元声明声明为一个模板函数即可。

      template<class type1, class type2>
      class Type {
          template<class type1, class type2>
          friend void function(Type<type1, type2>& obj);
         private:
          type1 __a;
          type2 __b;
      };
      
      template<class type1, class type2>
      void function(Type<type1, type2>& obj){
              // ...
      }
      
    • 函数模板具体化

      或者先声明该模板函数,然后类内声明友元,并进行参数的具体化。

      // 声明
      template<class type1, class type2> class Type<type1, type2>
      template<class type1, class type2> void function(Type<type1, type2>& obj);
      
      template<class type1, class type2>
      class Type {
          // 参数具体化声明
          friend void function<>(Type<type1, type2>& obj);
         private:
          type1 __a;
          type2 __b;
      };
      // 类外具体化实现
      template<class type1, class type2>
      void function(Type<type1, type2>& obj){
              // ...
      }
      

模板中的继承

错误示例:

不允许继承抽象的类

template <class T>
class Base {};

// 继承
class Type : public Base {};

要继承,可以抽象,也可以具体。

  • 具体制定要继承的模板类

    template <class T>
    class Base {};
    
    // 继承
    class Type : public Base<int> {};
    
  • 抽象指定

    template <class T>
    class Base {};
    
    // 继承
    template <class T>
    class Type : public Base<T> {};
    
posted @ 2020-06-11 00:06  高厉害  阅读(145)  评论(0编辑  收藏  举报