第15章 面向对象编程
面向对象编程基于数据抽象、继承、动态绑定三个基本概念。在C++中,用类进行数据抽象,用类派生从一个类继承另一个类:派生类继承基类的成员。动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数。
(面向对象的三大特征:封装、继承、多态)
-15.1 面向对象编程:概述
面向对象编程的关键思想是多态性,在C++中,多态性仅用于通过继承而相关联的类型的引用或指针。
--1. 继承
非虚函数:基类定义,派生类继承
虚函数:基类先定义,派生类重新定义
纯虚函数:基类不定义,每个派生类必须定义
--2. 动态绑定
通过基类的引用(或指针)调用虚函数时,发生动态绑定。引用(或指针)既可以指向基类对象也可以指向派生类对象。用引用(或指针)调用的虚函数在运行时确定。
-15.2 定义基类和派生类
--15.2.1 定义基类
继承层次的根类一般都要定义虚析构函数。
---1. 基类成员函数
非虚函数的调用在编译时确定,虚函数的调用在运行时确定。
除构造函数外,任意非static成员函数都可以是虚函数。
---2. 访问控制和继承
public:都可以访问
protected:this、友元、派生类可以访问
private:this、友元可以访问,派生类不能访问
--15.2.2 protected成员
派生类只能通过派生类对象访问其基类的protected成员,不能通过基类对象访问其基类的protected成员。
例如,void Bulk_item::fun(Item_base& base, Bulk_item& derive);可以通过derive访问Item_base类的protected成员,但不能通过base访问Item_base类的protected成员。
--15.2.3 派生类
class classname: access-label base-class
access-label决定了对继承成员的访问权限。如果想要继承基类的接口,则应该进行public派生。
---1. 定义派生类
---2. 派生类和虚函数
如果派生类没有重定义某个虚函数,则使用基类中定义的版本。
派生类型必须对想要重定义的每个继承成员进行声明,且声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(或指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生类的引用(或指针)。
----3. 派生类对象包含基类对象作为子对象
派生类对象:派生类本身定义的(非static)成员 + 基类(非static)成员。
---4. 派生类中的函数可以使用基类的成员
类可以访问其基类的public和protected成员。
---5. 用作基类的类必须是已定义的
---6. 用派生类作基类
最底层的派生类对象包含其每个直接基类和间接基类的子对象。
---7. 派生类的声明
如果需要声明但并不实现一个派生类,则声明包含类名但不包含派生类列表。
--15.2.4 virtual与其他成员函数
动态绑定发生的条件:
①只有指定为虚函数的成员函数才能进行动态绑定
②必须通过基类类型的引用或指针进行函数调用
---1. 从派生类到基类的转换
double print_total(const Item_base&, size_t);
基类类型的引用或指针可以引用基类类型对象,也可以引用派生类类型对象。无论实际对象具有哪种类型,编译器都将它当作基类类型对象。
---2. 可以在运行时确定virtual函数的调用
引用和指针的静态类型(基类)与动态类型(基类或派生类)可以不同--多态性。
对象是非多态的--对象类型已知且不变,对象的动态类型总是与静态类型相同。
只有通过引用或指针调用,虚函数才在运行时确定。
---3. 在编译时确定非virtual调用
非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定。
---4. 覆盖虚函数机制
目的:为了派生类虚函数调用基类中的版本。
注意:派生类虚函数调用基类版本时,必须显式使用作用域操作符。如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归。
---5. 虚函数和默认实参
虚函数也可以有默认实参,在函数调用时:
①给定实参:该值在编译时确定;
②省略实参:由调用该函数的类型确定,与对象的动态类型无关。通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值;如果通过派生类的指针活引用调用虚函数,则默认实参是在派生类的版本中声明的值。
--15.2.5 公用/私有和受保护的继承
派生访问标号将控制派生类的用户对从Base继承而来的成员的访问。
---1. 接口继承和实现继承
public继承:接口继承
protected或private继承:实现继承
---2. 去除个别成员
size在Base中为public,但在Derived中为private。为了使size在Derived中成为public,可以在Derived的public部分增加一个using声明。
class Derived:private Base
{
public:
using Base::size;
protected:
using Base::n;
// ...
}
---3. 默认继承保护级别
使用class保留字定义的派生类默认具有private继承,而用struct保留字定义的派生类默认具有public继承。
--15.2.6 友元关系与继承
友元关系不能继承,基类的友元对派生类的成员没有特殊的访问权限。
--15.2.7 继承与静态成员
无论从基类派生出多少个派生类,每个static成员只有一个实例。
假定可以访问成员,则既可以通过基类也可以通过派生类访问static成员。
-15.3 转换与继承
可以将派生类对象的引用转换为基类子对象的引用,对指针也类似。
没有从派生类型对象到基类类型对象的直接转换。
--15.3.1 派生类到基类的转换
----1. 引用转换不同于转换对象
将派生类对象传给希望接受基类引用的函数:引用直接绑定到该对象
将派生类对象传给希望接受基类对象(而不是引用)的函数:派生类对象的基类部分被复制到形参
----2. 用派生类对象对基类对象进行初始化或赋值
①基类可能显式定义了将派生类对象复制或赋值给基类对象的含义(不常见)
②基类一般(显式或隐式)定义自己的复制构造函数和赋值操作符,形参是基类类型的const引用(常见)
----3. 派生类到基类转换的可访问性
public继承:可访问
private或protected继承:不能将派生类对象转换为基类对象
---15.3.2 基类到派生类的转换
基类到派生类的自动转换是不存在的。如果知道从基类到派生类的转换是安全的,可以使用static_cast强制编译器进行转换,或者用dynamic_cast申请在运行时进行检查。
-15.4 构造函数和复制控制
构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员。
--15.4.1 基类构造函数和复制控制
某些类需要只希望派生类使用的特殊构造函数,应定义为protected。
--15.4.2 派生类构造函数
---1. 合成的派生类默认构造函数
调用基类的默认构造函数初始化对象的基类部分。
---2. 定义默认构造函数
---3. 向基类构造函数传递实参
派生类构造函数通过将基类包含在构造函数初始化列表中来间接初始化继承成员。
---4. 在派生类构造函数中使用默认实参
---5. 只能初始化直接基类
一个类只能初始化自己的直接基类,原因是每个类都定义了自己接口。