zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    FZU 2112 并查集、欧拉通路
    HDU 5686 斐波那契数列、Java求大数
    Codeforces 675C Money Transfers 思维题
    HDU 5687 字典树插入查找删除
    HDU 1532 最大流模板题
    HDU 5384 字典树、AC自动机
    山科第三届校赛总结
    HDU 2222 AC自动机模板题
    HDU 3911 线段树区间合并、异或取反操作
    CodeForces 615B Longtail Hedgehog
  • 原文地址:https://www.cnblogs.com/szn409/p/5628326.html
Copyright © 2011-2022 走看看