继承和组合:
重用已存在类的代码有两种方式:1,组合,即在新类中创建已存在类的对象;2,继承,即创建一个新类作为一个已存在类的类型。
在派生类中可以重新定义基类中的成员函数,派生类的对象调用该函数时调用的是重新定义的成员函数,而调用基类的成员函数只能使用作用域显示的标明基类名。
构造函数和析构函数的调用顺序:构造函数是从类层次的最根处开始,在每一层,首先会调用基类构造函数,然后调用成员对象构造函数,最后调用的才是自己的构造函数的执行体(还有一点需要注意:成员对象的初始化次序完全不受构造函数的初始化表达式中的次序影响,该次序是由成员对象在类中声明的次序决定的),而析构函数的调用顺序和构造函数的调用顺序完全相反。
构造函数和析构函数不能被继承,operator=也不能被继承,因为它完成类似于构造函数的活动。每一个派生类都必须有自己的构造函数和析构函数。如果在派生类中重新定义了成员函数,所有在基类中的其他重载函数都会被隐藏。
一个类对象可以作为它自己的类或者它的基类的对象来使用,即向上类型转换。
多态性和虚函数:
多态性提供了接口与具体实现之间的隔离,从而将"what"和"how"分离开来,多态性改善了代码的组织性和可读性,同时使穿件的程序具有可扩展性。
把函数体和函数调用相联系称为捆绑:1,早捆绑(静态捆绑):捆绑在程序运行前(由编译器和链接器)完成;2,晚捆绑(动态捆绑):捆绑发生在程序运行时,需要确定并捆绑对象的类型,从未调用合适的成员函数。对于特定的函数,为实现动态捆绑,需要在基类中使用virtual关键字声明这个函数为虚函数,而调用所有匹配基类声明行为的派生类函数都将自动使用虚函数,动态捆绑只对虚函数起作用。
编译器为每个包含虚函数的类创建唯一的一个表(称为VTABLE)编译器在该表中放置特定类的虚函数的地址;在每个包含虚函数的类中,编译器都会放置一个指针,称为vpointer,指向这个对象的VTABLE,vpointer指针都在对象的相同位置(常常在对象的开头)。当通过基类指针做虚函数调用时(多态调用),编译器静态的插入能取得这个vpointer并在VTABLE表中查找函数地址的代码,这样就能调用正确的函数并引起动态捆绑的发生。
不懂虚函数就等于不懂得面向对象程序设计。
构造函数不能为虚函数,但是析构函数能够且常常必须是虚函数。
纯虚函数:
有时候希望基类仅仅作为其派生类的一个接口,而不希望用户实际地创建一个基类的对象,我们可以在基类中加入至少一个纯虚函数来使基类成为抽象类。纯虚函数使用关键字virtual,并且在其后面加上=0,如:virtual void f() =0;,编译器会阻止为抽象类生成对象,并在VTABLE中为函数保留一个位置,但这个特定位置中不存放地址,即VTABLE是不完全的。当继承一个抽象类时必须实现所有的纯虚函数,否则继承出的类也将是一个抽象类。
如果对一个对象进行向上类型转换,而不使用地址和引用,这个对象将被“切片”,直到剩下来的是最适合于目的的子对象,因为按值传递时,基对象的拷贝构造函数将被调用,构造函数初始化vpointer指向基类的VTABLE,并且只拷贝这个对象的基类部分,而不像使用指针或者引用那样简单地改变地址的内容。
编译器必须保证我们能够多态的通过基类调用函数,所以不允许我们改变重新定义过的虚函数的返回值。但存在特例:如果返回一个指向基类的指针或引用,则干函数的从新定义版本将会从基类返回的内容中返回一个指向派生类的指针或引用。