• C++Primer 第十六章


    //1.模板定义以关键字template开始,后跟一个模板参数列表,此列表不能为空。编译器用推断出的模板参数来实例化一个特定版本的函数。类型参数前必须使用class或者typename(推荐使用typename)。
    template <typename T> bool comp(T t0, T t1){return t0 > t1;}
    
    //2.除了定义类型参数外,还可以在模板中定义非类型参数。一个非类型参数表示一个值(必须是常量表达式,实际使用过程中,最好只使用整形,而不要使用其他类型)而非一个类型,通过特定的类型名而非typename来指定非类型参数。
    template<int M> inline int fun(int (&p)[M]){return sizeof p;}    //注意inline的位置
    int a[10] = {};
    int v = fun(a);    //v = 40
    
    //3.当编译器遇到一个模板定义时,它并不生成代码。只有当我们实例化出模板的一个特定版本时,编译器才会生成代码。这一特性影响了我们如何组织代码以及错误何时被检测到。
    //  与一般代码不同,模板的头文件通常既包含声明,也包含定义。
    
    //4.当调用一个函数模板的时候,编译器通常用函数实参为我们推断模板实参。
    //  与函数模板的不同之处是,编译器不能为类模板推断模板参数类型,为了使用类模板,必须在模板名后的尖括号中提供额外信息,用来代替模板参数的模板实参列表(函数模板也能这么做)。
    //  由以上可知,类模板的名字不是一个类型名,类模板用来实例化类型,而一个实例化的类型总是包含模板参数的。
    //  因为类模板的名字不是类型名,而类模板名字加上尖括号以及显示模板实参才是一个类型名,所以在模板类外定义模板类的成员函数的时候必须以关键字template开始,后接类模板参数列表。
    template<typename T>
    class CA
    {
    public:
        CA(){}
    public:
        T GetValue();
        void SetValue(T t){value = t;}
    private:
        T value;
    };
    template<typename T> T CA<T>::GetValue()    {return value;}
    
    CA<int> A;
    A.SetValue(10);
    int value = A.GetValue();    //value = 10
    
    //5.默认情况下,一个类模板的成员函数只有当程序用到它的时候才进行实例化。如果一个类模板的成员函数没有被用到,则此成员函数将不被实例化。
    //  上述特性使得:即使某种类型不能完全符合模板的要求,我们仍能使用该类型实例化类。
    
    //6.当我们使用一个类模板类型时必须提供模板实参,但这个规则有一个例外:在类模板自己的作用域中,我们可以直接使用模板名,而不提供实参。
    
    //7.模板和友员
    //  模板类与模板友元函数:
    //    A.类为模板,友元函数也是模板,且使用的模板参数一致。                则此友元函数仅对相同模板参数的类实例具有特殊访问权限。
    //    B.类为模板,友元函数不是模板(使用指定模板实参类型的类实例)。         则友元函数只对指定类型的模板参数的类实例具有特殊访问权限。
    //    C.类为模板,友元函数也是模板,且使用的模板类型参数不一致。           则此友元函数对所有类实例都具有特殊访问权限。
    //    D.类不是模板,友元函数是模板。                                   则所有模板类型的友元函数都是对此类具有特殊访问权限。
    //  模板类有模板类的友元关系(在A类中声明B类为其友元类)
    //    a.A类为模板, B类是模板,且A类和B类使用的模板参数一致               则此B类仅对相同模板参数的A类实例具有特殊访问权限。
    //    b.A类是模板, B类是模板,且A类和B类使用的模板参数不一致。           则所有B类的实例都对A类具有特殊访问权限
    //    c.A类是模板, B类不是模板。                                     则B类对所有A类的实例均具有特殊访问权限
    //    d.A类不是模板,B类是模板,且B类使用了指定模板实参类型。              则只有指定类型的B类实例才对A类具有特殊访问权限
    //    e.A类不是模板,B类是模板,且B类没有使用固定的模板类型。              则所有B类的实例都对A类具有特殊访问权限
    template<typename T>
    class CMyTest
    {
    private:
        T value;
    public:
        T GetValue()    {return value;}
    
        /*A结论*/friend void SetVlaue_10(CMyTest<T>& MyTest)        {MyTest.value = 10;}
        /*结论B*/friend void SetValue_20(CMyTest<int> &MyTest);//注意点:此类型的友员函数不能定义在类的内部,否则会造成重定义                
        /*结论C*/template<typename X> friend void SetValue(CMyTest/*<T> 这里的<T>可加可不加*/ &temMyTest, X value)    {temMyTest.value = value;}
    };
    void SetValue_20(CMyTest<int> &MyTest)    {MyTest.value = 20;}
    
    //前向声明
    template<typename T>class CMyTest2;
    class CMyTest1
    {
    public:
        void PrintfValueInt()    {printf("%d
    ", valueInt);}
    private:
        int valueInt;
    
        /*结论d*/friend class CMyTest2<CMyTest1>;
        /*结论e*/template<typename T> friend class CMyTest3;        //注意这里的friend的位置
    };
    
    template<typename T>
    class CMyTest2
    {
    public:
        void SetCMyTest1ValueInt_1000(T& a)    {a.valueInt = 1000;}
    
        /*结论a*/friend class CMyTest3<T>;
    private:
        int value;
    };
    
    
    template<typename T>
    class CMyTest3
    {
    public:
        void SetCMyTest1ValueInt_3000(CMyTest1& a)    {a.valueInt = 3000;}
        void SetValue_10(CMyTest2<T>& temMyTest)    {temMyTest.value = 10;}
    
    private:
        T value;
    
        /*结论c*/friend class CMyTest4;
    
    };
    
    class CMyTest4
    {
    public:
        template<typename T>void SetValue(CMyTest3<T>& temTest, T value)    {temTest.value = value;}
        /*结论D*/template<int X> friend void SetValue(CMyTest4 &MyTest)        {MyTest.value = X;}
    private:
        int value;
    };
    
    template<typename T>
    class CA
    {
        template<typename X> friend class CB;
    private:
        int value;
    };
    
    template<typename X> class CB
    {
        template<typename T>
        /*结论b*/void Fun(CA<T> a)    {a.value = 10;}
    };
    
    //8.可以将模板类型参数声明为友员,即使使用内置类型也不会出错。
    template<typename T> class CA
    {
        friend T;
    public:
        void SetValue(T v){value = v;}
    private:
        T value;
    };
    
    class CB
    {
    public:
        CB(){}
        CB(int v) : value(v){}
    public:
        void Fun(CA<CB>);
    public:
        int value;
    };
    void CB::Fun(CA<CB> a)    {printf("%d
    ", a.value.value);}
    
    CB b;
    CA<CB> a;
    a.SetValue(10);
    b.Fun(a);        //输出10
    
    //9.类模板的静态成员:相同类型的实例化后的类共同维护一个类的静态成员。不同实例化的类之间的静态成员互不相关。
    
    //10.模板参数遵循普通的作用域规则,一个模板参数名的可用范围是在其声明后,至模板声明或定义结束前。
    //   与任何其他名字一样,模板参数会隐藏外层作用域中声明的相同名字。但是与大多数其他上下文不同,在模板内不能重用模板参数名。
    //   由于模板参数名不能重用,所以一个模板参数名在一个特定的模板参数列表中只能出现一次。
    
    //11.考虑在类模板中: T::size_type *p;由于编译器无法掌握T的类型,所以编译器不知道这句话的意思是正在定义一个名为p的指针还是将名为size_type的static数据成员与名为p的变量相乘。
    //   默认情况下,C++假定通过作用域运算符访问的名字不是类型。因此,如果我们希望使用一个模板类型参数的类型成员,就必须显示的告诉编译器该名字是一个类型。通过使用关键字typename来实现这一点。
    //   由上述结论可知:  T::size_type *p;的默认意思是将静态成员size_type和p相乘。而typename T::size_type *p;则是定义了一个T::size_type类型的指针。
    template<typename T> typename vector<T>::value_type fun()    {return vector<T>::value_type();}    //vector<T>::value_type();返回一个类型为T的值初始化的值
    string s = fun<string>();    //s = "";
    
    //12.可以为类模板提供默认模板实参。在新标准下,也可以为函数提供默认模板实参(VS2010不支持此项特性)。
    //   无论何时使用一个模板,我们都必须在模板名之后加上尖括号。尖括号指出类必须从一个模板实例化而来。特别的,当一个类模板为其所有的模板参数均提供了默认实参,且我们希望使用这些默认实参,则必须在模板名后跟一个空尖括号对。
    
    //13.一个类,无论是模板类还是非模板类,都可以包含本身是模板的成员函数。这种成员称为成员模板。
    //   成员模板不能是虚函数。这是因为:编译器在处理一个类的时候,会希望确定其虚函数表,若允许定义成员模板为虚函数,则虚函数表无法在编译时就被确定,而必须根据成员模板的实例化情况确定。所以成员模板不能是虚函数。
    //   与类模板的普通函数成员不同,成员函数是函数模板。当我们在类模板外定义一个成员模板的时候,必须同时为类模板和成员模板提供模板参数列表。类模板的参数列表在前,后跟成员自己的参数列表。
    template<typename T = int> class CA    {public: template<typename X> void fun();};
    template<typename T> template<typename X> void CA<T>::fun(){printf("%d, %d
    ", sizeof(T), sizeof(X));}
    CA<> a;
    a.fun<double>();    //输出4, 8
    
    //14.当模板被使用时才会进行实例化这一特性意味着:相同的类实例可能出现在多个源文件中。在大系统中,在多个文件中实例化相同模板带来的开销可能会很严重。
    //   通过显示实例化可以来避免上述开销。在一个cpp中定义,在其他所有使用此类实例的cpp中声明。 
    extern template declaration;     //实例化声明
    template declaration;            //实例化定义,其中declaration是一个类或者是函数声明,其中的模板参数均被模板实参替换。
    //   由于编译器在使用一个模板时自动对其实例化,因此实例化声明必须出现在任何使用此实例化版本的代码之前。
    //   一个类模板的显示实例化会实例化该模板的所有成员,包括内联的成员函数。当编译器遇见一个显示实例化定义的时候,它不了解程序使用哪些函数,因此与处理类模板的普通实例化不同,编译器会实例化该类的所有成员。
    
    //15.如果一个函数形参的类型使用了模板类型参数,那么它将采用特殊的初始化规则。只有很有限的几种类型转换会自动应用于这些实参。编译器通常不是对实参进行类型转换而是生成一个新的模板实例。
    //   能应用于函数模板的类型转换如下:
    //     A:顶层const无论是在实参还是形参中都会被忽略。
    //     B:const转换:一个const对象的引用或指针可以接受一个非const对象的引用或指针。
    //     C:当函数形参不是引用类型的时候,则可以对数组或函数类型的实参应用正常的指针转换。
    //   一个模板类型参数可以用作多个函数形参的类型,由于上述类型转换的限制,因此传递给这些形参的实参必须具有相同的类型。如果推断出来的类型不匹配,则调用就是错误的。
    //   函数模板可以有普通类型定义的形参。这种函数形参能正常接收对应类型的实参(会进行正常的类型转换,而不需要遵守上述规则)。
    
    //16.在某些情况下,编译器无法推断出模板参数的类型。则每次调用都必须为无法推断的那个形参提供一个显示模板实参。
    template<typename T, typename X> T fun(X x)    {return T(0);}
    int value = fun<int>(10);    //value = 0
    //   对于模板类型参数已经显示指定了的函数形参,可以进行正常的类型转换
    //   当我们希望由用户确定返回类型时,用显示模板实参表示模板函数的返回类型是很有效的,但是在其他情况下,要求显示指定模板实参会给用户带来负担,此时使用尾置返回类型可以有效解决这个问题。
    template<typename T> auto fun(T beg)->decltype(*beg)        {return *beg;}//接受一个迭代器,返回迭代器所指对象的引用。
    
    //17.标准库的类型转换模板。定义在头文件type_traits中,声明在命名空间std中。
    对Mod<T>,其中Mod为:                若T为                    则Mod<T>::type为
    remove_reference                  X&或    X&&              X
                                      否则                     T
    add_const                         X&、const X 、函数        T
                                      否则                     const T
    add_lvalue_reference              X&                       T
                                      X&&                      X&
                                      否则                      T&
    add_rvalue_reference              X&、X&&                   T
                                      否则                       T&&
    remove_pointer                    X*                        X
                                      否则                       T
    add_pointer                       X&、X&&                   X*
                                      否则                       T*
    make_signed                       unsigned X                X
                                      否则                       T
    make_unsigned                     带符号类型                  unsigned X
                                      否则                       T
    remove_extent                     X[n]                       X
                                      否则                       T
    remove_all_extents                X[n1][n2]...               X
                                      否则                        T
    
    //18.当函数参数是一个函数模板实例的地址时,程序上下文必须满足:对每个模板参数,都能唯一确定其值或类型。
    
    //19.引用折叠和右值引用参数:
    //   右值引用参数:当我们将一个左值(如i, int类型)传递给函数的右值引用参数,且此右值引用指向模板类型参数(如T&&)时,编译器推断模板类型参数为实参的左值引用类型。此时编译器推断T的类型为int&,而非int。
    //        T被推断为int&看起来好像意味着上述模板函数的参数应该是一个类型int&的右值引用。通常,我们不能直接定义一个引用的引用,但是通过类型别名或通过模板类型参数间接定义是可以的。
    //   引用折叠:如果我们间接的创建了一个引用的引用,则这些引用将发生折叠。
    //        X& &、X& && 、X&& &都折叠为X&
    //        X&& &&折叠为X&&
    //   注意点:引用折叠只能应用于间接创建的引用的引用,如:类型别名或模板参数。
    
    //20.编写接受右值引用参数的模板函数:
    template <typename T> void f3(T&& val)    {T t = val;}    //实际上f3的形参可以接受任意类型的实参
    //   在上述函数中t到底是val的拷贝还是val的引用不能确定,应用remove_reference可能有点帮助,但是编写正确的代码仍然异常困难。
    //   在实际编程中,右值引用通常应用于两种情况:模板转发其实参,模板被重载。
    //   使用右值引用的函数模板通常的重载方式:
    template <typename T> void f(T&&);         //绑定到非const右值
    template <typename T> void f(const T&);    //绑定左值和const右值 
    //   std::move()的定义:
    template<class _Ty> inline typename tr1::_Remove_reference<_Ty>::_Type&& move(_Ty&& _Arg){return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);}
    //   转发函数(要保持传入参数的const属性,以及实参是左值还是右值的)的一般定义:
    template <typename F, typename T1, typename T2> void flip(F f, T1 &&t1, T2 &&t2){f(std::forward<T1>(t1), std::forward<T2>(t2)};
    //   std::forward : 定义在头文件utility中,声明在命名空间std中,返回该显示实参类型的右值引用。即, forward<T>的返回类型为T&&。
    
    //21.如果涉及函数模板,则重载函数的匹配规则会受到以下几方面的影响:
    //   A:对于一个调用,其候选函数包括所有模板实参推断成功的函数模板实例。
    //   B:候选的函数模板总是可行的,因为模板实参推断会排除任何不可行的模板。
    //   C:与往常一样,可行函数按类型转换来排序。
    //   D:与往常一样,如果恰有一个函数提供比任何其他函数都更好的匹配,则选择此函数。但是如果有多个函数提供同样好的匹配,则:
    //        如果同样好的函数中只有一个是非模板函数,则选择此函数。
    //        如果同样好的函数中没有非模板函数,而有多个函数模板,且其中一个模板比其他模板更特例化,则选择此模板。否则调用有歧义。
    
    //22.模板特例化:编写单一模板,使之对任何可能的模板实参都是最合适的,都能实例化,这并不总是能办到的。在某些情况下,通用模板的定义对特定类型是不合适的:通用定义可能编译失败或者做的不正确。
    //   其他时候,我们也可以利用某些特定的知识来编写更高效的代码,而不是从通用模板实例化。当我们不能使用模板版本时,可以定义类或者函数模板的一个特例化版本。
    
    //23.为了指出我们正在实例化一个模板,应使用关键字template后跟一对空尖括号。空尖括号指出我们将为原模板的所有模板参数提供实参。
    template <typename T>void Fun(T t){}
    template<> void Fun(int t){}            //特例化版本
    //   当我们定义一个特例化版本时,函数参数类型必须与一个先前声明的模板中对应的类型匹配,否则会发生编译错误。
    //   特例化的本质是实例化一个模板,而非重载它。因此,特例化不影响函数匹配。
    //   模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面,然后是这些模板的特例化版本。
    //   与函数模板不同,类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数。一个类模板的部分特例化本身是一个模板。
    //   我们可以特例化类的成员而不是类。
    
    //类模板部分特例化
    template<typename T0, typename T1>
    class CTest
    {
    };
    template<typename T0>
    class CTest<T0, int>
    {
    };
    
    //特例化成员:
    template<typename T0, typename T1>
    class CTest
    {
    public:
        CTest(T0 t_0, T1 t_1) : t0(t_0), t1(t_1){}
    
    public:
        T0 GetT0(){return t0;}
        T1 GetT1(){return t1;};
    
    private:
        T0 t0;
        T1 t1;
    };
    template<> int CTest<int , int>::GetT1()    {return 10;}    //特例化成员
    
    CTest<int , int> Test_Int(1, 2);
    CTest<double, double> Test_Double(1.1, 2.2);
    int value0 = Test_Int.GetT1();            //value0 = 10
    double value1 = Test_Double.GetT1();    //value1 = 2.2000000000000002
  • 相关阅读:
    【转】Nginx open_file_cache模块 文件描述符缓存
    缓冲区溢出二:从缓冲区溢出到获取反弹shell实例
    英文文法学习笔记(18)代名词
    英文文法学习笔记(17)特殊构句
    英文文法学习笔记(19)时态
    小知识:RMAN基于某个具体时间点的恢复示例
    小知识:NFS卡死问题处理
    英文文法学习笔记(20)时态的一致及叙述法
    英文文法学习笔记(21)连接词
    小知识:如何从图片中提取文字
  • 原文地址:https://www.cnblogs.com/szn409/p/5628326.html
Copyright © 2020-2023  润新知