C++面向对象7——C继承与C++继承对比、C++继承详解

继承

C语言与C++继承机制的对比与实现

一、C语言模拟继承的实现方法

C语言不支持面向对象编程的原生继承机制,但可以通过结构体嵌套和函数指针组合来模拟。

1. 结构体嵌套实现"is-a"关系
// 基类:Shape
typedef struct {int x;int y;
} Shape;// 派生类:Circle
typedef struct {Shape base;  // 嵌入基类作为第一个成员int radius;
} Circle;// 初始化函数
void Circle_init(Circle* circle, int x, int y, int radius) {circle->base.x = x;circle->base.y = y;circle->radius = radius;
}// 使用示例
Circle c;
Circle_init(&c, 10, 20, 5);
printf("Circle at (%d, %d)\n", c.base.x, c.base.y);
2. 函数指针实现多态
// 定义函数指针类型
typedef double (*AreaFunc)(void*);// 基类:Shape
typedef struct {int x;int y;AreaFunc calcArea;  // 函数指针实现多态
} Shape;// 派生类:Circle
typedef struct {Shape base;  // 必须作为第一个成员int radius;
} Circle;// 方法实现
double Circle_calcArea(void* self) {Circle* circle = (Circle*)self;return 3.14 * circle->radius * circle->radius;
}// 初始化函数
void Circle_init(Circle* circle, int x, int y, int radius) {circle->base.x = x;circle->base.y = y;circle->radius = radius;circle->base.calcArea = Circle_calcArea;  // 设置函数指针
}// 多态调用示例
double getArea(Shape* shape) {return shape->calcArea(shape);  // 动态调用
}
二、C语言与C++继承的核心区别
特性C语言模拟实现C++原生支持
语法支持无原生关键字,手动实现classpublicvirtual等关键字
类型系统无类型安全检查,需手动转换编译时类型检查,支持多态
访问控制无法实现private/protected封装支持public/protected/private访问控制
多态实现通过函数指针手动实现,运行时开销大通过虚函数表自动实现,语法简洁
构造/析构需手动调用初始化/清理函数自动调用构造函数和析构函数
菱形继承无法自动解决,需手动管理虚继承(virtual关键字)自动解决
代码复杂度高,需编写大量辅助代码低,语言特性直接支持
三、C语言模拟继承的局限性
  1. 类型安全问题

    Circle c;
    Shape* s = (Shape*)&c;  // 向上转换安全
    Circle* c2 = (Circle*)s;  // 向下转换需手动保证安全
    
  2. 访问控制缺失

    • 所有成员都是public,无法隐藏实现细节
    // 外部可直接访问Circle的所有成员
    c.radius = 10;  // 无访问限制
    
  3. 多态调用复杂性

    • 需显式传递this指针,函数调用语法复杂
    double area = s->calcArea(s);  // 需显式传递this
    
  4. 构造/析构管理

    • 对象初始化和清理需手动调用,易遗漏
    Circle c;
    Circle_init(&c, 10, 20, 5);  // 必须手动调用
    // 无自动析构机制
    
四、C++继承的优势
  1. 语法简洁性

    class Circle : public Shape {
    public:Circle(int x, int y, int r) : Shape(x, y), radius(r) {}double area() const override { return 3.14 * radius * radius; }
    private:int radius;
    };
    
  2. 自动类型转换

    Circle c(10, 20, 5);
    Shape* s = &c;  // 自动向上转换,安全
    Circle* c2 = dynamic_cast<Circle*>(s);  // 安全的向下转换(RTTI)
    
  3. 访问控制

    class Shape {
    protected:int x, y;  // 派生类可访问,外部不可访问
    };
    
  4. 虚函数与多态

    double area = s->area();  // 自动动态绑定,无需显式传递this
    
  5. 构造/析构自动化

    Circle c(10, 20, 5);  // 自动调用基类和派生类构造函数
    // 对象离开作用域时自动调用析构函数
    
五、应用场景对比
  1. C语言适用场景

    • 资源受限的嵌入式系统
    • 与C库交互的接口层
    • 对二进制兼容性有严格要求的场景
  2. C++适用场景

    • 大型面向对象应用程序
    • 需要多态和运行时灵活性的系统
    • 代码复用和可维护性要求高的场景
