10 继承与虚函数
2004字约7分钟
2023-08-06
10.1 Inheritance 继承
语法::public base_class_name
public
只是一种继承的方式,还有protect
,private
子类会拥有自己的以及父类的数据
public 继承(public inheritance): 在公有继承中,基类的 public 和 protected 成员的访问属性在子类中保持不变,子类可以访问基类的 public 成员和受保护成员,但不能直接访问基类的私有成员
通过子类的对象只能访问基类的 public 成员
通常用于"is-a"关系,表示派生类是基类的一种类型,派生类的对象可以替代基类的对象
protected 继承(protected inheritance): 在受保护继承中,基类的 public 和 protected 成员都以 protected 身份出现在子类中,基类的private 成员仍然是私有;子类成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员
通过子类的对象不能直接访问基类中的任何成员
通常用于实现继承的实现细节,不表示"is-a"关系,而是表示派生类需要基类的实现,但不希望公开基类的接口
private 继承(private inheritance): 在私有继承中,基类的 public 和 protected 成员都以 private 身份出现在子类中,基类的私有成员仍然是私有;子类成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员
通过子类的对象不能直接访问基类中的任何成员
基类 public 成员 | 基类 protected 成员 | 基类 private 成员 | |
---|---|---|---|
public 继承 | public 成员 | protected 成员 | 不能访问 |
protected 继承 | protected 成员 | protected 成员 | 不能访问 |
private 继承 | private 成员 | private 成员 | 不能访问 |
10.1.1 继承下的构造和析构
与复合下的构造和析构相似
构造是由内而外
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 虚函数
pure virtual 函数:
derived class 一定要重新定义 (override 覆写) 它;其没有定义只有声明
语法:
virtual xxxxxx =0;
virtual 函数:
derived class 可以重新定义 (override, 覆写) 它,且它已有默认定义
语法:
virtual xxxxxx;
non-virtual 函数:
不希望 derived class 重新定义 (override, 覆写) 它
10.3 继承 with virtual
例子:在 Windows 平台下用某个软件打开文件——分为好几步,但基本所有软件大多数操作都是一致的,只有一个操作如读取方式是不一样的
- 现有一个框架 Application framework 其写好了所有必要的函数,其中
Serialize()
就是一个 pure virtual 函数 - 使用这个框架写自己软件的打开文件,就继承这个框架,其中就需要自己 override 覆写
Serialize()
这个函数 - 在执行中,执行
myDoc.OnFileOpen();
中到Serialize()
时,是通过this
来指引到自己写的Serialize()
中去的
把关键动作延缓到子类再做,这是一个经典的设计模式——Template Method
10.4 缩略图
复合:
委托:
继承:
类中的元素: 变量名称 : 变量类型(与代码刚好相反
变量下面加下划线 表示
static
前面加一个
-
表示private
前面加一个
#
表示protected
前面加一个
+
表示public
(一般可以省略)
10.5 继承+复合
这种关系下的构造和析构与之前的类似
第一种:
构造由内到外 先 Base 再 Component
Derived 的构造函数首先调用 Base 的 default 构造函数,然后调用 Component 的 default 构造函数,然后才执行自己
Derived::Derived(…): Base(),Component() { … };
析构由外而内 先 Component 再 Base
Derived 的析构函数首先执行自己,然后调用 Component 的析构函数,然后调用 Base 的析构函数
Derived::~Derived(…){… /*~Component() ~Base()*/};
第二种:
同理构造由内到外,析构由外而内
10.6 继承+委托
10.6.1 例一 Observer
设计模式—— Observer
例如一串数据,可以用饼图来观察,也可以用条形图来观察,这种种的观察方式都是继承于 Observer
通过 vector<Observer> m_views;
来进行委托
当数据改变的时候,Observer 也需要更新,即 notify
函数,来将目前所有的观察者更新
10.6.2 例二 Composite
设计模式—— Composite
例如文件系统,文件夹里可以有文件夹(与自己相同的类),也可以有文件,其中文件就是最基本的 Primitive,而文件夹就是复合物 Composite
要达成目的,就可以再设计一个父类 Component ,文件和文件夹就继承于同一父类;
其中 Composite 要用委托到父类的方式 Component*
设计容器和操作——使其 Primitive 和 Composite 都可以适用
//父类 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 *clone()
,还有两个静态函数Image FindAndClone(imageType);
void addPrototype(Image *image){...}
子类中,创建一个静态的自己
_LAST
,把它放到父类的一个空间中,这样父类就可以找到新创建的子类private 的构造函数
LandSatImage()
中是addPrototype(this); //这里的 this 就是 _LAST
将自己的原型放到了父类中去子类中,准备一个
clone()
函数,父类通过调用找到的相应类型的 clone 函数来创建子类的副本这里的 clone 函数就不能用之前的那个构造函数来创建副本了——其会放到父类中去,所以创建一个新的构造函数
LandSatImage(int)
用传进一个无用参数(随便传个int型数据就好)来进行区分