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.pswp.cn/web/86390.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/86390.shtml
英文地址,請注明出處:http://en.pswp.cn/web/86390.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋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;配置容器啟動時執行的命令