14、继承与多态
本文主要介绍下多态的概念。
继承与抽象类
多态是面向对象的核心知识点,在C++中意味着调用对象成员函数时,会根据对象的真实类型来执行不同的函数,从而产生不同的行为。
- 比如同样是人,不同人的声音不相同。
- 比如同样是公司,不同公司的经营业务也不同。
这就可以就多态来解释。
那怎么实现多态,看这段代码,先定义一个People
类:
class People {
public:
virtual void Speak() { std::cout << "People Speak \n"; }
};
注意这里面的函数使用了virtual
修饰,用virtual
修饰的函数表示虚函数,带虚函数的类可以称之为父类,有父类自然可以派生出子类,子类可以覆盖父类的行为。
这里再定义两个类,一个男人类
,一个女人类
class MalePeople : public People {
public:
void Speak() { std::cout << "MalePeople Speak \n"; }
};
class FemalePeople : public People {
public:
void Speak() { std::cout << "FemalePeople Speak \n"; }
};
在MalePeople
和FemalePeople
使用了冒号,表示继承,冒号后面的public
表示继承的权限。
所以上面的代码的含义是:
MalePeople
以public
权限继承了People
,并覆盖父类People
的Speak
行为。
FemalePeople
以public
权限继承了People
,并覆盖父类People
的Speak
行为。
再看一段使用多态的代码:
int main() {
People *p1 = new People();
People *p2 = static_cast<People *>(new MalePeople());
People *p3 = static_cast<People *>(new FemalePeople());
p1->Speak(); // People Speak
p2->Speak(); // MalePeople Speak
p3->Speak(); // FemalePeople Speak
delete p3;
delete p2;
delete p1;
}
p1、p2、p3
都是People
的实例,但是通过他们的实例调用相同的函数却产生了不同的行为,这就是多态。
注意两点,想要实现上述的多态行为:
- 父类相应的函数一定要使用
virtual
修饰 - 一定要父类的指针或引用指向子类对象
继承权限
共有三种继承权限:
public
继承
- 父类中所有
public
成员在子类中为public
属性 - 父类中所有
protected
成员在子类中为protected
属性 - 父类中所有
private
成员在子类中不可访问
protected
继承
- 父类中所有
public
成员在子类中为protected
属性 - 父类中所有
protected
成员在子类中为protected
属性 - 父类中所有
private
成员在子类中不可访问
private
继承
- 父类中所有
public
成员在子类中为private
属性 - 父类中所有
protected
成员在子类中为private
属性 - 父类中所有
private
成员在子类中不可访问
大体可以理解为:
- 父类成员在子类中的访问权限不会高于指定的继承权限。
- 父类中的
private
成员在子类中使用不可访问。
然而平时开发过程中一般都会使用public
继承,其他的继承方式很少。
纯虚函数
在C++中,还有个纯虚函数的概念,就是在virtual
修饰的基础上加个=0
,比如:
class People {
public:
virtual void Speak() = 0;
};
这里的Speak
就是纯虚函数,含有纯虚函数的类叫抽象类,同时规定抽象类不允许被实例化,只能通过子类实例化,举例:
int main() {
People *p1 = new People(); // compile error
People *p2 = static_cast<People *>(new MalePeople());
People *p3 = static_cast<People *>(new FemalePeople());
}
多继承
就是子类继承了多个父类,比如一个男子篮球运动员,那就可以定义两个父类,一个MalePeople
类,一个BasketballPlayer
类,那如果想要定义男子篮球运动员
类,可以定义一个MaleBasketballPlayer
类,继承MalePeople
和BaskeballPlayer
,代码如下:
class MalePeople {
public:
void Speak() { std::cout << "MalePeople Speak \n"; }
};
class BasketBallPlayer {
public:
void Play() { std::cout << "Play Basketball \n"; }
};
class MaleBasketBallPlayer : public MalePeople, public BasketBallPlayer {};
和单继承方式差不多,只是用相同的语法在后面再派生多个即可。
虚继承
暂时无法在飞书文档外展示此内容
普通的继承就是非虚继承,如图, 非虚继承时,显然D会继承两次A,内部就会存储两份A的数据浪费空间,而且还有二义性,D调用A的方法时,由于有两个A,究竟时调用哪个A的方法呢,编译器也不知道,就会报错,所以有了虚继承,解决了空间浪费以及二义性问题。
暂时无法在飞书文档外展示此内容
在虚拟继承下,只有一个共享的基类子对象被继承,而无论该基类在派生层次中出现多少次。共享的基类子对象被称为虚基类。在虚继承下,基类子对象的复制及由此而引起的二义性都被消除了。
如何使用虚继承?
在继承的时候使用virtual
关键字,代码如下:
struct Base {
virtual void Func() { printf("Base Func\n"); }
};
struct BaseA : virtual public Base {
virtual void Func() { printf("BaseA Func\n"); }
};
struct BaseB : virtual public Base {
virtual void Func() { printf("BaseB Func\n"); }
};
struct Derive : public BaseB, public BaseA {
void Func() override { printf("Derive Func \n"); }
};
注意,为了易于观察,上面所有的父类都没有定义析构函数,正常父类的析构函数一定要设置成virtual
。
练习
- 多态只有这一种方式吗?
- 为什么一定要通过指针或引用方式才能达到多态的目的?
- 为什么析构函数一定要设置成virtual?
- 构造函数可以为虚函数吗?
- 多态的原理是怎么样的?
- 不同继承方式下,类对象的布局是什么结构?
