10 继承与虚函数

 

10.1 Inheritance 继承

语法::public base_class_name

public 只是一种继承的方式,还有protectprivate

子类会拥有自己的以及父类的数据

  • public 继承(public inheritance): 在公有继承中,基类的 public 和 protected 成员的访问属性在子类中保持不变,子类可以访问基类的 public 成员和受保护成员,但不能直接访问基类的私有成员

    通过子类的对象只能访问基类的 public 成员

    通常用于”is-a”关系,表示派生类是基类的一种类型,派生类的对象可以替代基类的对象

  • protected 继承(protected inheritance): 在受保护继承中,基类的 publicprotected 成员都以 *protected* 身份出现在子类中,基类的private 成员仍然是私有;子类成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员

    通过子类的对象不能直接访问基类中的任何成员

    通常用于实现继承的实现细节,不表示”is-a”关系,而是表示派生类需要基类的实现,但不希望公开基类的接口

  • private 继承(private inheritance): 在私有继承中,基类的 publicprotected 成员都以 *private* 身份出现在子类中,基类的私有成员仍然是私有;子类成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员

    通过子类的对象不能直接访问基类中的任何成员

  基类 public 成员 基类 protected 成员 基类 private 成员
public 继承 public 成员 protected 成员 不能访问
protected 继承 protected 成员 protected 成员 不能访问
private 继承 private 成员 private 成员 不能访问

10.1.1 继承下的构造和析构

与复合下的构造和析构相似

image-20230801150053963

  • 构造是==由内而外==

    Derived 的构造函数,编译器会自动先调用 Base 的 default 构造函数,再执行自己

    注意如果要调用 Base 的其他构造函数需要自己写出来

    Derived::Derived(…): Base() { … };

  • 析构是==由外而内==

    Derived 的析构函数会先执行自己,之后编译器调用 Base 的析构函数

    Derived::~Derived(…){ … /* ~Base() */ };

    注意:Base class 的 dtor 必需是 *virtual*

    否则下例会导致结束时只会调用 Base 的 dtor

    int main() {
     Base* ptr = new Derived();
     delete ptr; // 只会调用 Base 类的析构函数
     return 0;
    }
    

10.2 虚函数

image-20230801152023433

  • pure virtual 函数:

    derived class 一定要重新定义 (override 覆写) 它;其没有定义只有声明

    语法:virtual xxxxxx =0;

  • virtual 函数:

    derived class 可以重新定义 (override, 覆写) 它,且它已有默认定义

    语法:virtual xxxxxx;

  • non-virtual 函数:

    不希望 derived class 重新定义 (override, 覆写) 它

10.3 继承 with virtual

例子:在 Windows 平台下用某个软件打开文件——分为好几步,但基本所有软件大多数操作都是一致的,只有一个操作如读取方式是不一样的

image-20230801154005427

  1. 现有一个框架 Application framework 其写好了所有必要的函数,其中 Serialize() 就是一个 pure virtual 函数
  2. 使用这个框架写自己软件的打开文件,就继承这个框架,其中就需要自己 override 覆写 Serialize() 这个函数
  3. 在执行中,执行 myDoc.OnFileOpen(); 中到 Serialize() 时,是通过 this 来指引到自己写的 Serialize() 中去的

把关键动作延缓到子类再做,这是一个经典的设计模式——Template Method

10.4 缩略图

  • 复合:image-20230802084858622

  • 委托:image-20230802085101744

  • 继承:image-20230802085210589

  • 类中的元素:image-20230802085810812 变量名称 : 变量类型(与代码刚好相反

    • 变量下面加下划线 表示 static
    • 前面加一个 - 表示 private

    • 前面加一个 # 表示 protected

    • 前面加一个 + 表示 public(一般可以省略)

10.5 继承+复合

这种关系下的构造和析构与之前的类似

  • 第一种:

    image-20230801161457590

    • 构造==由内到外== 先 Base 再 Component

      Derived 的构造函数首先调用 Base 的 default 构造函数,然后调用 Component 的 default 构造函数,然后才执行自己

      Derived::Derived(…): Base(),Component() { … };

    • 析构==由外而内== 先 Component 再 Base

      Derived 的析构函数首先执行自己,然后调用 Component 的析构函数,然后调用 Base 的析构函数

      Derived::~Derived(…){… /*~Component() ~Base()*/};

  • 第二种:

    image-20230801162202797

    同理构造==由内到外==,析构==由外而内==

10.6 继承+委托

10.6.1 例一 Observer

设计模式—— Observer

例如一串数据,可以用饼图来观察,也可以用条形图来观察,这种种的观察方式都是继承于 Observer

image-20230801163932926

通过 vector<Observer> m_views; 来进行委托

当数据改变的时候,Observer 也需要更新,即 notify 函数,来将目前所有的观察者更新

10.6.2 例二 Composite

设计模式—— Composite

例如文件系统,文件夹里可以有文件夹(与自己相同的类),也可以有文件,其中文件就是最基本的 Primitive,而文件夹就是复合物 Composite

image-20230802082524919

要达成目的,就可以再设计一个父类 Component ,文件和文件夹就继承于同一父类;

其中 Composite 要用委托到父类的方式 Component* 设计容器和操作——使其 PrimitiveComposite 都可以适用

//父类 Component
class Component
{
private:
    int value;
public:
    Component(int val)	{value = val;}  
    virtual void add( Component* ) {} //虚函数
};

//复合物 Composite
class Composite 
    : public Component
{
	vector <Component*> c;  
public:
	Composite(int val) : Component(val) {}

	void add(Component* elem)
	{
		c.push_back(elem);
	}
	
}

//基本类 Primitive
class Primitive
    : public Component
{
public:
	Primitive(int val): Component(val) {}
};

component中add是虚函数(且是空函数),不能是纯虚函数——Primitive 不会 override add函数(最基本的单位,不能 add 了),而 Composite 需要 override add函数

10.6.3 例三 Prototype

设计模式—— Prototype

框架(父类)要创建未来才会出现的子类——要求子类要创建一个自己当作原型 Prototype 让框架(父类)来找到并创建 FindAndClone

补充:当一个子类继承自父类时,它可以被视为是父类的一种类型,因此可以使用父类的指针或引用来引用子类的对象;

这种用父类的指针或引用来处理子类对象的方式称为——*向上转型 ** *Upcasting

image-20230802163941216

  1. 父类中,有一个存放原型的数组,有纯虚函数 Image *clone(),还有两个静态函数 Image FindAndClone(imageType); void addPrototype(Image *image){...}

  2. 子类中,创建一个静态的自己 _LAST ,把它放到父类的一个空间中,这样父类就可以找到新创建的子类

    ==private 的构造函数== LandSatImage() 中是 addPrototype(this); //这里的 this 就是 _LAST 将自己的原型放到了父类中去

  3. 子类中,准备一个 clone()函数,父类通过调用找到的相应类型的 clone 函数来创建子类的副本

    这里的 clone 函数就不能用之前的那个构造函数来创建副本了——其会放到父类中去,所以创建一个新的构造函数 LandSatImage(int) 用传进一个无用参数(随便传个int型数据就好)来进行区分