继承和多态

1.继承是什么

就是通过定义一个公共的类(基类),让需要公共信息的类(派生类)直接去继承;继承有单继承和多继承;

2.继承的本质:

本质就是复用

3.继承的语法

class person //基类

{

public:

int id;

int age;

string place;

};

class student:public person //public为继承方式 student 派生类

{

public:

int _studid;

int _major;

}

①继承方式:public > protected > private;

访问方式:public > protected > private;

最后函数和成员变量到底能不能看到取决于 继承方式和访问方式中"小"的呢个;

②在 C++ 中,如果派生类使用 class 定义,则默认继承方式是 private;使用 struct 定义,则默认继承方式是 public

③子类继承了基类的私有成员(在内存布局中存在),但不能直接访问它们(在派生类的作用域中不可见)。从"继承"一词在 C++ 标准语义上,私有成员是被继承但不可直接访问的,

④友元关系不能继承

⑤静态成员存储在静态区,继承后不仅仅属于父类,也属于子类;

4.赋值兼容转换(仅限于共有继承)

派生类的对象可以赋值给积累的对象/指针/引用;

class Base { /*...*/ };

class Derived : public Base { /*...*/ };

Derived d;

Base b = d; // 合法赋值
class Base { /*...*/ };

class Derived : public Base { /*...*/ };

Derived d;

Base b = d; // 合法赋值
Derived d;

Base& rb = d; // 合法绑定

这种赋值的本质是将派生类对象中的基类部分提取出来:

  • 对于对象赋值,会调用基类的拷贝构造函数
  • 对于指针/引用,只是改变了访问的视角,实际对象仍然是派生类对象
  • 反向赋值(基类赋给派生类)是不允许的
  • 如果基类有虚函数,通过基类指针/引用调用时会正确调用派生类的实现

5.重写(覆盖),重定义(隐藏 ) ,重载

①重载(overload)发生在同一作用域(如一个类内或全局),继承体系中不同作用域的同名函数不是重载,是隐藏

cpp 复制代码
// 1. 重载 Overload(同一作用域,函数名相同,参数不同)
class OverloadDemo {
public:
    void show(int x) { cout << "show(int): " << x << endl; }
    void show(double x) { cout << "show(double): " << x << endl; } // 重载
};

②重定义(隐藏) 基类和派生类的作用域不同,同名函数不构成重载,而是隐藏。如果基类有 func(int),派生类有 func(double),则派生类对象调用时,基类的版本会被隐藏,不能直接找到;

++***重写(覆盖)和重定义(隐藏)*都发生在继承中++

++重写是重定义的一种特殊情况(特指虚函数且原型相同++)

③重写(覆盖) 当派生类中定义的函数与基类函数原型完全相同时,若基类函数为虚函数,则构成覆盖(重写)以实现多态;若基类函数非虚函数,则派生类函数会隐藏基类同名函数。虚函数是在基类中用virtual关键字声明的成员函数,允许派生类对其进行覆盖,以实现运行时多态。

cpp 复制代码
// 2. 重写 Override(虚函数 + 继承 + 原型相同)
class Base {
public:
    virtual void func() { cout << "Base::func()" << endl; } // 虚函数
};

class Derived : public Base {
public:
    void func() override { cout << "Derived::func()" << endl; } // 重写(覆盖)
};

// 3. 重定义 Redefine(继承 + 同名隐藏)
class Base2 {
public:
    void test() { cout << "Base2::test()" << endl; }
    void test(int) { cout << "Base2::test(int)" << endl; }
};

class Derived2 : public Base2 {
public:
    // 重定义(隐藏基类所有 test 版本)
    void test() { cout << "Derived2::test()" << endl; }
};
 return 0;
}

++"函数原型完全相同"是指函数名、参数列表(参数类型、个数、顺序)以及是否为const成员函数都完全一致。++

++必须是基类中的该函数声明为虚函数,派生类中重写(override)才构成运行时多态,若只是派生类里写virtual而基类没有,不是覆盖++

④重载要求函数名相同、参数不同;重写要求原型相同(且基类函数是虚函数);重定义只需同名即可(参数可同可不同,会隐藏基类同名函数)

6.什么是虚函数

严格来说"被 virtual 修饰的函数"不一定就是虚函数(必须是非静态成员函数):构造函数不能是虚函数,普通成员函数才可以设为虚函数(此外还有如静态成员函数也不能是虚函数)。

7.为什么静态成员函数不能是虚函数?

静态成员函数没有 this 指针,无法通过虚表调用,因此不能是虚函数,且静态函数不参与重写(覆盖)

8.为什么基类析构函数建议设为虚函数?

确保用基类指针删除派生类对象时能正确调用派生类析构函数,避免内存泄漏。

9.多态调用条件

通过基类指针或引用调用虚函数,且该虚函数在派生类中被覆盖。

10.纯虚函数,抽象类

①什么是纯虚函数:

纯虚函数是在基类中声明为 virtual 返回类型 函数名(参数) = 0 的特殊虚函数,它没有(或不要求)基类实现,强制派生类必须重写该函数,从而使该类成为抽象类,无法实例化,用于定义统一的接口规范。

cpp 复制代码
class Shape { // 抽象类
public:
    virtual void draw() const = 0; // 纯虚函数
    
