zoukankan      html  css  js  c++  java
  • C++ Primer 第十六章 模板与范型编程

    16.1 模板定义
        模板和c#范型一样,建立一个通用的类或函数,其参数类型和返回类型不具体指定,用一个虚拟的类型来代表,通过模板化函数或类实现代码在的重用。
        定义语法是:
        template<typename 类型参数> 
      返回类型 函数名(模板形参表) 
      {
        函数体 
      }
     
      或 :
      template<class 类型参数> 
      返回类型 函数名(模板形参表) 
      { 
        函数体
      }

        template是一个声明模板的关键字,类型参数一般用T这样的标识符来代表一个虚拟的类型,当使用函数模板时,会将类型参数具体化。typename和class关键字作用都是用来表示它们之后的参数是一个类型的参数。只不过class是早期C++版本中所使用的,后来为了不与类产生混淆,所以增加个关键字typename。
        函数模板:

    template <typename T> //加法函数模板 
    T Add(T x,T y) 
    {
        return x+y; 
    }; 
     
    int main() 
    {
        int x=10,y=10;
        std::cout<<Add(x,y)<<std::endl;//相当于调用函数int Add(int,int)
     
        double x1=10.10,y1=10.10;
        std::cout<<Add(x1,y1)<<std::endl;//相当于调用函数double Add(double,double)


     
        long x2=9999,y2=9999;
        std::cout<<Add(x2,y2)<<std::endl;//相当于调用函数long Add(long,long)

    }

        template内可以定义多个类型形参,每个形参用,分割并且所有类型前面都要用typename修饰。
        template <typename T,typename Y> T Add(T x,Y y) ; // ok
        template <typename T,Y> T Add(T x,Y y) ; // 错误,Y之前缺少修饰符

        函数模板也可以声明inline 语法是 template <typename T,typename Y> inline T Add(T x,Y y) ;

        类模板:

    template <typename T,typename Y>
    class base
    {
        public:
            base(T a);
            Y Get();

        private:
             T s1
             T s2 
    };

    int main() 

       base<int,string> it(1,"name"); //  类后面的类型参数不能缺省

    }

        和函数模板不一样,类模板无法使用类型推断,所以定义对象时一定要显示传递类型参数。

        类型形参名称有自己的作用域:

    typedef stirng T; // 该T与下面的类型形参不会产生冲突,不过最好不要重名以免混淆
    template <typename T> 
     
    T Add(T x,T y) 
    {
        typedef stirng T; //
     错误,内部定义会产生名字冲突
        
    //... 

    };

        可以像申明一般函数或类一样声明(而不定义)。但类型形参不能省略 template <typename T,typename Y> class base ; 声明了一个类模板。

        模板类型参数可以用typename 或者class 来修饰,大部分情况下二者可以互换。但有一种特殊用方法时需要typename

    class base
    {
        public:
            class inbase{}; // 内部类

    };

    template <typename T> 
    void test()
    {
        typename T::inbase p; // 这时候必须要在前面加上typename,表示要定义一个类型为T类(T是类型参数)内部定义的inbase类对象

        T::inbase p; // 如果不加编译会报错,因为编译器认为T::inbase表示T类的静态成员inbase,所以这样书写语法是错误的
    }

        要注意,这种用法需要满足条件:类型形参T必须要定义内部类inbase 否则会编译错误。

        模板编程中还可以在类型形参列表中定义非类型形参,这时非类型形参会被当成常量

    template <typename T,int i> 
     
    T Add(T x) // Add(T x,int i) 这样定义编译错误,i 和非形参i名称冲突

    {
        return x + i;
    };
      
    int main() 

       Add<int,10>(5);
    }

     

        范型编程有两个重要原则:形参尽量使用const引用(防止拷贝),形参本身操作尽量少(传递一个不支持函数形参体操作的类型会报错)


    16.2 实例化
        函数模板可以定义函数指针并予以赋值

    template <typename T,typename Y> T Get(T x,Y y) ; // 声明函数
    int(*pr) (int,string) = Get ; // 定义函数指针并赋值
    pr(5,"str") ;  // 用函数指针调用函数无需解引,或者(*pr)(5,"str") ;

    函数模板指针作为形参时需注意重载情况。对二义性的调用要指定类型来消除

    template <typename T> T Get(T x) ; // 声明函数
    void fun(int (*) (int));
    void fun(string (*) (string));

    fun(Get); // 错误,有二义性,类型推断后重载的两个fun函数都能通过。

    fun(Get<int>); // 指定类型,消除了二义性


    16.3 模板编译模型

        [1] 当编译器看到模板定义的时候,它不立即产生代码。 只有在看到用到模板时 ,如调用了函数模板或定义了类模板的对象的时候,编译器才产生特定类型的模板实例 。
        [2] 一般而言,当调用函数的时候,编译器只需要看到函数的声明。类似地,定义类类型的对象时,类定义必须可用,但成员函数的定义不是必须存在的。因此,应该将类定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。
        [3] 模板则不同:要进行实例化,编译器必须能够访问定义模板的源代码。 当调用函数模板或类模板的成员函数的时候,编译器需要函数定义,需要哪些通常放在源文件中的代码。
        [4] 标准C++为编译模板代码定义了两种模型。 所有编译器都支持第一种模型,称为“包含”模型( inclusion compilation model) ;只有一些编译器支持第二种模型,“分别编译”模型( separate compilation model) 。
        [5] 在两种模型中,构造程序的方式很大程度上是相同的:类定义和函数声明放在头文件中,而函数定义和成员定义放在源文件中。两种模型的不同在于,编译器怎样使用来自源文件的定义 。
        [6] 在包含编译模型,编译器必须看到用到的所有模板的定义。一般而言,可以通过在声明函数模板或类模板的头文件中添加一条#include指示使定义可用,该#include引入了包含相关定义的源文件 。
        [7] 在分别编译模型中,编译器会为我们跟踪相关的模板定义。但是,我们必须让编译器知道要记住给定的模板定义,可以使用export关键字来做这件事 。export关键字能够指明给定的定义可能会需要在其他文件中产生实例化 。
        [8] 在一个程序中,一个模板只能定义为导出一次。 一般我们在函数模板的定义中指明函数模板为导出的 ,这是通过在关键字template之前包含export关键字而实现的。对类模板使用export更复杂一些 ,记得应该在类的实现文件中使用export,否者如果在头文件中使用了export,则该头文件只能被程序中的一个源文件使用。
        [9] 导出类的成员将自动声明为导出的。也可以将类模板的个别成员声明为导出的,在这种情况下,关键字export不在类模板本身指定,而是只在被导出的特定成员定义上指定。任意非导出成员的定义必须像在包含模型中一样对待:定义应放在定义类模板的头文件中。

    16.4 类模板成员
        普通类不但定义非模板函数成员,也能定义模板函数成员:

    class base
    {
        public:
            template<typename T> T Get(T a); // 模板函数成员申明

    };

    template<typename T> T base::Get(T a) //成员函数类外部定义

    {
        return a;
    }

        可这样调用:
        base obj ;
        obj.Get<int>(20) ;
        obj.Get("str") ;  // 类型推断,等价于obj.Get<string>("str") ;


        如果是模板类

    template<typename T>
    class base
    {
        public:
            template<typename Y> Y Get(Y a); // 模板函数成员申明

    };

    template<typename T> // 这一步不可少,确定T也是个模板类型参数

    template<typename Y> Y base<T>::Get(Y a)
    {
        return a;
    }

        可这样调用:
        base<string> obj ;
        obj.Get<int>(20) ;
        obj.Get("str") ; // 类型推断,等价于obj.Get<string>("str") ;

        类模板或函数模板可以作为其他类的友元,不过由于其特殊性可以做一些限制。

    template<typename T>
    class he
    {
        // ...

    }

    template<typename T>
    class base
    {
        template<typename Y> friend class he; // 表示所有类型的模板类对象都是友元

        friend class he<int>; // 表示只有int类型形参的模板类对象才是友元 
        friend class he<T>;   // 表示只有类型形参和base类型参数一致的模板类对象才是友元
    }

        友元函数和模板类情况相似。 第一种友元可以看做是完全申明,第二种和第三种友元则需要至少在base定以前有完全申明,否则会编译错误。

    16.5 一个范型句柄类
        如果对上一章句柄类有充分理解范型句柄类应该非常容易掌握。

    16.6 模板特化
        模板的特化(template specialization)分为两类:函数模板的特化和类模板的特化。
        函数模板的特化:当函数模板需要对某些类型进行特别处理,称为函数模板的特化。例如:

    bool IsEqual(T t1, T t2) 
    {
         return t1 == t2; 
    };

    int main()
    {
         char str1[] = "Hello";

         char str2[] = "Hello";

         cout << IsEqual(11) << endl;

         cout << IsEqual(str1, str2) << endl;   //输出0

     return 0;
     
    }

        最后一行比较字符串是否相等。由于对于传入的参数是char *类型的,IsEqual函数模板只是简单的比较了传入参数的值,即两个指针是否相等,因此这里打印0。显然,这与我们的初衷不符。因此,sEqual函数模板需要对char *类型进行特别处理,即特化:

    template <> bool IsEqual(char* t1, char* t2) // 函数模板特化
    {
        return strcmp(t1, t2) == 0;
    }


        这样,当IsEqual函数的参数类型为char* 时,就会调用IsEqual特化的版本,而不会再由函数模板实例化。

        类模板的特化:与函数模板类似,当类模板内需要对某些类型进行特别处理时,使用类模板的特化。例如:

    template <class T>
    class compare 
    {
      public:
        bool IsEqual(T t1, T t2)
        {
           return t1 == t2;
        }
    };   
     
    int main() 
    {
      char str1[] = "Hello";

      char str2[] = "Hello";

      compare<int> c1;

      compare<char *> c2;
      cout << c1.IsEqual(11) << endl; //比较两个int类型的参数


      cout << c2.IsEqual(str1, str2) << endl;   //比较两个char *类型的参数
      return 0
    }

        这里最后一行也是调用模板类compare<char*>的IsEqual进行两个字符串比较,显然这里存在的问题和上面函数模板中的一样,我们需要比较两个字符串的内容,而

    不是仅仅比较两个字符指针。因此,需要使用类模板的特化:

    template<>class compare<char *> //特化(char*) 
    {
      public:
         bool IsEqual(char* t1, char* t2)
         { 
            return strcmp(t1, t2) == 0;  //使用strcmp比较字符串

         }

    };

        注意:进行类模板的特化时,需要特化所有的成员变量及成员函数。

  • 相关阅读:
    Linux进程管理及while循环(转)
    AT5661-[AGC040C]Neither AB nor BA【模型转换】
    CF573D-Bear and Cavalry【动态dp】
    关于专人整理和分析需求
    Codeforces 1005D Polycarp and Div 3
    [Luogu]P5513 [CEOI2013]Board
    IDEA Mybatis 中文数据添加到MySQL,显示乱码
    如何比较两个word文档的差异
    抗体计算设计
    抗体
  • 原文地址:https://www.cnblogs.com/kingcat/p/2514939.html
Copyright © 2011-2022 走看看