0.基础恶补
a.重写和重载概念辨析
重写:子类的方法覆盖父类的方法,要求方法名、参数都相同
重载:在同一个类中有两个及以上的方法,拥有相同的方法名,但参数却不同
b.纯虚函数
c.三种对象的产生方式虚函数是为了重载和多态的需要,子类中可以重写或不重写该函数;纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的interface。
纯虚函数引入原因:
1、同“虚函数”;
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。纯虚函数就是基类只定义了函数体,没有实现过程,定义方法如: virtual void Eat() = 0; 不要在cpp中定义;纯虚函数相当于接口,不能直接实例化,需要派生类来实现函数定义;
纯虚函数不能调用的两处地方
基类纯虚析构函数缺乏定义的状况/*************************两种调用纯虚函数错误的情况**************************** *****1. 在基类的构造函数中调用纯虚函数。此时实现纯虚函数的子类尚未被构造,故错误 *****2. 在基类的析构函数中调用纯虚函数。此时实现纯虚函数的子类已经被析构,故错误 *******************************************************************************/ #include<iostream> class BaseWithPureFunction { public: BaseWithPureFunction() { CallPureFunc();//此处调用了纯虚拟函数,该虚拟函数由派生类实现,但此处派生类还未构造成功,会导致r6025错误 } virtual void PureFunc()=0; void CallPureFunc() { PureFunc(); } }; class BaseEx:public BaseWithPureFunction { public: virtual void PureFunc() { printf( "BaseEx::PureFunc()/r/n "); } }; class BaseClassWithDestructorCallPureFun { public: virtual void PureFunc() = 0; void CallPureFunc() { PureFunc(); } virtual ~BaseClassWithDestructorCallPureFun() { std::cout<<"~BaseClassWithDestructorCallPureFun destructor call"<<std::endl; CallPureFunc(); } }; class BaseEx2 : public BaseClassWithDestructorCallPureFun { public: virtual void PureFunc() { std::cout<<"PureFunc Call From BaseEx2" <<std::endl; }; virtual ~BaseEx2 () { std::cout<<"~BaseEx2 destructor call"<<std::endl; } }; int main() { //先调用父类构造函数, 在父类构造函数中我们调用了纯虚函数, 此时子类对象还没有建立,导致调用纯虚函数错误!!! BaseEx BaseObj; { //在基类的析构函数调用了纯虚函数。函数退出时先析构派生类再析构父类,结果出错 //BaseEx2 ObjBaseEx; } }
#include<iostream> using namespace std; class base_class { public: virtual ~base_class()=0; }; //没有以下定义的纯虚析构函数是错误的。因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用, //然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~base_class的调用,所以要保证为它提供函数体。 // base_class::~base_class(){} class child_class:public base_class { public: //子类析构函数中,隐含的调用了父类析构函数,所以如果父类中将析构函数写成了纯虚函数,并且没有~base_class()函数体的话,就会报错 virtual ~child_class () { cout<<"child_class deconstructor!"<<endl; } }; int main() { { child_class temp; } }
d.一些原则//三种产生对象的方式 Point global;//全局内存配置,生命周期等同整个程序的生命周期 Point foobar() { Point local;//局部内存配置,生命周期在{}之间 Point *heap=new Point;//堆内存配置,生命周期在new和delete之间,但在此处指针*heap的生命周期在{}之间 *heap=local; delete heap; return local; }
纯虚函数的存在:一般来说不要把虚析构函数写成纯虚析构函数
虚函数的存在:一般而言,把所有成员函数都声明成虚函数,然后再靠编译器优化操作吧非必要的virtual invocation去除,并不是好的设计观念
虚函数中的const:因为不确定子类会不会更改数据,所以虚函数最好不要声明成const了
1.无继承情况下的对象构造
无继承情况下的对象构造。plain old data约等于bitwise copy semantic,他们的构造函数是trivial,要不就是根本没有被构造,要不就是构造了也没有被调用。复制构造函数、析构函数也都是类似的情况。
2.继承情况下的对象构造
本节转自:http://blog.youkuaiyun.com/cyningsun/article/details/8194377
A.继承情况下对象的构造过程
B.继承情况下对象构造过程中如何压制虚基类的构造函数的重复调用a. 调用所有的虚基类构造函数,从左到右,由最深到最浅(虚基类在对象模型中是以独特的方式(固定部分与共享部分)支持的,不涉及到在对象模型中的偏移量的问题)
b. 调用所有的上一层的基类构造函数,以基类的声明顺序为顺序(这是因为一般基类的 subobject 都会被放在 object 的开始,并且按基类声明的次序放置)
c. 如果 class object 有虚函数表指针,设定其初值,指向适当的虚函数表
d. 如果有一个member object 并没有出现在成员初始化列表中,且它有一个默认构造函数,那么该默认构造函数必须被调用
e. 记录在成员初始化列表中的数据成员初始化操作会被放在 constructor 的函数本身,并以members声明的顺序为顺序。
f. 程序员自己的代码(在此步以上的操作均为编译器安插的)
“virtual base class constructors的调用”有着明确的定义:只有一个完整的classobject被定义出来时,它才会被调用;如果object只是某个完整的object的subject,它就不会被调用。
上面这句话举个例子:
class PVertex 的object中,在保存 PVertex 自己的数据之前,上面有很多个父类的subobject,那么虚基类Point的构造函数不会被其他的subobject所调用,它的构造函数只有当整个object被定义出来时,也即PVertex数据定义出来时才会被调用,也就是只会被PVertex所调用。而在PVertex之前的subobject 对虚基类构造函数的调用操作将会被抑制。另外,如果没有最下层的PVertex,那么就是被Vertex3d调用。综上所述,这样才能保证共享虚基类对象的一致性。
3.对象复制语意学(好吧,指的是“=”copy assignment operator,而不是拷贝构造函数copy constructor)
a.关于重载赋值操作符和复制拷贝构造函数的区别
b.explicit拷贝构造函数是用一个已存在的对象去构造一个不存在的对象(拷贝构造函数毕竟还是构造函数嘛),也就是初始化一个对象。
而赋值运算符重载函数是用一个存在的对象去给另一个已存在并初始化过(即已经过构造函数的初始化了)的对象进行赋值。
实例:http://www.cnblogs.com/darknightsnow/archive/2012/10/17/2728078.html
C++中, 一个参数的构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个构造器 ,2 是个默认且隐含的类型转换操作符。
所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候编译器就自动调用这个构造器, 创建一个AAA的对象。
这样看起来好象很酷, 很方便。 但在某些情况下, 却违背了我们的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用,使用, 不能作为类型转换操作符被隐含的使用。
c.
当设计一个class,并以一个class object指定给另一个object时,我们有三种选择:
a.什么都不做,实施默认行为
b.提供一个explicit copy assignment operator
c.拒绝拷贝,拒绝拷贝方式:
1.将copy assignment operator私有化
2.不提供函数定义,导致在链接失败
对象赋值(拷贝)函数是为了打开named value return(NVR)
对象赋值操作copy assignment operator的合成条件和构造函数类似
当不要Bitwise Copy Semantics时,类就需要合成一个对象赋值操作:
1.当class内含一个member object,而member object声明有一个copy constructor operator时
2.当class继承一个base class而后者存在有一个copy constructor operator时
3.当class声明了一个或多个virtual functions时,
4.当class派生自一个继承串链,其中有一个或多个virtual base classes时。此时无论基类有没有copy operator。
4.解构语意学
如果class没有定义destructor,那么只有class存在member object且该member object含有destructor的情况下,编译器才会自动合出一个destructor来。默认情况下编译器并不会合成一个destructor,即使是它拥有一个virtual function。
析构函数的扩展方式与构造函数相同,但顺序相反。