C++Primer 第十六章

时间:2023-03-09 17:38:43
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[] = {};
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();
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 = ;}
/*结论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 = ;} //前向声明
template<typename T>class CMyTest2;
class CMyTest1
{
public:
void PrintfValueInt() {printf("%d\n", 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 = ;} /*结论a*/friend class CMyTest3<T>;
private:
int value;
}; template<typename T>
class CMyTest3
{
public:
void SetCMyTest1ValueInt_3000(CMyTest1& a) {a.valueInt = ;}
void SetValue_10(CMyTest2<T>& temMyTest) {temMyTest.value = ;} 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 = ;}
}; //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\n", a.value.value);} CB b;
CA<CB> a;
a.SetValue();
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\n", 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();}
int value = fun<int>(); //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 ;} //特例化成员 CTest<int , int> Test_Int(, );
CTest<double, double> Test_Double(1.1, 2.2);
int value0 = Test_Int.GetT1(); //value0 = 10
double value1 = Test_Double.GetT1(); //value1 = 2.2000000000000002