• Hello,C++(7)函数模板和类模板


    函数模板和类模板

    1、介绍

    1)C++提供两种模板机制:函数模板、类模板

    2)作用:使类型参数化,又称参数模板

    使得程序(算法)可以从逻辑功能上抽象,将数据类型作为参数传递

    总结:

    • 模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。
    • 模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

    所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。

    凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。

    在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

    2、函数模板

    1)应用场景:函数的业务逻辑一样,但函数的参数类型不一样

      函数模板的本质:类型参数化

    2)语法

    函数模板声明形式 :
    template
    < 类型形式参数表 > 类型形式参数的形式为: typename T1 , typename T2 , …… , typename Tn 或 class T1 , class T2 , …… , class
    Tn
    函数模板定义:
    template    < 类型形式参数表 >
    类型 函数名 (形式参数表){
      语句序列
    }

    3)举例

    #include <iostream>
    using namespace std;
    
    //template 关键字告诉C++编译器 我要开始泛型了.你不要随便报错  
    //数据类型T 参数化数据类型
    template <typename T>
    void myswap(T &a, T &b)
    {
        T t;
        t = a;
        a = b;
        b = t;
    }
    
    void main()
    {
        //char a = 'c';
        
        int  x = 1;
        int     y = 2;
        myswap(x, y); //自动数据类型 推导的方式 (不常用,不太好用,看懂即可不推荐使用)
    
        float a = 2.0;
        float b = 3.0;
    
        myswap(a, b); //自动数据类型 推导的方式 
        myswap<float>(a, b); //显示类型调用 
    
        cout<<"hello..."<<endl;
        system("pause");
        return ;
    }

    4)注意

    每个模板函数前面都要加函数模板声明:template<class T>

    模板函数的调用方式:

    sortArray<char, int>(a, num); //显示类型调用 模板函数 <>

    3、函数模板遇上函数重载

    函数模板和普通函数在一起,调用规则:

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

             2  C++编译器优先考虑普通函数

             3 如果函数模板可以产生一个更好的匹配,那么选择模板

             4 可以通过空模板实参列表的语法限定编译器只通过模板匹配

    函数模板和普通函数的本质区别结论:

      1、函数模板不允许自动类型转化

      2、普通函数能够进行隐式的自动类型转换

    举例:

    #include "iostream"
    using namespace std;
    
    
    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)
    {
        cout<<"T Max(T a, T 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);
    }
    
    
    void main()
    {
        int a = 1;
        int b = 2;
    
        cout<<Max(a, b)<<endl; //当函数模板和普通函数都符合调用时,优先选择普通函数
        cout<<Max<>(a, b)<<endl; //若显示使用函数模板,则使用<> 类型列表
    
        cout<<Max(3.0, 4.0)<<endl; //如果 函数模板产生更好的匹配 使用函数模板
    
        cout<<Max(5.0, 6.0, 7.0)<<endl; //重载
    
        cout<<Max('a', 100)<<endl;  //调用普通函数 可以隐式类型转换 
        system("pause");
        return ;
    }

    结论: 函数模板不提供隐式的数据类型转换,必须是严格的匹配,而普通函数可以隐式转换数据类型。

    4、函数模板小结

    模板函数进行编译时,编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。

    编译器并不是把函数模板处理成能够处理任意类型的函数,编译器从函数模板通过具体类型产生不同的函数。

    我们使用函数模板,根据具体类型的参数化,就能适用于不同类型的变量交换,达到了代码复用的效果。

    下边来深入理解下函数模板:

        - 对于函数模板中使用的类型不同,编译器会产生不同的函数

        - 编译器会对函数模板进行两次编译

        - 第一次是对函数模板本身进行编译,包括语法检查等(生成函数体)

        - 第二次是对参数替换后的代码进行编译,这就相当于编译普通函数一样,进行类型规则检查等。(根据类型生成函数头)

    需要注意的是

        - 函数模板是不允许隐式类型转换的,调用时类型必须严格匹配

    5、类模板

    类模板与函数模板的定义和使用类似,当有两个或多个类,其功能是相同的,仅仅是数据类型不同,

    如下面语句声明了一个类:

    • 类模板用于实现类所需数据的类型参数化
    • 类模板在表示如数组、表、图等数据结构显得特别重要,

     这些数据结构的表示和算法不受所包含的元素类型的影响

    举例:

    //类的类型参数化 抽象的类
    //单个类模板
    template<typename T>
    class A 
    {
    public:
        A(T t)
        {
            this->t = t;
        }
    
        T &getT()
        {
            return t;
        }
    protected:
    public:
        T t;
    };
    void main()
    {
       //模板中如果使用了构造函数,则遵守以前的类的构造函数的调用规则
        A<int>  a(100); 
        a.getT();
        printAA(a);
        return ;
    }

    6、继承中的类模板语法

    示例:

    //结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int> 
    //
    class B : public A<int>//(需要调用父类的构造函数,所以用参数列表来做)
    {
    public:
        B(int i) : A<int>(i)
        {
    
        }
        void printB()
        {
            cout<<"A:"<<t<<endl;
        }
    protected:
    private:
    };
    
    //模板与上继承
    //怎么样从基类继承  
    //若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数
    void pintBB(B &b)
    {
        b.printB();
    }
    void printAA(A<int> &a)  //类模板做函数参数 
    { 
         //
        a.getT();
    }
    
    void main()
    {
        A<int>  a(100); //模板类中如果使用了构造函数,则遵守以前的类的构造函数的调用规则 
        a.getT();
        printAA(a);
    
        B b(10);
        b.printB();
    
    
        cout<<"hello..."<<endl;
        system("pause");
        return ;
    }

    类模板有三种方式:

    • 类模板语法知识体系梳理

    • 所有的类模板函数写在类的内部

    • 所有的类模板函数写在类的外部,在一个cpp中

      • 这种方式下:Main文件里要包含.cpp需要把所有.h和.cpp文件放在同一个项目里,main文件要进行两次编译。

    尽量用1、3方式。

    注意三个地方具体化:
    1、参数具体化加<T>

    2、返回值具体化加<T>

    3、函数名 要不要加类的域名作用符

                  

     注意:声明友元函数的时候:friend ostream& operator<< <T> (ostream &out, Complex<T> &c3) ;

    全局函数不要加类的域名作用符,例如operator<<是全局函数

    友元函数本来就不是类里面的,是全局函数 不需要加域名作用符

     

    结论:

    友元函数只用来进行左移友移操作符重载。

    而且重载<<>>操作符只能用友元函数,其他运算符重载都要用成员函数

    不要滥用友元函数

                 

    8、总结

    归纳以上的介绍,可以这样声明和使用类模板:

    1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。
    
    2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。
    
    3) 在类声明前面加入一行,格式为:
    
        template <class 虚拟类型参数>
    
    如:
    
        template <class numtype> //注意本行末尾无分号
    
        class Compare
    
        {…}; //类体
    
    4) 用类模板定义对象时用以下形式:
    
        类模板名<实际类型名> 对象名;
    
        类模板名<实际类型名> 对象名(实参表列);
    
    如:
    
        Compare<int> cmp;
    
        Compare<int> cmp(3,7);
    
    5) 如果在类模板外定义成员函数,应写成类模板形式:
    
       template <class 虚拟类型参数>
    
       函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

    9、关于类模板的几点说明:

    1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

        template <class T1,class T2>

        class someclass

        {…};

    在定义对象时分别代入实际的类型名,如:

        someclass<int,double> obj;

    2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

    3) 类模板可继承

    10、类模板中的static关键字

    • 从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员
    •  和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化
    •  每个模板类有自己的类模板的static数据成员副本

    示例:

    template<typename T>
    class TA
    {
    public:
        static T a;
    public:
        TA()
        {
    
        }
    };
    template<typename T>
    T TA<T>::a = 10;
    
    int main()
    {
        //说明static属于的是具体类型的类
        TA<int> Oa;
        Oa.a += 10;
        cout << Oa.a << endl;
        TA<int> Ob;
        cout << Ob.a << endl;
    
        TA<char> Ca;
        cout << (int)Ca.a << endl;
    
        return 0;
    }

    结论:

    static属于类模板实例化的模板类。实例化的模板类有多个,则static成员分别属于多个模板类。即模板类有自己的static成员,互不干扰。

  • 相关阅读:
    golang的reflect
    minium-介绍
    selenium+Node.js在windows下的配置和安装
    Jmeter-逻辑控制器之Switch控制器(Switch Controller)
    Jmeter-逻辑控制器之Foreach
    chrome插件-YSlow 一个使用的web性能测试插件
    jmeter
    Jmeter-从数据库中获取数据并作为变量传输
    Jmeter-无法启动,'findstr'不是内部或外部命令,也不是可运行的程序
    Jmeter-响应结果unicode转成中文显示
  • 原文地址:https://www.cnblogs.com/juanjuanduang/p/10864059.html
Copyright © 2020-2023  润新知