当前位置: 首页 > article >正文

C++基础面试题 | 什么是C++中的虚继承?

在这里插入图片描述

文章目录

    • 回答重点
      • 菱形继承问题
      • 虚继承解决菱形继承问题
      • 虚继承的二义性解决
    • 虚继承总结
    • 拓展知识:virtual关键字的用法
      • 1. 虚函数 (Virtual Function)
      • 2. 纯虚函数 (Pure Virtual Function)
      • 3. 虚析构函数 (Virtual Destructor)
      • 4. 虚继承 (Virtual Inheritance)
      • 5. 虚函数表 (Vtable) 和 虚函数指针 (Vptr)

回答重点

虚继承是C++中的一种继承方式,用于解决菱形继承(也称为钻石继承)问题。虚继承可以确保从一个共同基类派生的多个子类只会在最派生类中继承一份该基类的成员,而不会产生多个冗余基类,从而避免冗余和二义性。

菱形继承问题

假设有以下继承结构:

    A
   / \
  B   C
   \ /
    D

在这个继承关系中,类 B 和类 C 都继承自类 A,然后类 D 同时继承自 BC。这就形成了一个菱形结构。

非虚继承的情况下,D 会有两份 A 的成员:一份来自 B,一份来自 C。这不仅浪费内存,还会引发二义性问题。例如,D 对象调用 A 类中的成员时,编译器无法确定该成员来自 B 继承的 A,还是来自 C 继承的 A

虚继承解决菱形继承问题

为了避免这个问题,C++引入了虚继承。通过虚继承,基类 A 在子类 BC 中只会有一份副本,而不是每个派生类保留一份副本。

语法
虚继承通过在继承时加上 virtual 关键字实现:

class A {
public:
    int value;
};

class B : virtual public A {
    // 虚继承A
};

class C : virtual public A {
    // 虚继承A
};

class D : public B, public C {
    // D 从B和C继承,A只存在一份
};

BC 都通过虚继承方式继承自 A,因此当 D 继承自 BC 时,A 的成员在 D 中只存在一份。通过将中间的继承对象声明为虚继承,避免了基类冗余和二义性。

虚继承的二义性解决

即使通过虚继承解决了成员变量的二义性,构造函数的调用仍需要在派生类中明确指定。例如:

class D : public B, public C {
public:
    D() : A(), B(), C() {}  // 需要显式调用A的构造函数
};

虚继承总结

  • 虚继承用于解决菱形继承问题。
  • 在虚继承中,基类的成员只会有一份副本,从而避免冗余和二义性问题。
  • 虚继承虽然解决了复杂继承结构中的问题,但引入了一定的复杂性,需要在构造函数中显式调用基类的构造函数。

拓展知识:virtual关键字的用法

virtual 关键字在C++中主要用于以下四个场景:

  1. 虚函数:实现多态。
  2. 纯虚函数:定义抽象接口。
  3. 虚析构函数:保证正确析构派生类对象。
  4. 虚继承:解决菱形继承中的二义性问题。

1. 虚函数 (Virtual Function)

虚函数用于实现运行时多态。当基类的函数被声明为虚函数时,派生类可以重写这个函数,并在通过基类指针或引用调用时,动态地选择调用派生类的函数版本。

语法:

class Base {
public:
    virtual void display() { 
        std::cout << "Base display" << std::endl; 
    }
};

class Derived : public Base {
public:
    void display() override {  // override 表示派生类重写基类虚函数
        std::cout << "Derived display" << std::endl;
    }
};

int main() {
    Base* obj = new Derived();
    obj->display();  // 调用的是Derived类的display函数
}

关键点:

  • 虚函数允许在运行时根据对象类型来决定调用哪个版本的函数。
  • 派生类的函数如果与基类的虚函数同名并且参数相同,则会重写该虚函数。
  • 可以使用 override 关键字显式声明重写,但这不是强制的。

2. 纯虚函数 (Pure Virtual Function)

纯虚函数是一种特殊的虚函数,用于在基类中定义接口而不提供实现,要求所有派生类必须实现该函数。包含纯虚函数的类称为抽象类,不能直接实例化。

语法:

class Base {
public:
    virtual void display() = 0;  // 纯虚函数
};

