1.子类对象一定“是一个”基类对象,基类对象不一定是子类对象
1.希望派生类只继承成员函数的接口——将函数声明为纯虚函数
2.希望派生类同时继承函数的接口和实现,但允许派生类改写实现——将函数声明为虚函数
3.希望同时继承接口和实现,并且不允许派生类改写任何东西——声明为非虚函数
1.基类声明为非虚的函数表示这个函数不希望被子类所修改,而是直接被子类继承使用
2.非虚函数是静态绑定的,当将一个子类对象通过基类指针的形式调用时,并不会调用子类对象所重写的函数——基类的非虚函数被调用
3.如果子类重写了基类的非虚函数,则分别通过子类对象和指针调用时会显得有些精神分裂——子类对象调用该函数时调用的是子类的,如果通过几类的指针调用,则会调用基类的非虚函数
1.由于重定义继承而来的非虚函数是一种错误,因此此条相当于不要“继承一个有缺省参数值的虚函数”
2.虚函数是动态绑定而缺省参数值是静态绑定的,当一个子类对象通过基类指针的形式调用一个被继承而来的有缺省值的函数时,子类的函数被调用,但是缺省值确实基类的!
1.如果将子类的指针转换成基类后要调用子类中一个不是从基类继承的函数或者是继承的非虚函数,此时只能通过强制类型的静态转换来实现——不到万不得已的时候千万不要使用向下转换,会使代码变得不宜维护
2.第1点最好的解决办法是通过虚函数继承来解决
3.如果无法通过虚函数继承来解决第一点中的问题,尽量使用动态转换——dynamic_cast,它可以决绝转换失败的问题,当转换失败时返回一个NULL指针
1.当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类
2.当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类
1.和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象转换成基类对象
2.从私有基类继承而来的成员都成为了派生类的私有成员,即使它们在基类中是保护或公有成员
3.如果有一个通用但不安全的类,为了防止用户滥用带来的隐患,可以将该类设计为要给基类,将其所有接口和成员变量声明为protected,然后让其他特定的类来私有继承该类
class GenericStack { protected: GenericStack(); ~GenericStack(); void push(void *object); void * pop(); bool empty() const; private: //... // 同上 }; GenericStack s; // 错误! 构造函数被保护 class IntStack : private GenericStack { public: void push(int *intPtr) { GenericStack::push(intPtr); } int * pop() { return static_cast<int*>(GenericStack::pop()); } bool empty() const { return GenericStack::empty(); } }; class CatStack : private GenericStack { public: void push(Cat *catPtr) { GenericStack::push(catPtr); } Cat * pop() { return static_cast<Cat*>(GenericStack::pop()); } bool empty() const { return GenericStack::empty(); } }; IntStack is; // 正确 CatStack cs; // 也正确
1.如果一个类从多个类继承而来,且其基类中存在相同的成员函数或者成员变量,但是子类又没有定义该接口或变量,则通过子类调用时会产生二义性
class Lottery { public: virtual int draw(); //... }; class GraphicalObject { public: virtual int draw(); //... }; class LotterySimulation: public Lottery,public GraphicalObject { //... // 没有声明draw }; LotterySimulation *pls = new LotterySimulation; pls->draw(); // 错误! ---- 二义 pls->Lottery::draw(); // 正确——当显式地用一个类名来限制修饰一个虚函数时,函数的行为将不再具有虚拟的特征。相反,被调用的函数只能是你所指定的那个,即使调用是作用在派生类的对象上 pls->GraphicalObject::draw(); // 正确
2.如果一个classD类从类classA和classB继承而来,且类classA和classB都定义了相同名称的虚函数func,当子类需要重写class A和Aclass B的2个func时,将发生二义性——一个类只允许一个同名的同参数的函数,此时可以通过增加中间层来解决这个问题
以下是一个集纯虚函数,简单虚函数和内联函数(参见条款33)综合应用之大成的方法,值得牢记在心:
class Lottery { public: virtual int draw(); //... }; class GraphicalObject { public: virtual int draw(); //... }; class AuxLottery: public Lottery { public: virtual int lotteryDraw() = 0; virtual int draw() { return lotteryDraw(); } }; class AuxGraphicalObject: public GraphicalObject { public: virtual int graphicalObjectDraw() = 0; virtual int draw() { return graphicalObjectDraw(); } }; class LotterySimulation: public AuxLottery, public AuxGraphicalObject { public: virtual int lotteryDraw(); virtual int graphicalObjectDraw(); //... }; LotterySimulation *pls = new LotterySimulation; Lottery *pl = pls; GraphicalObject *pgo = pls; // 调用LotterySimulation::lotteryDraw pl->draw(); // 调用LotterySimulation::graphicalObjectDraw pgo->draw();
3.如果子类从虚基类中继承而来,则基类的初始化参数是由子类构造函数的参数传递进来的,当继承层次较深将导致参数的多次传递,如果中间的继承关系被修改,则执行初始化的类(离虚基类最近的类)还可能改变,解决的办法是,不要在虚基类中声明成员变量,仅仅提供接口(java的做法,其接口——虚基类禁止包含数据)
4.砖石继承的二义性:有类class A class B class C class D,B和C继承自A,D继承自B和C。如果A和C中定义了虚函数mf,B和D未定义,通过D的对象调用mf时,可能会产生二义性:
1.如果A是虚基类,则由于C重写了mf,其优先级高于A的mf,因此C的mf被调用
2.如果A不是虚基类,则将产生二义性——因为函数mf不具备多态性,通过D调用mf时将难以在A和C之间取舍
1.一个空类,C++编译器至少为它提供了一个拷贝构造函数、一个赋值云算法、一个析构函数、一个取址云算法、一个构造函数,所有这些函数都是共有的
2.除非这个类的基类的析构函数是虚函数,否则编译器提供的析构函数是非虚函数
3.缺省的拷贝构造函数和赋值云算法对类的非静态数据成员进行”以成员为单位的“逐一拷贝(赋值)
4.对于类的非静态成员变量,缺省的拷贝构造函数和赋值运算符调用其成员变量类的拷贝构造函数或者赋值运算符
5.如果不想实现operator = 或 operator&,可以将其声明为private