六、总结

C语言通过结构体嵌套和函数指针可以部分模拟继承和多态,但存在类型安全、访问控制和代码复杂度等问题。C++则通过语言原生特性(类、继承、虚函数等)提供了更安全、更高效、更简洁的面向对象编程支持。

选择建议:

  • 若需兼容C代码或资源极度受限,使用C语言模拟
  • 若追求开发效率和代码可维护性,优先使用C++

现代C++(如C++11及以后)通过智能指针、移动语义等特性进一步提升了继承机制的安全性和性能,减少了传统C++的一些痛点。

C++ 继承机制深度解析

一、继承的基本概念

继承是面向对象编程(OOP)的核心机制,允许一个类(派生类)继承另一个类(基类)的属性和方法,实现代码复用和多态。

继承的本质:

  • 类型扩展:派生类是基类的扩展,拥有基类的所有非私有成员
  • is-a 关系:派生类对象可被视为基类对象(里氏替换原则)
  • 访问控制:通过继承方式和访问修饰符控制成员可见性

示例:

class Shape {
protected:string color;
public:Shape(const string& c) : color(c) {}virtual double area() const { return 0.0; }
};class Circle : public Shape {
private:double radius;
public:Circle(double r, const string& c) : Shape(c), radius(r) {}double area() const override { return 3.14159 * radius * radius; }
};
二、访问权限与继承方式的组合

继承方式(public/protected/private)与基类成员访问权限(public/protected/private)的组合决定派生类成员的可见性:

访问权限表:

基类成员public继承protected继承private继承
public派生类public派生类protected派生类private
protected派生类protected派生类protected派生类private
private不可访问不可访问不可访问

关键特性:

  1. protected访问权限

    • 对外部隐藏,但允许派生类访问
    • 实现封装与继承的平衡
  2. private继承

    • 基类public/protected成员变为派生类private成员
    • 实现"has-a"关系(组合)的替代方案
    class Vehicle {
    public:void move() { cout << "Moving..." << endl; }
    };class Car : private Vehicle {  // 私有继承
    public:void drive() { move(); }  // 显式转发基类方法
    };
    
三、基类构造函数详解

派生类构造函数必须初始化基类部分,初始化方式取决于基类构造函数的形式:

