1.定义为 virtual 的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。
2.在 C++ 中,通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象,这一事实是动态绑定的关键。
3.用引用(或指针)调用的虚函数在运行时确定,被调用的函数是引用(或指针)所指对象的实际类型所定义的。
4.成员默认为非虚函数,对非虚函数的调用在编译时确定。
5.除了构造函数之外,任意非 static 成员函数都可以是虚函数。保留字virtual只在类内部的成员函数声明中出现,不能用在类定义体外部出现的函数定义上。
6.派生类只能通过派生类对象访问其基类的 protected 成员,派生类对其基类类型对象的 protected 成员没有特殊访问权限。
7.每个派生类对象包含两个部分:从基类继承的成员和自己定义的成员。
8.尽管不是必须这样做,派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本。
9.派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)
10.一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实。派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做。
11.因为每个派生类对象都有基类部分,类可以访问共基类的public 和 protected 成员,就好像那些成员是派生类自己的成员一样。
12.C++ 中的函数调用默认不使用动态绑定。要触发动态绑定,满足两个条件:第一,只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,非虚函数不进行动态绑定;第二,必须通过基类类型的引用或指针进行函数调用。
13.基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型这是仅在运行时可知的)可能不同。这是 C++ 用以支持多态性的基石。对象是非多态的。
14.通过基类引用或指针调用基类中定义的函数时,如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数。如果调用虚函数,则直到运行时才能确定调用哪个函数,运行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本
15.非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。
16.在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这里可以使用作用域操作符:
17.派生类虚函数调用基类版本时,必须显式使用作用域操作符。
18.每个类控制它所定义的成员的访问。派生类可以进一步限制但不能放松对所继承的成员的访问。
19.(1)如果是公用继承,基类成员保持自己的访问级别:基类的 public 成员为派生类的 public 成员,基类的 protected 成员为派生类的 protected成员。
(2)如果是受保护继承,基类的 public 和 protected 成员在派生类中为protected 成员。
(3)如果是私有继承,基类的的所有成员在派生类中为 private 成员。
20.无论派生列表中是什么访问标号,所有继承 Base的类对 Base 中的成员具有相同的访问。派生访问标号将控制派生类的用户(用户就是对象??)对从 Base 继承而来的成员的访问:
class Base { public: void basemem();// public member protected: int i;// protected member // ... }; struct Public_derived : public Base { int use_base() { return i; } // ok: derived classes can access i // ... }; struct Private_derived : private Base { int use_base() { return i; } // ok: derived classes can access i }; Base b; Public_derived d1; Private_derived d2; b.basemem();// ok: basemem is public d1.basemem(); // ok: basemem is public in the derived class d2.basemem(); // error: basemem is private in the derived class
21.派生访问标号还控制来自非直接派生类的访问:
struct Derived_from Private : public Private_derived { // error: Base::i is private in Private_derived int use_base() { return i; } }; struct Derived_from_Public : public Public_derived { // ok: Base::i remains protected in Public_derived int use_base() { return i; } };
22.派生类可以恢复继承成员的访问级别,通过增加一个 using 声明:
class Base { public: std::size_t size() const { return n; } protected: std::size_t n; }; class Derived : private Base { public:// maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; // ... };
23.使用 class保留字定义的派生默认具有 private 继承,而用 struct保留字定义的类默认具有 public 继承
24.友元关系不能继承。基类的友元对派生类的成员没有特殊访问权限。如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的类。
25.如果基类定义 static 成员,则整个继承层次中只有一个这样的成员。
26.假定可以访问成员,则既可以通过基类访问 static 成员,也可以通过派生类访问 static 成员。一般而言,既可以使用作用域操作符也可以使用点或箭头成员访问操作符。
27.因为派生类对象也是基类对象,所以存在从派生类型引用到基类类型引用的自动转换,即,可以将派生类对象的引用转换为基类子对象的引用,对指针也类似。
28.没有从基类引用(或基类指针)到派生类引用(或派生类指针)的(自动)转换。
29.编译器不会自动将派生类型对象转换为基类类型对象。但是,一般可以使用派生类型对象对基类对象进行赋值或初始化
30.从基类到派生类的自动转换是不存在的。需要派生类对象时不能使用基类对象
31.构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。
32.某些类需要只希望派生类使用的特殊构造函数,这样的构造函数应定义为protected。
33.派生类的构造函数受继承关系的影响,每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类。
34.派生类的合成默认构造函数与非派生的构造函数只有一点不同:除了初始化派生类的数据成员之外,它还初始化派生类对象的基类部分。基类部分由基类的默认构造函数初始化。
35.派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。派生类应通过使用基类构造函数尊重基类的初始化意图,而不是在派生类构造函数函数体中对这些成员赋值。
36.一个类只能初始化自己的直接基类。
37.如果派生类定义了自己的复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分:
class Base { /* ... */ }; class Derived: public Base { public: // Base::Base(const Base&) not invoked automatically Derived(const Derived& d): Base(d) /* other member initialization */ { /*... */ } };
38.赋值操作符通常与复制构造函数类似:如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
39.析构函数的工作与复制构造函数和赋值操作符不同:派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。每个析构函数只负责清除自己的成员:
class Derived: public Base { public: // Base::~Base invoked automatically ~Derived(){ /* do what it takes to clean up derived members*/ } };
40.对象的撤销顺序与构造顺序相反:首先运行派生析构函数,然后按继承层次依次向上调用各基类析构函数。
41.如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类型的,则没有定义该行为。要保证运行适当的析构函数,基类中的析构函数必须为虚函数:
42.如果析构函数为虚函数,那么通过指针调用时,运行哪个析构函数将因指针所指对象类型的不同而不同:
Item_base *itemP = new Item_base; // same static and dynamic type delete itemP;// ok: destructor for Item_base called itemP = new Bulk_item;// ok: static and dynamic types differ delete itemP;// ok: destructor for Bulk_item called
43.如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,无论派生类显式定义析构函数还是使用合成析构函数,派生类析构函数都是虚函数。
44.即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数。
45.虚函数必须在基类和派生类中具有同样的形参。
46.如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本。
47.在继承情况下,派生类的作用域嵌套在基类作用域中。如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义。名字查找在编译时发生
48.基类类型的指针(引用或对象)只能访问对象的基类部分
49.与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。可以使用作用域操作符访问被屏蔽的基类成员
50.在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽:
51.理解 C++ 中继承层次的关键在于理解如何确定函数调用。确定函数调用遵循以下四个步骤:
(1)首先确定进行函数调用的对象、引用或指针的静态类型。
(2)在该类中查找函数,如果找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。如果不能在类或其相关基类中找到该名字,则调用是错误的。
(3)一旦找到了该名字,就进行常规类型检查,查看如果给定找到的定义,该函数调用是否合法。
(4)假定函数调用合法,编译器就生成代码。如果函数是虚函数且通过引用或指针调用,则编译器生成代码以确定根据对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。
52.含有(或继承)一个或多个纯虚函数的类是抽象基类。除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象。
53.在函数形参表后面写上 = 0 以指定纯虚函数
54.句柄类存储和管理基类指针。句柄的用户可以获得动态行为但无须操心指针的管理。
55.句柄类经常需要在不知道对象的确切类型时分配对象的新副本。解决这个问题的通用方法是定义虚操作进行复制,我们称将该操作命名为clone。