1. 面向对象的程序设计是基于三个基本概念:数据抽象、继承和动态绑定。
在C++ 在,凭借一流的数据抽象,随着一类从一个类派生还继承:派生类的成员继承基类。决定是使用基类中定义的函数还是派生类中定义的函数。在C++ 中,多态性仅用于通过继承而相关联的类型的引用或指针。
2. 继承
通过继承我们能够定义这种类,它们对类型之间的关系建模,共享公共的东西,只特化本质上不同的东西。派生类(derivedclass)能够继承基类(baseclass)定义的成员,派生类能够无须改变而使用那些与派生类型详细特性不相关的操作,派生类能够重定义那些与派生类型相关的成员函数。将函数特化。考虑派生类型的特性。最后,除了从基类继承的成员之外。派生类还能够定义很多其它的成员。
在C++ 中,基类必须指出希望派生类重写哪些函数。定义为 virtual 的函数是基类期待派生类又一次定义的,基类希望派生类继承的函数不能定义为虚函数。
3. 动态绑定
动态绑定我们能够编敲代码使用继承层次中随意类型的对象,无须关心对象的详细类型。使用这些类的程序无须区分函数是在基类还是在派生类中定义的。
在C++ 中。通过基类的引用(或指针)调用虚函数时,发生动态绑定。
引用(或指针)既能够指向基类对象也能够指向派生类对象,这一事实是动态绑定的关键。
用引用(或指针)调用的虚函数在执行时确定。被调用的函数是引用(或指针)所指对象的实际类型所定义的。
4. 基类成员函数
保留字 virtual 的目的是启用动态绑定。成员默觉得非虚函数,对非虚函数的调用在编译时确定。
为了指明函数为虚函数,在其返回类型前面加上保留字virtual。除了构造函数之外,随意非static 成员函数都能够是虚函数。
保留字仅仅在类内部的成员函数声明中出现。不能用在类定义体外部出现的函数定义上。
5. 訪问控制和继承
在基类中,public和private 标号具有普通含义:用户代码能够訪问类的public 成员而不能訪问private 成员。private成员仅仅能由基类的成员和友元訪问。派生类对基类的public 和private 成员的訪问权限与程序中随意其它部分一样:它能够訪问public 成员而不能訪问private 成员。
protected 成员能够被派生类对象訪问但不能被该类型的普通用户訪问。能够觉得protected 訪问标号是private 和public 的混合:
- 像private 成员一样。protected成员不能被类的用户訪问。
- 像public 成员一样,protected成员可被该类的派生类訪问。
- 此外。protected还有还有一重要性质:
- 派生类仅仅能通过派生类对象訪问其基类的protected 成员,派生类对其基类类型对象的protected 成员没有特殊訪问权限。
6. 派生类
为了定义派生类,使用类派生列表指定基类。
类派生列表指定了一个或多个基类,具有例如以下形式:
class classname:access-label base-class
这里access-label 是public、protected或private。base-class是已定义的类的名字。
类派生列表能够指定多个基类。
继承单个基类是为常见。
• 假设是公用继承。基类成员保持自己的訪问级别:基类的public 成员为派生类的public 成员。基类的protected 成员为派生类的protected 成员。
• 假设是受保护继承。基类的public 和protected 成员在派生类中为 protected 成员。
• 假设是私有继承,基类的的全部成员在派生类中为private 成员。
比如,考虑以下的继承层次:
class Base { public: void basemem(); //public member protected: int i; // protectedmember // ... }; struct Public_derived: public Base { int use_base() {return i; } // ok: derived classes can access i // ... }; structPrivate_derived : private Base { int use_base() {return i; } // ok: derived classes can access i };
不管派生列表中是什么訪问标号,全部继承 Base 的类对Base 中的成员具有同样的訪问。派生訪问标号将控制派生类的用户对从Base 继承而来的成员的訪问:
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 thederived class // 派生訪问标号还控制来自非直接派生类的訪问: struct Derived_fromPrivate : public Private_derived { // error: Base::i isprivate in Private_derived int use_base() {return i; } }; structDerived_from_Public : public Public_derived { // ok: Base::iremains protected in Public_derived int use_base() {return i; } };
7. 用作基类的类必须是已定义的
已定义的类才干够用作基类。假设已经声明了Item_base 类,但未定义它,则不能用Item_base 作基类:
class Item_base; //declared but not defined // error: Item_base mustbe defined class Bulk_item :public Item_base { ... };
这一限制的原因应该非常easy明确:每一个派生类包括而且能够訪问其基类的成员。为了使用这些成员,派生类必须知道它们是什么。
这一规则暗示着不可能从类自身派生出一个类。
8. 不论什么能够在基类对象上运行的操作也能够通过派生类对象使用。
由于能够使用基类类型的指针或引用来引用派生类型对象,所以,使用基类类型的引用或指针时,不知道指针或引用所绑定的对象的类型:基类类型的引用或指针能够引用基类类型对象。也能够引用派生类型对象。不管实际对象具有哪种类型,编译器都将它当作基类类型对象。
将派生类对象当作基类对象是安全的,由于每一个派生类对象都拥有基类子对象。
并且,派生类继承基类的操作
基类类型引用和指针的关键点在于静态类型(在编译时可知的引用类型或指针类型)和动态类型(指针或引用所绑定的对象的类型这是仅在执行时可知的)可能不同。
9. C++ 中的多态性
引用和指针的静态类型与动态类型能够不同,这是C++ 用以支持多态性的基石。
通过基类引用或指针调用基类中定义的函数时。我们并不知道运行函数的对象的确切类型,运行函数的对象可能是基类类型的。也可能是派生类型的。
假设调用非虚函数。则不管实际对象是什么类型,都执行基类类型所定义的函数。假设调用虚函数,则直到执行时才干确定调用哪个函数,执行的虚函数是引用所绑定的或指针所指向的对象所属类型定义的版本号。 从编写代码的角度看我们无需操心。仅仅要正确地设计和实现了类。不管实际对象是基类类型或派生类型,操作都将完毕正确的工作。 还有一方面,对象是非多态的——对象类型已知且不变。
对象的动态类型总是与静态类型同样,这一点与引用或指针相反。执行的函数(虚函数或非虚函数)是由对象的类型定义的。
仅仅有通过引用或指针调用,虚函数才在执行时确定。仅仅有在这些情况下。直到执行时才知道对象的动态类型。
在编译时确定非 virtual 调用
10. 覆盖虚函数机制
在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本号,这里能够使用作用域操作符:
Item_base *baseP =&derived; // calls version from the base class regardless of the dynamic type of baseP double d =baseP->Item_base::net_price(42);
11. 为什么会希望覆盖虚函数机制?
最常见的理由是为了派生类虚函数调用基类中的版本号。
在这样的情况下,基类版本号能够完毕继承层次中全部类型的公共任务,而每一个派生类型仅仅加入自己的特殊工作。
派生类虚函数调用基类版本号时。必须显式使用作用域操作符。假设派生类函数忽略了这样做,则函数调用会在执行时确定并且将是一个自身调用,从而导致无穷递归。
12. 继承与组合
继承层次的设计本身是个复杂的主题,已超出本书的范围。可是,有一个重要的设计指南很基础。每一个程序猿都应该熟悉它。
定义一个类作为还有一个类的公用派生类时,派生类应反映与基类的“是一种(IsA)”关系。
在书店样例中,基类表示按规定价格销售的书的概念,Bulk_item是一种书,但具有不同的定价策略。
类型之间还有一种常见的关系是称为“有一个(HasA)”的关系。书店样例中的类具有价格和ISBN。
通过“有一个”关系而相关的类型暗含有成员关系,因此,书店样例中的类由表示价格和ISBN 的成员组成。
13. 去除个别成员
假设进行private 或protected 继承,则基类成员的訪问级别在派生类中比在基类中更受限:
class Base { public: std::size_t size()const { return n; } protected: std::size_t n; }; class Derived :private Base { . . . };
派生类能够恢复继承成员的訪问级别,但不能使訪问级别比基类中原来指定的更严格或更宽松。
在这一继承层次中,size在Base 中为public,但在Derived 中为private。为了使size 在Derived 中成为public。可以在Derived 的public部分添加一个using 声明。例如以下这样改变Derived 的定义。可以使 size 成员可以被用户訪问,并使n 可以被从Derived 派生的类訪问:
class Derived :private Base { public: // maintain access levels for members related to the size of the object using Base::size; protected: using Base::n; // ... };
14. 默认继承保护级别
struct 和class 保留字定义的类具有不同的默认訪问级别。相同,默认继承訪问级别依据使用哪个保留字定义派生类也不相同。使用class 保留字定义的派生默认具有private 继承,而用struct 保留字定义的类默认具有public 继承:
class Base { /* ...*/ };
struct D1 : Base { /*... */ }; // public inheritance bydefault
class D2 : Base { /*... */ }; // private inheritance bydefault
15. 友元关系与继承
友元关系不能继承。
基类的友元对派生类的成员没有特殊訪问权限。
假设基类被授予友元关系。则仅仅有基类具有特殊訪问权限。该基类的派生类不能訪问授予友元关系的类。
每一个类控制对自己的成员的友元关系:
class Base { friend class Frnd; protected: int i; }; // Frnd has no accessto members in D1 class D1 : publicBase { protected: int j; }; class Frnd { public: int mem(Base b) {return b.i; } // ok: Frnd is friend to Base int mem(D1 d) {return d.i; } // error: friendship doesn't inherit }; // D2 has no accessto members in Base class D2 : publicFrnd { public: int mem(Base b) {return b.i; } // error: friendship doesn't inherit };
16. 继承与静态成员
不管从基类派生出多少个派生类,每一个static 成员仅仅有一个实例。
static 成员遵循常规訪问控制:假设成员在基类中为private。则派生类不能訪问它。假定能够訪问成员。则既能够通过基类訪问static 成员,也能够通过派生类訪问static 成员。
一般而言,既能够使用作用域操作符也能够使用点或箭头成员訪问操作符。
struct Base { static voidstatmem(); // public by default }; struct Derived : Base{ void f(constDerived&); }; void Derived::f(constDerived &derived_obj) { Base::statmem(); // ok: Base defines statmem Derived::statmem(); // ok: Derived in herits statmem // ok: derivedobjects can be used to access static from base derived_obj.statmem(); // accessed through Derived object statmem(); // accessed through this class