class Derived : public Base {
public:
    void display() override { 
        std::cout << "Derived display" << std::endl; 
    }
};

关键点:

  • 包含纯虚函数的类不能被实例化,必须由派生类实现纯虚函数。
  • 纯虚函数用于定义抽象接口,强制派生类实现特定功能。

3. 虚析构函数 (Virtual Destructor)

虚析构函数用于确保通过基类指针删除派生类对象时,能够正确调用派生类的析构函数,防止内存泄漏。

语法:

class Base {
public:
    virtual ~Base() { 
        std::cout << "Base Destructor" << std::endl; 
    }
};

class Derived : public Base {
public:
    ~Derived() { 
        std::cout << "Derived Destructor" << std::endl; 
    }
};

int main() {
    Base* obj = new Derived();
    delete obj;  // 调用Derived的析构函数,然后调用Base的析构函数
}

关键点:

  • 如果基类没有虚析构函数,通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类的资源未释放。
  • 使用虚析构函数保证了正确的析构顺序:先调用派生类的析构函数,再调用基类的析构函数。

4. 虚继承 (Virtual Inheritance)

虚继承用于解决菱形继承问题,确保从多个派生类继承自同一个基类时,基类只被继承一份,避免重复继承和二义性。

语法:

class A {
public:
    int value;
};

class B : virtual public A { };  // 虚继承
class C : virtual public A { };  // 虚继承

class D : public B, public C { };  // D中只会有一份A

关键点:

  • 虚继承可以避免菱形继承中多次继承基类的问题。
  • 基类在派生类中只存在一份,减少冗余和二义性。

5. 虚函数表 (Vtable) 和 虚函数指针 (Vptr)

虽然这是 virtual 关键字的底层实现机制,但了解虚函数表和虚函数指针有助于理解 virtual 的运行机制。

  • 虚函数表 (Vtable):类包含虚函数时,编译器会生成一个虚函数表,其中记录了类的虚函数地址。
  • 虚函数指针 (Vptr):每个对象都包含一个指向虚函数表的指针,调用虚函数时,通过该指针找到正确的函数实现。

http://www.kler.cn/news/306936.html

相关文章:

  • LabVIEW机动车动态制动性能校准系统
  • spring项目中如何通过redis的setnx实现互斥锁解决缓存缓存击穿问题
  • [项目][WebServer][HttpServer]详细讲解
  • 一码空传临时网盘PHP源码,支持提取码功能
  • 数据中台进化为数据飞轮的必要
  • 【笔记】自动驾驶预测与决策规划_Part2_基于模型的预测方法
  • 初学Linux(学习笔记)
  • Vue.js入门系列(二十九):深入理解编程式路由导航、路由组件缓存与路由守卫
  • 【C++】入门基础(下)
  • Java项目基于docker 部署配置
  • 关于新版本 tidb dashboard API 调用说明
  • 评价类——熵权法(Entropy Weight Method, EWM),完全客观评价
  • ansible安全优化篇
  • 在深圳停车场我居然能看到很漂亮的瓦房
  • 707. 设计链表
  • SQL,从每组中的 json 字段中提取唯一值
  • 鸿蒙开发基础
  • Rust Web开发框架对比:Warp与Actix-web
  • SpringBoot + MySQL + MyBatis 实操示例教学
  • 从冯唐的成事心法 看SAP协助企业战略落地到信息化
  • 车载软件架构 --- SOA设计与应用(上)
  • DAY20240913 VUE:深入解析 Vue Router 局部路由守卫:路由独享与组件内部守卫的妙用与区别
  • 自修C++PrimerPlus--类型转换、右值引用、引用中的类对象
  • 虹科方案 | 精准零部件测试!多路汽车开关按键功能检测系统
  • C++实现unordered_map和unordered_set
  • 【Kafka】分区与复制机制:解锁高性能与容错的密钥
  • 交换技术是一种在计算机网络和通信系统中广泛应用的关键技术,它主要通过交换设备(如交换机、路由器等)实现数据的转发和传输
  • VBA V3高级视频行为分析系统(含源码)
  • 数据库系统 第52节 数据库日志和恢复
  • 用Matlab求解绘制2D散点(x y)数据的最小外接圆、沿轴外接矩形