构造函数调用规则:

  1. 默认构造函数:若基类有默认构造函数,派生类构造函数可省略基类初始化

    class Base {
    public:Base() { cout << "Base()" << endl; }
    };class Derived : public Base {
    public:Derived() { cout << "Derived()" << endl; }  // 自动调用Base()
    };
    
  2. 带参数构造函数:必须在派生类构造函数初始化列表中显式调用

    class Base {
    public:Base(int x) { cout << "Base(" << x << ")" << endl; }
    };class Derived : public Base {
    public:Derived(int y) : Base(y) {  // 显式调用Base(int)cout << "Derived(" << y << ")" << endl;}
    };
    
  3. 多重继承的构造顺序

    • 按基类声明顺序调用(与初始化列表顺序无关)
    class A { public: A() { cout << "A" << endl; } };
    class B { public: B() { cout << "B" << endl; } };
    class C : public A, public B {  // 先构造A,再构造B
    public:C() { cout << "C" << endl; }
    };
    
四、虚函数与多态的实现原理

虚函数是C++实现运行时多态的核心机制,通过虚函数表(VTable)和虚表指针(VPTR)实现动态绑定。

虚函数的关键特性:

  1. 虚函数表(VTable)

    • 每个包含虚函数的类都有一个虚函数表
    • 虚函数表存储类的虚函数地址
    • 派生类重写虚函数时,虚函数表中对应项被替换
  2. 虚表指针(VPTR)

    • 每个对象包含一个虚表指针,指向所属类的虚函数表
    • 对象构造时VPTR被初始化,指向正确的虚函数表

示例:

class Shape {
public:virtual void draw() { cout << "Drawing Shape" << endl; }
};class Circle : public Shape {
public:void draw() override { cout << "Drawing Circle" << endl; }
};// 动态绑定示例
Shape* shape = new Circle();
shape->draw();  // 输出"Drawing Circle",通过虚函数表调用

override关键字的优势:

  • 确保函数正确重写基类虚函数
  • 编译器检查签名匹配,避免意外重载
class Base {
public:virtual void func(int x) {}
};class Derived : public Base {
public:void func(int x) override {}  // 正确// void func(double x) override {}  // 编译错误:签名不匹配
};
五、多重继承的复杂性

多重继承允许一个类从多个基类继承属性和方法,但引入了命名冲突和菱形继承等问题。

命名冲突的解决:

  • 作用域解析符:显式指定调用哪个基类的成员
class A { public: void f() {} };
class B { public: void f() {} };
class C : public A, public B {
public:void g() {A::f();  // 调用A::f()B::f();  // 调用B::f()}
};

数据冗余问题:

  • 多重继承可能导致数据在对象中重复存在
class Person { public: string name; };
class Student : public Person { public: int studentId; };
class Teacher : public Person { public: string subject; };
class TeachingAssistant : public Student, public Teacher {// 包含两份Person::name
};
六、虚继承的底层实现

虚继承通过共享基类实例解决菱形继承问题,其实现涉及虚基类表(Virtual Base Table)。

菱形继承问题:

       Animal/      \Mammal   Bird\      /Bat
  • Bat对象包含两份Animal数据,访问时产生二义性

虚继承解决方案:

class Animal { public: int weight; };
class Mammal : virtual public Animal {};  // 虚继承
class Bird : virtual public Animal {};    // 虚继承
class Bat : public Mammal, public Bird {};// Bat对象仅包含一份Animal::weight

虚继承的内存布局:

  • 每个派生类对象包含虚基类指针(VBPtr)
  • VBPtr指向虚基类表,表中存储虚基类的偏移量
  • 所有派生类共享同一个基类实例

构造顺序:

  1. 虚基类(按声明顺序)
  2. 非虚基类(按声明顺序)
  3. 成员对象(按声明顺序)
  4. 派生类自身构造函数
七、继承的高级应用场景
  1. 接口类(纯抽象类)

    class Drawable {
    public:virtual void draw() const = 0;  // 纯虚函数virtual ~Drawable() = default;
    };class Circle : public Drawable {
    public:void draw() const override { /*...*/ }
    };
    
  2. CRTP(奇异递归模板模式)

    template<typename Derived>
    class Base {
    public:void interface() {static_cast<Derived*>(this)->implementation();}
    };class Derived : public Base<Derived> {
    public:void implementation() { /*...*/ }
    };
    
  3. Mixin类

    template<typename T>
    class Loggable : public T {
    public:void log(const string& msg) { /*...*/ }
    };class MyClass {};
    using LoggableMyClass = Loggable<MyClass>;
    
八、继承的设计原则
  1. 里氏替换原则(LSP)

    • 派生类必须能够替换其基类而不影响程序正确性
    • 避免违反基类行为的"is-a"关系
  2. 优先使用组合而非继承

    • 组合(Composition)实现"has-a"关系,耦合度更低
    class Engine { /*...*/ };
    class Car {
    private:Engine engine;  // 组合而非继承
    };
    
  3. 开闭原则

    • 通过虚函数和继承实现对扩展开放,对修改关闭
九、总结对比表
特性描述关键语法
public继承基类接口保持不变,实现"is-a"关系class Derived : public Base
protected继承基类public成员变为protected,限制外部访问class Derived : protected Base
private继承基类接口被私有,实现"uses-a"关系class Derived : private Base
虚函数运行时动态绑定,实现多态virtual 返回类型 函数名()
纯虚函数强制派生类实现,使基类成为抽象类virtual 返回类型 函数名() = 0
override显式标记重写函数,提高安全性函数声明 override
虚继承解决菱形继承的数据冗余和二义性class Derived : virtual public Base

性能考量:

  • 虚函数调用比普通函数慢(需通过虚函数表寻址)
  • 虚继承增加内存开销(需维护虚基类表)
  • 多重继承可能导致对象布局复杂

合理运用继承机制需要平衡代码复用与设计复杂度,遵循面向对象设计原则,在适当场景选择继承、组合或模板技术。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.tpcf.cn/web/86390.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

运维打铁: Windows 服务器基础运维要点解析

文章目录 思维导图一级节点&#xff1a;Windows 服务器基础运维要点 详细内容解析系统安装与配置硬件准备安装介质选择系统安装过程初始配置 日常监控与维护性能监控服务状态检查日志管理 安全管理账户与权限管理防火墙配置病毒防护 备份与恢复备份策略制定备份工具使用恢复测试…

Python实例题:基于量子计算的优化算法实现(量子计算、优化理论)

目录 Python实例题 题目 问题描述 解题思路 关键代码框架 难点分析 扩展方向 Python实例题 题目 基于量子计算的优化算法实现&#xff08;量子计算、优化理论&#xff09; 问题描述 开发一个基于量子计算的优化算法实现&#xff0c;包含以下功能&#xff1a; 量子计…

基本算法--蓝桥杯备考

1.前缀和 1.定义 假设有一个数组a[n],要计算它的前j个元素的和为 a[0]a[1]...a[j-1] 时间复杂度为O(j)&#xff0c;且随着j的变大时间复杂度越来越大。 使用了前缀和算法则为 sum[j]-sum[j-1] 时间复杂度是O(1)&#xff0c;且数据越大优势越明显。 2.例题一 详解见《可…

pgsql 中各个字符串的区别

PostgreSQL 提供了多种字符串类型&#xff0c;它们在存储方式、长度限制和适用场景上有所不同。以下是主要字符串类型的详细对比和区别&#xff1a; 一、核心字符串类型对比 CHAR(n)/CHARACTER(n) 特点&#xff1a;固定长度字符串&#xff0c;不足部分用空格填充最大长度&…

ubuntu中lightdm干嘛的?

在 Ubuntu 或其他 Linux 发行版中&#xff0c;LightDM 是一个轻量级的 显示管理器&#xff08;Display Manager&#xff09;&#xff0c;负责图形化登录界面、用户认证和会话启动。以下是它的核心作用、特点及类似替代品的对比&#xff1a; 1. LightDM 的核心作用 功能说明图形…

GraphQL注入 -- GPN CTF 2025 Real Christmas

part 1 服务器会每段时间禁用已注册的账号,此处存在漏洞 def deactivate_user_graphql(email):graphql_endpoint current_app.config["GRAPHQL_ENDPOINT"]query f"""mutation {{deactivateUser (user: {{email: "{email}"}}){{ success…

【机器学习深度学习】非线性激活函数

目录 前言 一、什么是激活函数&#xff1f; 1.1 作用 二、如果没有激活函数&#xff0c;会发生什么&#xff1f; 2.1 先看一张图理解“线性”的局限 2.2 核心认知&#xff1a;为什么非线性如此重要&#xff1f; 三、非线性激活函数到底解决了什么问题&#xff1f; 1. 引…

国外开源客服系统chathoot部署,使用教程

目录 一、系统版本要求&#xff1a; 二、部署步骤 2.1 安装docker 和docker-compose 2.2 准备docker-compose.yaml 2.3 初始化数据库 2.4 安装nginx 2.6 启动项目 三、使用教程 一、系统版本要求&#xff1a; linux ubuntu 22.042核4G 40GB&#xff08;或以上&#xf…

什么是回归测试?什么时候需要做回归测试?

回归测试详解&#xff1a;概念、时机与最佳实践 1. 什么是回归测试&#xff1f; 回归测试&#xff08;Regression Testing&#xff09; 是指在对软件进行修改&#xff08;如修复Bug、新增功能、优化代码&#xff09;后&#xff0c;重新执行已有测试用例&#xff0c;以确保&am…

Android-Layout Inspector使用手册

Layout Inspector Android Layout Inspector 是 Android Studio 中用于调试应用布局的工具 启动方法&#xff1a; 通过下载Layout Inspector插件&#xff0c;在 “View - Tool Windows - Layout Inspector” 或 “Tools - Layout Inspector” 启动。 主要界面区域&#xff1a…

postgreSQL 数据库字典导出工具

为满足项目验收文档需求&#xff0c;开发了一个基于Python的PostgreSQL数据字典导出工具。 废话不多说&#xff0c;先分享一下 软件截图 数据字典文件样式,文件格式为docx 软件源码 基于python开发&#xff0c; import tkinter as tk from tkinter import ttk, messagebox …

【AI解析】 CppNumericalSolvers:一个现代化的 C++17 纯头文件优化库 示例代码解析

一个轻量级仅头文件的 C17 库&#xff0c;提供针对&#xff08;无&#xff09;约束非线性函数及表达式模板的数值优化方法 https://github.com/PatWie/CppNumericalSolvers CppNumericalSolvers 库 include 目录下的文件及其功能说明 根目录文件 文件名功能说明function.h(主函…

第3篇:Gin的请求处理——获取客户端数据(Gin文件上传,接收JSON数据)

引言&#xff1a;Context是Gin的"瑞士军刀" 在Gin框架中&#xff0c;Context就像一把多功能的瑞士军刀&#xff0c;封装了所有与请求相关的操作。新手开发者常犯的错误是只把它当作参数传递的工具&#xff0c;却忽略了它强大的数据处理能力。 想象一个场景&#xf…

启动hardhat 项目,下载依赖的npm问题

Windows 环境 Hardhat 依赖安装问题排查指南 &#x1f6a8; 问题描述 在 Windows 环境下安装 Hardhat 项目依赖时&#xff0c;遇到以下错误&#xff1a; npm ERR! code ETARGET npm ERR! notarget No matching version found for nomicfoundation/edr^0.11.1. npm ERR! nota…

大数据里的拉链表:数据版本管理的时间胶囊

哈喽各位数据打工人&#xff5e;今天咱们来聊聊大数据领域一个超实用的神器 ——拉链表&#xff01;听起来像时尚单品&#xff1f;NoNoNo&#xff0c;它可是数据仓库里管理历史数据的宝藏工具✨ 就算你是刚入门的小白也能轻松听懂&#xff0c;咱们全程少玩比喻多讲人话&#xf…

docker执行yum报错Could not resolve host: mirrorlist.centos.org

解决办法&#xff1a; -- 依次执行以下命令cd /etc/yum.repos.d/sed -i s|#baseurlhttp://mirror.centos.org|baseurlhttp://vault.centos.org|g /etc/yum.repos.d/CentOS-*sed -i s/mirrorlist/#mirrorlist/g /etc/yum.repos.d/CentOS-*yum update -yecho "export LC_ALL…

JVM OutOfMemoryError原因及排查解决方案

在Java后端开发中&#xff0c;java.lang.OutOfMemoryError&#xff08;简称OOM&#xff09;是一个令开发者头疼的异常。它通常意味着Java虚拟机&#xff08;JVM&#xff09;在尝试分配新对象时&#xff0c;发现堆中没有足够的空间来容纳该对象&#xff0c;或者其他内存区域耗尽…

吐槽之前后端合作开发

大家好&#xff0c;我是佳瑞&#xff0c;从事10多年java开发程序员&#xff0c;爆照一张&#xff0c;存活互联网。 也做过vue开发自己的网站&#xff0c;觉得前端是真比后端开发轻松很多&#xff0c;就是画页面调样式&#xff0c;打包发布&#xff0c;当然不说是高级源码修改…

Oracle LogMiner日志分析工具介绍

Oracle LogMiner日志分析工具介绍 LogMiner使用须知LogMiner字典使用online catalog作为日志挖掘字典使用redo日志文件作为日志挖掘字典使用文本文件作为日志挖掘字典Redo日志文件自动获取日志文件手动获取日志文件启动LogMiner进行分析V$LOGMNR_CONTENTS视图LogMiner使用须知 …

2-4 Dockerfile指令(个人笔记)

以下指令基于 ubuntu Dockerfile整体示例 From&#xff1a;设置基础镜像 Maintainer &#xff1a;镜像维护者信息 COPY/ADD&#xff1a;添加本地文件到镜像中 WorkDir&#xff1a;设置工作目录 Run&#xff1a;执行命令 CMD/EntryPoint&#xff1a;配置容器启动时执行的命令