(GeekBand)C++面向对象高级编程(下)第一周笔记(2)
第九节 Member templatel
成员模板常见于STL库(泛型编程典范)中的某些构造函数,目的是使其更有弹性,在类模板参数已经确定时,仍可以通过成员模板增加复用性。
#include<iostream>
using namespace std;
template<class T1,class T2>
struct Pair
{
typedef T1 first_type;
typedef T2 second_type;
T1 first;
T2 second;
Pair()
:first(T1()),second(T2()){}
Pair(const T1& a,const T2& b)
:first(a),second(b){}
template<class U1,class U2>
Pair(const Pair<U1,U2>& p)
:first(p.first),second(p.second){}
};
class A
{
};
class B
{
};
class C:public A
{
};
class D:public B
{
};
int main()
{
/*
Pair<double,double> b;
Pair<int,int> a(b);
*/
Pair<C,D> b;
Pair<A,B> a(b);
//Pair<A,B> a(Pair<C,D>())
return 0;
}
解析:
在案例中,我们已经通过Pair<int,int> a(),明确了对象a中的模板参数,然而我们仍然可以传入一个模板参数为<doub,double>的对象b,说明Pair类的构造函数可以接收与类模板参数类型相关的数据类型,不仅仅局限于<int>与<double>,它适用与任何继承关系类型,如案例中的A与C,B与D。
PS:有一点值得注意,对于继承关系类型,成员模板的使用有顺序要求,只可以将子类传入父类为模板参数的模板类,反之则不可以。
成员模板在智能指针中也有应用:
template<typename _Tp>
class shared_ptr:public __shared_ptr<_Tp>
{
template<typename _Tp1>
explicit shared_ptr(_Tp1* __p)
:__shared_ptr<_Tp>(__p){}
};
int main()
{
Base1* ptr=new Derived;//up-cast;
shared_ptr<Base1> sptr(new Derived1);//模拟up-cast
return 0;
}
解析:
Base1* ptr=new Derived;是分配空间返回Derived类型指针,并使用Base1类型接收,由于在类图中父类通常在子类的上方,所以这个动作被形象的称为“up-cast”。为了使智能指针适应这一特性,借助成员模板技术,为shared_ptr类传入模板参数Base1(父类),而实际传入构造函数的参数为Derived1(子类),在类中通过参数列表传入模板参数Base1以及Derived1类型指针,父类进行绑定,实现模拟up-cast。
PS:成员模板只是工具,还是要利用原生指针的特性去实现。
第十节 specialization(模板特化)
有时我们在设计模板时,想要特征化模板中的某一部分,这种使用方法叫做模板特化。
#include<iostream>
using namespace std;
template<class Key>
struct Hash
{
};
template<>
struct Hash<char>
{
void operator()(char x)const{cout<<"char"<<endl;}
};
template<>
struct Hash<int>
{
void operator()(int x)const{cout<<"int"<<endl;}
};
template<>
struct Hash<long>
{
void operator()(long x)const{cout<<"long"<<endl;}
};
int main()
{
Hash<char>()(1000);
Hash<int>()(1000);
Hash<long>()(1000);
return 0;
}
解析:
代码的执行结果为:
char
int
long
可以看到我们在定义范化模板Hash后又针对Hash不同类型的模板参数定义了不同的Hash,即模板特化,在使用时,根据传入模板参数的不同调用不同的特化模板。
第十一节 partial specialization(模板偏特化)
模板偏特化的“偏”分两种,一种是模板参数个数上的“偏”(特化类型少于模板总类型),另一种是模板参数范围上的“偏”(特化类型范围被缩小为指针)。
个数“偏”:
#include<iostream>
using namespace std;
template<class Key1,class Key2>
struct Hash
{
};
template<class Key2>
struct Hash<char,Key2>
{
void operator()(char x)const{cout<<"It's OK.'"<<endl;}
};
int main()
{
Hash<char,int>()(1000);
return 0;
}
解析:
可以看到在案例中我们定义了范化模板,模板参数为Key1与Key2,之后又定义了偏特化模板,只对其中的第一个参数(也必须是第一个参数,要遵循自左向右的顺序逐一特化)进行了特化,为char,第二个参数仍保持为符号Key2。这样不论在调用时第二个模板参数被绑定任何数据类型,仍然由第一个模板参数决定执行是否执行范化模板或某个特化模板。
范围“偏”:
#include<iostream>
using namespace std;
template<class Key>
struct Hash
{
};
template<class Key>
struct Hash<Key*>
{
void operator()(Key* x)const{cout<<"It's OK.'"<<endl;}
};
int main()
{
Hash<char*>()(new char);
return 0;
}
解析:
在案例中我们对范化模板中的模板参数Key进行了范围上的特化,即将它接收的类型指定为指针,也就是说,当传入的模板参数是其指针类型时,将调用特化模板函数。
第十二节 tamplate template parameter(模板模板参数)
模板模板参数也常常应用在STL库中,但是很少有书籍去讨论这样深层的细节。模板模板参数,即同时传入一个模板类以及将要与该模板类绑定的数据类型。
template<typename T,
template<typename T>
class SmartPtr
>
class XCls
{
private:
SmartPtr<T> sp;
public:
XCls():sp(new T){}
};
XCls<string,shared_ptr> p1;
解析:
我们知道shared_ptr(见笔记智能指针部分)是一个模板类,需要绑定一种数据类型才可以使用,我们同时将string与shared_ptr传入XCls类,在类中string作为模板参数与shared_ptr类绑定,实现模板模板参数。
第十三节 关于C++标准库
本章简单讲述了标准库在语言中重要性以及对于初学者的一些学习建议,就不多说了。
一句话,大家共勉:
革命尚未成功,同志仍需努力
第十四节 三个主题
variadic templates(数量不定的模板参数 since C++11)
C++2.0支持我们在定义范化模板时不必指定模板参数数量,这种用法极大的增加的模板的复用性。
解析:
我们可以看到有参print()中第一个模板参数为==typename T==,第二个模板参数是一个包==typename... Types==,而有参print()的第一个形参使用了符号T的取地址==T&==,而第二个为包==Types&...==,每次执行有参print()会打印第一个形参,之后调用自身,并传入形参包,包中的第一个参数自然成了下一个有参print()中的第一个形参被打印,如此执行,类似于递归。直到包中剩下最后一个元素,再次调用print()时进入的是上方的无参print(),这就是递归的出口。
当然参数的用法不局限于打印或调用自身,也可以用于组合与继承,在这节课不做讨论。
PS:sizeof(args...)可以获取包的大小(参数数量)。
auto(since C++11)
解析:
很方便很实用的一个语法糖,相信大家在课程之前都有使用过。
auto型变量可以根据等号右侧数据类型自适应改变自身类型。auto可以让编译器推测等号右侧的数据类型,最终调整auto对象的数据类型,值得注意的时,使用auto必须要进行初始化,编译器无法没有右值得情况下进行推测。另外,不推荐刚刚学习标准库的新手使用auto,除非对代码本身有非常深的理解,否则将走上一条比较偏的道路。
PS:auto的出现对解决λ(C++2.0)类型问题有奇效。
ranged-base for(since C++11)
对for的一点优化。
解析:
for中":"左侧为接收data的变量,右侧为data的容器,搭配auto使用效果更佳。
PS:data从“:”右侧流向左侧时是pass by value,等于copy一份data来使用,如果想对data本身进行操作,要加“&”转换为pass by reference。