C++基础知识(六:继承)

简介: 多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。


目录

多态概念与实践

1. 多态的基础

2. 函数重载(静态多态)

3. 动态多态

4. 虚析构函数

【1】多态的前提

【2】虚函数(virtual)

【4】虚析构函数

示例:


多态概念与实践

多态是面向对象编程的四大基本原则之一,它让程序能够以统一的接口处理不同的对象类型,从而实现了接口与实现分离,提高了代码的灵活性和复用性。多态主要体现在两个层面:静态多态(编译时多态,如函数重载)和动态多态(运行时多态,主要通过虚函数实现)。

1. 多态的基础

  • 继承与多态:继承是实现多态的前提条件。通过继承,子类可以继承父类的属性和方法,并在此基础上进行扩展或重写,从而实现不同类之间相同接口的多种行为。

2. 函数重载(静态多态)

  • 定义:在同一作用域内,函数名相同但参数列表不同(类型、数量或顺序不同),实现不同的功能,这就是静态多态,编译器在编译时期就能确定调用哪个函数。

3. 动态多态

  • 虚函数:通过在基类中声明虚函数(使用virtual关键字),允许子类重写(override)该函数,实现运行时的多态性。虚函数的关键在于,它允许通过基类指针或引用调用子类的实现。
  • 虚函数表(V-Table):每个包含虚函数的类都有一个虚函数表,存储虚函数的地址。对象实例包含一个指向虚函数表的指针,该指针在对象创建时初始化。当子类重写了虚函数,子类的虚函数表会在相应位置存储子类的函数地址。
  • 实现机制:在运行时,通过基类指针调用虚函数时,实际调用的是该指针所指向对象的虚函数表中的函数地址,从而实现动态绑定,达到多态的效果。

4. 虚析构函数

  • 重要性:当使用基类指针删除派生类对象时,如果没有虚析构函数,只会调用基类的析构函数,导致派生类特有的资源无法被正确释放。因此,为保证多态对象能被正确销毁,基类的析构函数应声明为虚函数。
  • 工作原理:虚析构函数确保通过基类指针删除对象时,会首先调用派生类的析构函数,然后再调用基类的析构函数,从而彻底释放派生类的资源。

【1】多态的前提

  1. 继承:多态的实现通常基于类的继承关系。一个类(子类)继承自另一个类(父类),子类可以继承父类的属性和方法,并且可以覆盖或扩展父类的行为。
  2. 虚函数:在基类中定义虚函数(使用virtual关键字),是实现动态多态的关键。虚函数允许子类重写父类中的同名函数,这样在运行时,根据对象的实际类型来决定调用哪个版本的函数。
  3. 父类指针或引用:通过父类的指针或引用来指向子类的对象,这是多态调用的常见方式。这样,即使使用的是父类的接口,也能调用到子类重写后的方法,实现动态行为。
  4. 方法重写:子类在继承父类的过程中,可以重写(override)父类中的虚函数,提供自己的实现。这是多态表现不同行为的基础。

在子类中重写父类的虚函数就是函数重写的过程,可以实现多态

【2】虚函数(virtual)

只要基类中是虚函数,后面的所有子类中该函数都是虚函数

常规来说,在继承时,给父类中的函数加上virtual关键字,定义成一个虚函数,

在子类中,可以对父类中的虚函数进行函数重写(override)

只要有虚函数的类,都会有一个虚函数表和一个虚(函数表)指针

虚指针是指向虚函数表的指针;

虚函数表,存储所有的虚函数的信息

虚函数表:保存所有虚函数的入口地址,每一个包含虚函数的类都会有一张虚函数表

如果发生继承关系,子类先复制父类的虚函数表,如果子类对某个虚函数重写,就去更改虚函数表中,该函数的入口地址

虚函数表指针:指向虚函数表的指针,父类中有一个虚函数表指针,子类中的虚函数表指针是从父类中继承下来的虚函数表指针,指向子类的虚函数表(虚函数表指针存在类中的第一个位置)

image.gif 编辑

【4】虚析构函数

由于实现多态,需要使用父类的指针,指向子类的空间,父类指针可以操作的空间,只有父类自己的部分,所以,在delete父类指针时,并不会释放调子类的空间

解决方法:给基类(父类)的析构函数前面加上virtual关键字,只要基类是虚析构函数,后面继承的所有子类都是虚析构函数,虚析构函数会引导父类的指针,释放掉子类的空间

示例:

#include <iostream>
using namespace std;
class Person {
public:
    virtual ~Person() { cout << "Person的析构" << endl; }
    virtual void play() { cout << "吃饭" << endl; }
    virtual void fun() { cout << "fun" << endl; }
};
class Student : public Person {
public:
    void play() override { cout << "打游戏" << endl; }
    ~Student() { cout << "Student的析构" << endl; }
};
class SubStudent : public Student {
public:
    void play() override { cout << "第二次继承" << endl; }
    void fun() override { cout << "fun的第二次继承" << endl; }
};
int main() {
    Person *p = new Student;
    p->play(); // 通过父类指针调用子类重写的play方法
    delete p;  // 正确释放资源,由于虚析构函数的存在,Student的析构也会被调用
    // 下面是注释掉的代码示例,展示了多态的其他用法
    // Person *p1 = new SubStudent;
    // p1->play();  // 第二次继承
    // p1->fun();   // fun的第二次继承
    return 0;
}

image.gif

image.gif 编辑

可以看到这里虽然是Person类型的指针但是调用出来的函数确实student的play()。

相关文章
|
1月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
80 11
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
C++
C++番外篇——虚拟继承解决数据冗余和二义性的原理
C++番外篇——虚拟继承解决数据冗余和二义性的原理
39 1
|
1月前
|
安全 编译器 程序员
C++的忠实粉丝-继承的热情(1)
C++的忠实粉丝-继承的热情(1)
18 0
|
1月前
|
编译器 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
C++入门11——详解C++继承(菱形继承与虚拟继承)-2
29 0
|
1月前
|
程序员 C++
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
C++入门11——详解C++继承(菱形继承与虚拟继承)-1
32 0
|
2月前
|
C++
C++(二十)继承
本文介绍了C++中的继承特性,包括公有、保护和私有继承,并解释了虚继承的作用。通过示例展示了派生类如何从基类继承属性和方法,并保持自身的独特性。此外,还详细说明了派生类构造函数的语法格式及构造顺序,提供了具体的代码示例帮助理解。
|
2月前
|
C++
c++继承层次结构实践
这篇文章通过多个示例代码,讲解了C++中继承层次结构的实践应用,包括多态、抽象类引用、基类调用派生类函数,以及基类指针引用派生类对象的情况,并提供了相关的参考链接。
|
3月前
|
安全 Java 编译器
|
4月前
|
存储 Java 程序员
【c++】继承深度解剖
【c++】继承深度解剖
36 1