复制构造函数和赋值操作符
#include<iostream>
class Base {
public:
Base(double t = 1.2) : test(t) {}
public:
double test;
};
class Derived : public Base {
public://复制构造函数不会自动唤醒
//Base::Base(const Base&) not invoked automatically
Derived(double t = 2.2) : Base(t){}
Derived(const Derived& d) :
Base(d) /*other member initialization */ {/*...*/}//显式使用基类复制构造函数Base(d);
//Base::~Base invoked automatically编译器显式(编译器调用怎么看显式)自动调用基类析构函数
//要注意的是,对象的撤销顺序与构造顺序相反(理所当然),按层次逐个调用析构函数
~Derived() {/*do what it takes to clean up derived members*/}
public:
Derived&
operator=(const Derived&);
};
//Base::operator=(const Base&) not invoked automatically
Derived& Derived::operator=(const Derived &rhs){
if(this != &rhs) {//必须防止自身赋值
Base::operator=(rhs); //assign the base part(给基类部分赋值)
//do whatever needed to clean up the old value in the derived part(给其他部分清楚或者赋值)
//assign the members from the derived
}
return *this;
}
//如果不显式给基类初始化,基类将默认初始化一个
class Derived2 : public Base{
public:
Derived2(double t) : Base(t){}
Derived2(const Derived2& d2) {/*...*/}
};
int main(){
Base b1;
std::cout << b1.test << std::endl;
Derived d1(3.14159);
Derived d2(d1);
std::cout << d1.test << std::endl;
std::cout << d2.test << std::endl;
Derived2 d3(3.14159);
Derived2 d4(d3); //复制构造函数如果省略基类的初始化,将调用默认构造函数
std::cout << d3.test << std::endl;
std::cout << "After copy:" << std::endl;
std::cout << d4.test << std::endl;
}
至于析构函数,下边就要说说为什么要virtual析构函数了
delete指向动态分配对象的指针时,需要在从内存清掉之前运行析构函数清除对象。
处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针。
综合来说:也就是说被基类指针误读为基类对象的派生类对象,析构函数只删除派生类的基类部分,其他地方都没搞定,为了解决这个问题,基类中的destructor必须是virtual。
virtual ~Base() { std::cout << " ~Base() called" << std::endl; }
~Derived() { std::cout << " ~Derived() called" << std::endl; }
Base *itemP = new Base;
delete itemP;
std::cout << "=================================================" << std::endl;
itemP = new Derived;
delete itemP;
利用virtual的“覆盖机制”,让Derived对象不存在Base::~Base(),这样必须先从 ~Derived()开始。。。。。。。
REVIEW:C++的函数调用默认不使用动态绑定,要触发动态绑定,必须满足两个条件:
第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数(但是假如爸爸定义了虚函数,他子孙默认也是虚的),非虚函数不进行动态绑定;
第二,必须通过基类类型(为什么是基类,安全啊,兼容啊,都可以传)的引用或指针进行函数调用,要理解这一要求,需要理解在使用继承层次中某一类型的对象的引用或指针时会发生什么(这不又发生一个么)。
(前边介绍的时候基本上是给函数传参时,传递一个基类的引用或指针,根据具体对象可以动态的选择执行基类还是派生类的成员函数。)
void print_total(ostream &os, const Item_base &item, size_t n){
os << *.....*/ << item.net_price() << std::endl;//net_price有两个版本,要调用哪个就看传入的确切对象是什么了
}
还有,virtual析构函数是不需要同名的(其他的需要同名吧)
virtual void Base::func1() { std::cout << "没被覆盖到" << std::endl; }
void Derived::func1() { std::cout << "同名func1,覆盖了" << std::endl; }void Derived::func2() { std::cout << "是的,必须同名,不然不覆盖?" << std::endl; }
测试代码exception:但是要记住,直接使用作用域标识符能忽略virtual,因为析构函数那例子是delete时自动调用,我就不能发挥作用域标识符的功能了
根据第一条,如果Base类destructor是virtual,派生类默认也是virtual,即使合成版也不利外。也就是说可以让孙子直接动态覆盖爸爸(还有爷爷)Derived *derivedP = new Derived; derivedP->func1(); derivedP->func2(); delete derivedP; derivedP->Base::func1();//但是要记住,直接用作用域标识符能忽略virtual的作用
exception:三法则规定,如果有析构,基本也需要复制和赋值。可是基类析构是个例外,如果基类只是为了(将析构函数设为虚函数)而有的空析构函数,那么这就是个例外,其他那俩没用(具体一般情况为什么都需要那俩,也不清楚。。。原话:这是经验法则)//把~Derived()屏蔽了,直接跳到D2来,virtual作用存在。。。。。。 derivedP = new Derived2(12); delete derivedP; itemP = new Derived2(12); delete itemP;
而大多数情况下,在继承层次中,根类定义虚析构函数是必要的
构造函数和赋值操作符不是虚函数:
构造函数是在对象完全构造之前运行的,在构造函数运行的时候,对象的动态类型还不完整。
赋值操作符设为虚函数可能会令人混淆:
因为virtual要函数相同,函数相同意味着形参也相同,基类operator=(Base& b;而每层派生的operator=(Derived& d);形参类型不一样。
两个习题
15.19应该就是没声明基类虚析构函数
15.20就是一些测试,前边测的差不多了,就不提了,测试方法也是在函数体加打印,只不过要测试复制函数和赋值操作符