    // 纯虚函数可以有函数体(可选)
    virtual double area() const = 0;
    
    // 普通成员函数
    void printType() const {
        cout << "This is a shape" << endl;
    }
    
    virtual ~Shape() {} // 虚析构函数
};

②纯虚函数的作用

定义接口规范

强制派生类必须实现特定功能,确保所有子类都有统一的接口。

实现抽象类

使类成为抽象类,不能实例化,只能作为基类使用。

实现多态基础

通过基类指针/引用调用不同派生类的实现,实现运行时多态。

框架设计

在设计模式(如模板方法模式)中定义算法骨架,让子类实现具体步骤

③什么是抽象类

抽象类是包含至少一个纯虚函数的类,它不能直接实例化对象,主要用于作为基类定义接口。

④抽象类的作用

定义接口规范 - 强制派生类实现特定方法

实现多态基础 - 通过基类指针统一管理不同子类对象

cpp 复制代码
// 1. 声明抽象类(含纯虚函数)
class Animal {
public:
    virtual void makeSound() = 0;  // 纯虚函数
    virtual void move() = 0;        // 纯虚函数
    void sleep() {                  // 普通成员函数
        cout << "Sleeping..." << endl;
    }
    virtual ~Animal() {}
};

// 2. 派生类必须实现所有纯虚函数
class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Woof!" << endl;
    }
    void move() override {
        cout << "Running on four legs" << endl;
    }
};

11.基类派生类的析构构造顺序

初始化先父类后子类;

析构先子类后父类;(先父类析构,在调用父类,会出现"野指针")

注意:父类的析构不需要显式调用,会自动在所有析构调用后调用

12.多态

①什么是多态

多种形态,具体点就是为了去完成某个行为,当不同的对象去完成是会产生不同的效果;

②多态和继承的关系

多态依赖于继承,但不是所有继承都产生多态。

必要条件:继承 + 虚函数(覆盖) + 基类指针/引用调用

继承提供基础:派生类从基类派生,形成层次结构

多态实现方式:通过基类接口统一操作不同派生类对象

简言之:继承是多态的前提,多态是继承的高级应用。

③多态的条件

虚函数的重写(函数名、参数、返回值完全相同),重写它的实现

父类指针或者应用调用虚函数;

④多态的分类

14.面试题(为什么析构函数建议设置成虚函数以及虚函数为什么要重写)

析构函数设为虚函数,正是利用虚函数重写机制实现多态销毁:虚析构函数参与重写当基类析构函数声明为 virtual 时,派生类析构函数自动成为重写版本(即使不写 override)。

举个例子

cpp 复制代码
#include <iostream>
using namespace std;

class Animal {
public:
    Animal() { cout << "动物出生\n"; }
    virtual ~Animal() { cout << "动物去世\n"; }  // 虚析构函数
};

class Cat : public Animal {
private:
    char* name;
public:
    Cat() {
        name = new char[10];  // 猫独有资源:动态内存
        strcpy(name, "小花");
        cout << "小猫" << name << "出生\n";
    }
    
    ~Cat() {
        cout << "小猫" << name << "去世\n";
        delete[] name;  // 清理猫独有资源
    }
};

int main() {
    Animal* myPet = new Cat();  // 基类指针指向派生类对象
    delete myPet;  // 关键在这里!
    
    return 0;
}

情况1:虚析构函数(如上代码)

text

动物出生

小猫小花出生

小猫小花去世 ← 先调用Cat的析构函数,清理猫独有资源

动物去世 ← 再调用Animal的析构函数

✅ 资源完全释放

情况2:析构函数非虚(去掉virtual)

text

动物出生

小猫小花出生

动物去世 ← 只调用了Animal析构函数!

❌ 内存泄漏:name 指向的10字节内存永远无法释放,因为Cat::~Cat()根本没被调用!

这就是为什么:当通过基类指针删除派生类对象时,虚析构函数确保调用正确的派生类析构函数,避免资源泄漏。

复制代码
相关推荐
oioihoii2 小时前
C++多线程中join与detach机制深度解析
java·jvm·c++
初圣魔门首席弟子3 小时前
智能指针使用bug
c++·算法
闻缺陷则喜何志丹3 小时前
【组合数学 动态规划】P6870 [COCI2019-2020#5] Zapina|普及+
c++·数学·算法·动态规划·组合数学
CoderCodingNo3 小时前
【GESP】C++五级真题(贪心考点) luogu-B3872 [GESP202309 五级] 巧夺大奖
开发语言·c++
唐·柯里昂7983 小时前
[rk3566AI模型部署]泰山派buildroot部署yolov5 使用rknn_model_zoo
c语言·c++·笔记·yolo·rk3566·瑞芯微·泰山派
图形学爱好者_Wu3 小时前
每日一个C++知识点|const和static的区别
c++
郝学胜-神的一滴3 小时前
Linux 多线程编程:深入理解 `pthread_join` 函数
linux·开发语言·jvm·数据结构·c++·程序人生·算法
Trouvaille ~3 小时前
【C++篇】C++11新特性详解(二):右值引用与移动语义
c++·stl·基础语法·右值引用·默认成员函数·完美转发·移动语义
罗湖老棍子3 小时前
瑞瑞的木板(洛谷P1334 )
c++·算法·优先队列·贪心·哈夫曼树