zoukankan      html  css  js  c++  java
  • 类模板机制

    1. 基本概念

       是对一批仅仅成员数据类型不同的类的抽象,程序员只要为这一批类所组成的整个类家族创建一个类模板,给出一套程序代码,就可以用来生成多种具体的类。

       总结以下两点:

       1)类模板用于实现类所需数据的类型参数化。

       2)类模板在表示如数组、表、图等数据结构显得特别重要,这些数据结构的表示和算法不受所包含的元素类型的影响。

       类模板基本语法举例:

    // 参数化一个类型T,允许参数化多个类型
    template<typename T>
    class A 
    {
    public:
        A(T t) { this->t = t; }
        T &getT() { return t; }
    
    public:
        T t;
    };
    
    // 子类从模板类继承的时候,既可以派生类模板,也可以派生非模板类
    // 1. 可以从类模板派生出非模板类,在派生中,作为非模板类的基类,必须是类模板实例化后的模板类
    class B : public A<int>
    {
    public:
        B(int i) : A<int>(i) {}
        void printB() { cout << "A:" << t << endl; }
    };
    
    // 2. 从类模板派生类模板可以从类模板派生出新的类模板
    template <class T>
    class C : public A<T>
    {
    public:
    	C(T t) : A<T>(t) {}	
    };
    
    int main()
    {
        A<int> a(100);  // 需要提供类型参数
        int x = a.getT();
        B b(10);
        b.printB();
        C<int> c(10);
        return 0;
    }
    

    2. 模板的编译过程

       什么是编译单元:一个编译单元(translation unit)是指一个.cpp文件以及它所#include的所有.h文件,.h文件里的代码将会被扩展到包含它的.cpp文件里,

                       然后编译器编译该.cpp文件为一个.obj文件,并且本身包含的就已经是二进制码,但是不一定能够执行,因为并不保证其中一定有main函数。

       什么是分离式编译:一个项目由若干个源文件共同实现,而每个源文件(.cpp)单独编译成目标文件(.obj),最后将所有目标文件连接起来形成单一的可执行文件的过程。

       下面举一个例子来说明:

       test.h文件内容如下

    void func(); // 声明一个函数 func

      test.cpp文件内容如下:

    #include "test.h"
    
    //这里实现出 test.h 中声明的 func 函数
    void func()
    {
        … // do something
    }
    

      main.cpp文件内容如下:

    #include "test.h"
    
    int main()
    {
        func(); // 调用func,func具有外部连接类型
        return 0;
    }

      说明:test. cpp和main.cpp各自被编译成不同的.obj文件,在main.cpp中,调用了func函数,然而当编译器编译main.cpp时,它仅仅知道的只是

            main.cpp中所包含的test.h文件中的一个关于void func();的声明,所以,编译器将这里的f看作外部连接类型,func的实现代码实际存在

            于test.cpp所编译成的test.obj中。在main.obj中对f的调用只会生成一行call指令,链接器负责在其它的.obj中寻找func的实现代码,找

            到以后将call func这个指令的调用地址换成实际的func的函数进入点地址。

       然而,对于模板,模板函数的代码其实并不能直接编译成二进制代码,其中要有一个“实例化”的过程。举个例子(将模板的声明和实现分离):

       test.h文件内容如下:

    template<class T>
    class A
    {
    public:
        void func(); // 这里只是个声明
    };
    

       test.cpp文件内容如下:

    #include "test.h"
    
    template<class T>
    void A<T>::func()  // 模板的实现
    {
        …//do something
    }
    

      main.cpp文件内容如下:

    #include "test.h"
    
    int main()
    {
        A<int> a;
        a.func();   // #1
        return 0;
    }

      说明:编译器在#1处并不知道A<int>::f的定义,因为它不在test.h里面,于是编译器只好寄希望于链接器,希望它能够在其他.obj里面找到A<int>::func的实例,

            在本例中就是test.obj,然而,后者中真有A<int>::func的二进制代码吗?NO!!!因为C++标准明确表示,当一个模板不被用到的时侯它就不该被实例化出来,

            test.cpp中用到了A<int>::func了吗?没有!!所以实际上test.cpp编译出来的test.obj文件中关于A::f一行二进制代码也没有,于是链接器就傻眼了,只好给

            出一个链接错误。但是,如果在test.cpp中写一个函数,其中调用A<int>::func,则编译器会将其实例化出来,链接器就能够完成任务。

       模板的二次编译

           1)非模板类在编译的时候就会被实例化出代码,但编译模板类则不是如此,C++标准明确表示当一个模板不被用到的时侯它就不该被实例化出来。

           2)当编译到用模板类特例定义对象的代码时,如A<int> a; 此时编译器才会生成对应实例化类的二进制代码,即第二次编译。

           3)第二次编译的时候如果该编译单元能访问到模板类的实现代码,则好说,否则只能等到链接,如果其它模块也没有实例化过该代码,则会链接出错。

       解决办法

           1)模板类声明在test.h中,定义在main.cpp中,调用在main.cpp中,则能运行成功。

           2)模板类声明在test.h中,定义在test.h中,调用在main.cpp中,则能运行成功,因为在预处理阶段会对头文件展开。

       这里抛出一个问题:假如多个.cpp都定义了相同类型的对象,那编译器会在每个编译单元都会产生相同的代码吗?

       答案:NO,编译器肯定不会那么蠢的,具体实现细节暂时不表。

    3. 类模板中的友元函数

       1)普通非模板类或函数的友元声明,将友元关系授予明确指定的类或函数。

    template<class T>
    class A
    {   
        friend void func();  
        friend class B; 
    private:    
        T _a;
    };
    
    void func()
    {   
        A<int> a1;     // func()函数内可访问类A的任意实例
        A<double> a2;   
        a1._a = 1;    
        a2._a = 1.0;  
        cout << a1._a << endl;
        cout << a2._a << endl;
    }
    

         func可访问A任意类实例中的私有和保护成员

       2)类模板或函数模板的友元声明,授予对友元所有实例的访问权。

    template<typename T>
    class A
    {   
        template<class T1>  
        friend void func();
    
    private:    
        T _a;
    }; 
    
    template<typename T1>
    void func()
    {   
        A<int> a1;  
        A<double> a2;
        a1._a = 1;    
        a2._a = 1.0;  
        cout << a1._a << endl;  
        cout << a2._a << endl;
    } 
    
    void functest()
    {   
        func<int>();    // func任意实例可访问类A的任意实例
        func<double>();
    }
    

        我们模板类A中声明了模板函数func,两者拥有各自的模板形参T和T1,两者是互不影响的,所以在

          这里对于func的所有实例(如func<int>,func<double>)对于A的所有实例(如A<int>,A<double>)中的私有或保护成员都可以进行访问。

       3)只授予对类模板或函数模板的特定实例的访问权的友元声明。

    template<typename T>
    class A
    {   
        friend void func<int>();
    
    private:    
        T _a;
    }; 
    
    template<typename T1>
    void func()
    {   
        A<int> a1;  
        A<double> a2;   
        a1._a = 1;    
        a2._a = 1.0;  
        cout << a1._a << endl;
        cout << a2._a << endl;
    }
    
    void functest()
    {   
        func<int>(); 
        // func<double>();
    } 
    
    int main()
    {   
        functest();  
        return 0;
    }
    

         在模板类A中我们声明了模板函数func实例化后的fun<int>的友元关系,因此我们在这里仅能够在func<int>中对模板类A中

         的所有实例的私有或保护成员具有访问权限,而对于func<double>则不是模板类A的友元函数,不具有访问其实例的私有或保护成员的权限(编译会出错)。

       4)对于3)的例子可以做一点修改

    template<typename T>
    class A
    {   
        friend void func<T>();  // 这里是一个泛化的T类型
    
    private:    
        T _a;
    };
    
    template<typename T1>
    void func()
    {   
        A<int> a1;  
        A<double> a2;
        a1._a = 1;    
        a2._a = 1.0;  
        cout << a1._a << endl;  
        cout << a2._a << endl;
    }
    
    void functest()
    {   
        func<int>(); 
        func<double>();
    } 
    
    int main()
    {   
        functest();  
        return 0;
    }
    

          我们发现对于func<int>仅对模板类A的实例A<int>的私有或保护成员具有访问权限,对于A<double>则没有;而对于func<double>则反之。

            总之,对于func的实例只对与它的模板实参一致的A实例有友元关系。

  • 相关阅读:
    centos vsftpd
    centos nginx
    linux 修改配色
    面试题讲解
    文件操作
    Python
    Python-linux作业
    python(12.17)笔记
    python周末作业(12.14--16)
    python作业(12.12)
  • 原文地址:https://www.cnblogs.com/yanghh/p/12945885.html
Copyright © 2011-2022 走看看