C++ primer 第十六章笔记 初稿

来源:互联网 发布:清华北大抢人 知乎 编辑:程序博客网 时间:2024/06/13 03:34

16.1 模板定义

  1. typename 关键字在模板被广泛使用后才引入,尽管指示更加清楚,但class仍然被许多人使用

  2. 非类型模板参数

    • 要求:可以是一个整型/指针/左值引用,但必须是常量或有静态生存期(针对指针和引用)

    • 指明参数是一个数组:
      如果函数定义为如下形式,当传入字符串常量时,T会默认转换成字符数组

      template<class T,int N>void print(T r[N]) {...}

      如果想使用,则必须显示提供字符串长度,未达到最初目的

      int array[6] = {1, 2, 3, 4, 5, 6};print<int, 6>(array);

      可以将函数定义修改为如下形式,显示说明传递数组

      template<class T,int N>void print(T (&r)[N]) {...}

      这样就可以直接使用

      int array[6] = {1, 2, 3, 4, 5, 6};print(array);
  3. 两个重要原则

    • 函数参数应当是const引用(可以传递不能拷贝的对象)
    • 条件判断一般都使用<运算符
  4. 模板若想生成一个实例,必须已知定义。

    注:使用模板的函数声明或使用模板定义一个指针或引用,都不是实例化

  5. 定义在类模板之内的函数,被隐式定义为内联函数(inline)

  6. 一般在模板类外定义的函数,形式如下

    template<typename T> ret-type class_name<T>:: f(parm_list)
  7. 模板类的成员函数只有在使用时才被实例化,因此即使一个实例不完全支持模板的所有操作,也可以使用该模板

  8. 一个省略操作:在模板类中,返回类型若为类本身,则无需提供实参 < T > (不提供反而很习惯)

  9. 模板类的友元

    • 一个类若包含非模板友元,则该友元必然可以访问模板的所有实例

    • 如果友元也是模板,其访问权限由模板类授权决定

    • 如下方法可建立实例类型一对一(即类型相同)的友元关系

      template<typename T> class A{    friend class B<T>;    friend void f<T>(...);    ...}
    • 如下方法可以建立其他类型友元

      class A{...};  template<typename>  template<typename>  class B {         template<typename X> friend class A; //B的所有实例都是A的所有实例的友元      friend T; //T是D<T>的友元  };  
  10. 类型别名
    使用using可以很方便的“固定”一个或多个类型参数

    template <typename T> using twins = std::pair<T, T>;
    twin<int> int_twin;
    twin<double> double_twin;
  11. 静态成员:不同实例有不同的静态成员,但同类型实例只有一个静态成员,所以要访问一个静态成员,必须显示指明类型。

  12. 如果要访问在模板的类型参数中定义的类型别名,需在类型参数前显示地加上typename

    template <typename T>typename T::val_type *top; //top是val_type类型T::val_type *top           //默认是相乘
  13. 新标准下(C++11)可以为函数和类模板提供默认实参,但必须遵照默认实参的规则(右侧参数均有默认实参)
  14. 当成员函数模板与类模板参数互相独立时,类外定义应两次注明template,而不是合并它们

    template<typename T1>template<typename T2>A<T> :: f(T2 b, T2 e){...}
  15. 显示实例化

    • 原因:同一个模板在多个文件中被实例化,造成浪费
    • 方式:
      • 声明:extern template class A;
      • 定义:template class A;
    • 注意:如果显示实例化一个类模板,必须可用于所有的成员

16.2 实参推断

  1. 类型确定与转换

    • 基本转换:
    • 顶层const被忽略,非const对象可传递给const引用形参
    • 若形参非引用形式,则可以将数组转换为指针

      template <typename T> void f1(T)template <typename T> void f2(const T&);int a[10];f1(a);          //f1(int *);f2(a);          //非法
    • 显示确定:即在名字后使用尖括号注明,省略规则即尾部忽略。

  2. 置尾返回类型的使用

    • 面临的问题:
      • 模板中的返回类型在遇到参数列表前不可知(如返回一个形参同类的迭代器)
      • 有时返回类型应该是值,但迭代器只能返回解引用
    • 解决办法
      • 置尾返回
      • 使用remove_reference(头文件:type_traits)
        template <typename T>auto f(T begin, T end) ->     typename::remove_reference<decltype(*begin)>::type{    return *begin;}
  3. 引用折叠

    • 面临的问题:

      • 有时形参是右值引用,但实参是左值
      • 通过类型别名,有时会产生引用的引用
    • 机制:

      • X& &&, X & &, X&& & 折叠为 X&
      • X&& && 折叠为 X&&
    • 实际效果

      template <typename T> void f(T &&);int i = 1;f(i);           //T 为 int&f(i * 2);       //T 为 int
    • 标准库的move定义

      template <typename T>typename remove_reference<T>::type&& move(T &&t){    return static_cast<typename remove_reference<T>::type&&>(t);}
  4. 转发

    • 面临的问题:

      1. 对于一个实现逆序输出的函数,一般情况下它没有问题

        template <typename F, typename T1, typename T2>void filp1(F f, T1 t1, T2 t2){    f(t2, t1);}
      2. 但如果f希望改变实参,则出现问题

        void f(int v1, int &v2){    cout<<++v2<<v1<<endl;}
      3. 因此将模板函数的定义修改为接收右值引用(可以保留引用的底层const属性)

        template <typename F, typename T1, typename T2>void filp2(F f, T1 &&t1, T2 &&t2){    f(t2, t1);}
      4. 但如果f希望获得一个右值引用,则出现问题,因为函数参数是一个左值表达式,而左值无法转化为右值

        void f(int &&v1, int &v2){    cout<<++v2<<v1<<endl;}

      因此需要使用forward

      • 头文件:unity
      • 效果:返回参数的右值引用
        所以最终filep应该为
        template <typename F, typename T1, typename T2>void filp(F f, T1 &&t1, T2 &&t2){    f(std::forward<T2> t2, std::forward<T1>t1);}

16.3 模板重载

  1. 基本匹配原则:

    • 如果有且仅有一个非模板函数同样好,选择非模板函数
    • 如果有多个模板函数同样好,选择最特例化的一个,例如
      template <typename T> f(const T&);template <typename T> f(T *);string s = "...";string *p = &s;f(p);               //调用第二个版本
  2. 如果直接将字符串作为参数带入模板,优先判定为char*

16.4 可变参模板

  1. 表示形式
    • 模板参数列表中,typename…表示多个类型,type…表示多个给定类型的非类型参数
    • 函数中形参的表述方式如下:

      template <typename... Args>
      void f(const Args&... rest){...}
  2. 可以用 sizeof 访问参数的数目。

  3. 通常可以用递归的方式处理可变参模板

    template <typename T, typename...Args>ostream& f(ostream& os, const T& t, const Args& ... args) {     os<<t<<"\t";      return f(os, args...); //包扩展  } 
  4. 包扩展

    • 形式:在模式后加…,即上述中的调用操作
    • 如果想多次调用某个函数,则形式为
      template <typename T, typename...Args>ostream& print(ostream& os, const T& t, const Args& ... args) {     return f(os, g(args)...);} 

16.5 模板特例化

  1. 函数模板特例化

    • 遇到的问题:当处理字符串时,尽管已经重载了不同版本的成员函数,可以在传递字面常量数组时做出区分,但如果传递字符指针,则无法达到目的。

    • 解决办法:一般与声明在同一个文件中,提供实例化,即提供一个尖括号内为空的模板,并提供具体类型

    • 注意细节:对于原来类型为const T&的形参,如果实例化位指针则为底层const,故应为const char * const

  2. 模板特例化
    对于模板,可以对某一部分进行特例化,也可以对整体特例化

    #include <iostream>template<typename T>  class S {public:                                                 void info() {                                                                                               printf("In base template\n");                                                                       }                                                                                                       static int code;                                                                                     };                                                                                                       template<typename T>                                                                                     int S<T>::code = 10;                                                                                     template<>                                                                                               int S<int>::code = 100;    //普通静态成员变量的int特化                                                                                  template<>                                                                                               void S<int>::info() {    //成员函数的int特化                                                                                          printf("In int specialization\n");                                                                   }                                                                                                       int main(){    S<float> s1;    s1.info();    //普通版本    printf("Code is: %d\n", s1.code);    //code = 10     S<int> s2;    s2.info();   //int特化版本    printf("Code is: %d\n", s2.code);   //code = 100     return 0;}
原创粉丝点击