萬字重談C++——繼承篇

繼承的概念及定義

繼承的概念

繼承(Inheritance)機制作為面向對象程序設計中最核心的代碼復用方式,它不僅允許開發人員在保留基礎類特性的前提下進行功能擴展(從而創建新的派生類),更重要的是體現了面向對象程序設計的分層架構理念,這種架構完美地映射了從簡單到復雜的認知過程。與傳統函數級別的復用相比,繼承提升到了類設計層次的復用,為軟件系統的可擴展性和可維護性提供了更強大的支持。

class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";  // 存儲姓名信息int _age = 18;           // 存儲年齡信息
};/*
通過繼承機制,基類 Person 的所有成員(包括數據成員和成員函數)
都會成為子類的組成部分。以下示例展示了 Student 和 Teacher 類
如何有效地復用 Person 類的成員。開發者可以通過調試工具觀察
Student 和 Teacher 對象,直觀地驗證數據成員的復用情況。
*/class Student : public Person
{
protected:int _stuid;  // 存儲學號信息
};class Teacher : public Person
{
protected:int _jobid;  // 存儲工號信息
};int main()
{Student s;Teacher t;s.Print();t.Print();return 0;
}

繼承定義

定義格式

在面向對象系統中,Person?作為父類(也稱為基類),而?Student?扮演子類(也稱為派生類)的角色,這種層級關系構成了面向對象程序設計的基礎架構。

繼承關系和訪問限定符

繼承基類成員訪問方式的變化

  1. 基類的?private?成員在派生類中具有不可訪問性(無論采用何種繼承方式)。這種不可見性并不意味著派生類對象沒有繼承這些成員,而是從語法層面禁止了派生類對象(無論是在類內部還是外部)的訪問權限。

  2. 基類的?private?成員在派生類中無法直接訪問。若希望基類成員在派生類中可訪問,同時避免類外訪問,應當使用?protected?訪問限定符。由此可見,protected?限定符的出現正是為了滿足繼承機制的特殊需求。

  3. 總結訪問規則可以發現:基類的私有成員在子類中始終不可見,而基類其他成員在子類中的訪問權限取決于

    Min(成員在基類的訪問限定符,繼承方式)

    其中?public > protected > private

  4. 在 C++ 中,使用?class?關鍵字定義類時,默認繼承方式是?private;使用?struct?時,默認繼承方式則為?public。為了代碼可讀性和維護性,強烈建議顯式聲明繼承方式。

  5. 在工程實踐中,public?繼承是最常用的繼承方式,而?protected/private?繼承的應用場景相對較少。這主要是因為?protected/private?繼承限制了派生類外部的可訪問性,降低了代碼的維護性和擴展性。

?基類和派生類對象賦值轉換

class Person
{
public:void Print(){cout << _name << endl;}
protected:string _name;  // 存儲姓名
private:int _age;      // 存儲年齡
};// class Student : protected Person
// class Student : private Personclass Student : public Person
{
protected:int _stunum;   // 存儲學號
};/*
在面向對象系統中,派生類對象可以賦值給基類對象、基類指針或基類引用,
這一特征常被形象地稱為“切片”或“切割”,比喻將派生類中屬于父類的部分
“切出”進行賦值。反之,基類對象不能直接賦值給派生類對象。基類指針或引用可以通過強制類型轉換賦值給派生類指針或引用,但需要注意:
只有當基類指針確實指向派生類對象時,這種轉換才是安全的。在多態類型中,
可以使用 RTTI (Run-Time Type Information) 的 dynamic_cast 進行安全轉換
(具體實現將在后續章節講解)。
*/class Person
{
protected:string _name;  // 存儲姓名string _sex;   // 存儲性別int _age;      // 存儲年齡
};class Student : public Person
{
public:int _No;      // 存儲學號
};void Test()
{Student sobj;// 1. 子類對象可以賦值給父類對象/指針/引用Person pobj = sobj;Person* pp = &sobj;Person& rp = sobj;// 2. 基類對象不能賦值給派生類對象sobj = pobj;  // 錯誤操作// 3. 基類指針可以通過強制類型轉換賦值給派生類的指針pp = &sobj;Student* ps1 = (Student*)pp;  // 安全轉換ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp;  // 非安全轉換,可能引發越界訪問ps2->_No = 10;
}

繼承中的作用域

  1. 在繼承體系中,基類和派生類各自擁有獨立的作用域。

  2. 當子類和父類中存在同名成員時,子類的成員會屏蔽父類對該同名成員的直接訪問,這種現象叫做隱藏,也稱重定義。在子類成員函數中,可以通過?基類::成員?的方式顯式訪問被隱藏的基類成員。

  3. 成員函數的隱藏只需函數名相同即可構成隱藏,無需參數列表完全一致。

  4. 實際工程中,建議避免在繼承體系中定義同名成員,避免帶來混淆。例如:

class Person
{
protected:string _name = "小李子";  // 姓名int _num = 111;           // 身份證號
};class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;cout << "身份證號:" << Person::_num << endl;  // 顯式訪問基類成員cout << "學號:" << _num << endl;}
protected:int _num = 999;  // 學號,隱藏了基類的_num
};void Test()
{Student s1;s1.Print();
}
class A
{
public:void fun(){cout << "func()" << endl;}
};class B : public A
{
public:void fun(int i){A::fun();  // 顯式調用基類函數cout << "func(int i)->" << i << endl;}
};void Test()
{B b;b.fun(10);
}

?派生類的默認成員函數

C++中的6個默認成員函數指的是編譯器在無顯式定義時自動生成的函數。派生類中這些函數的生成及行為:

  1. 構造函數
    派生類構造函數必須調用基類的構造函數來初始化基類部分。如果基類沒有默認構造函數,則派生類構造函數須在初始化列表中顯式調用相應基類構造函數。

  2. 拷貝構造函數
    派生類的拷貝構造函數必須調用基類的拷貝構造函數來完成基類部分的復制初始化。

  3. 賦值運算符(operator=)
    派生類的賦值運算符必須調用基類的賦值運算符完成基類成員的復制。

  4. 析構函數
    派生類析構函數執行完畢后,自動調用基類的析構函數,保證銷毀順序從派生部分到基類部分。

  5. 對象初始化順序
    實例化派生類對象時,先調用基類構造,再調用派生類構造。

  6. 對象銷毀順序
    銷毀派生類對象時,先調用派生類析構,再調用基類析構。

  7. 析構函數重寫注意
    因后續實現多態常重寫析構函數,編譯器會對析構函數名做特殊處理,形成隱藏關系。若基類析構函數未標記virtual,則子類析構函數和基類析構函數構成隱藏關系,可能導致析構不完全。

class Person
{
public:Person(const char* name = "peter"): _name(name) {cout << "Person()" << endl;}Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}~Person(){cout << "~Person()" << endl;}
protected:string _name;  // 姓名
};class Student : public Person
{
public:Student(const char* name, int num): Person(name), _num(num){cout << "Student()" << endl;}Student(const Student& s): Person(s), _num(s._num){cout << "Student(const Student& s)" << endl;}Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s);_num = s._num;}return *this;}~Student(){cout << "~Student()" << endl;}
protected:int _num;  // 學號
};void Test()
{Student s1("jack", 18);
}

繼承與友元

友元關系不能繼承,即基類中聲明為友元的類或函數,不能訪問子類的私有或保護成員。

class Student;
class Person
{
public:friend void Display(const Person& p, const Student& s);
protected:string _name; // 姓名
};
class Student : public Person
{
protected:int _stuNum; // 學號
};
void Display(const Person& p, const Student& s)
{cout << p._name << endl;cout << s._stuNum << endl;
}
void main()
{Person p;Student s;Display(p, s);
}

繼承與靜態成員

基類定義的?static?靜態成員變量在整個繼承體系中只有一份實例。無論有多少派生類對象,都共享同一個靜態成員。

class Person
{
public :Person () {++ _count ;}
protected :string _name ; // 姓名
public :static int _count; // 統計人的個數。
};
int Person :: _count = 0;
class Student : public Person
{
protected :int _stuNum ; // 學號
};
class Graduate : public Student
{
protected :string _seminarCourse ; // 研究科目
};
void TestPerson()
{Student s1 ;Student s2 ;Student s3 ;Graduate s4 ;cout <<" 人數 :"<< Person ::_count << endl;Student ::_count = 0;cout <<" 人數 :"<< Person ::_count << endl;
}

菱形繼承的問題

單繼承:一個子類只有一個直接父類時稱這個繼承關系為單繼承

多繼承:一個子類有兩個或以上直接父類時稱這個繼承關系為多繼承

菱形繼承:菱形繼承是多繼承的一種特殊情況。

菱形繼承的問題:從下面的對象成員模型構造,可以看出菱形繼承有數據冗余和二義性的問題。 在Assistant的對象中Person成員會有兩份。

示例模型(對象成員)揭示菱形繼承會引發二義性和數據冗余問題:

class Person
{
public:string _name;  // 姓名
};class Student : public Person
{
protected:int _num;  // 學號
};class Teacher : public Person
{
protected:int _id;   // 職工編號
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse;  // 主修課程
};
void Test()
{Assistant a;a._name = "peter";  // 二義性,編譯器無法確認訪問哪個基類的_name// 需明確指定訪問路徑解決二義性,但數據冗余依然存在a.Student::_name = "xxx";a.Teacher::_name = "yyy";
}

虛擬繼承

為解決菱形繼承中數據冗余和二義性問題,可采用虛擬繼承

class Person
{
public:string _name;  // 姓名
};class Student : virtual public Person
{
protected:int _num;  // 學號
};class Teacher : virtual public Person
{
protected:int _id;   // 職工編號
};class Assistant : public Student, public Teacher
{
protected:string _majorCourse;  // 主修課程
};void Test()
{Assistant a;a._name = "peter";  // 無二義性,只有一份 Person 成員
}

繼承的總結和反思

  1. C++語法的復雜性,部分來源于多繼承及菱形繼承的底層實現。虛擬繼承雖能解決菱形繼承問題,但底層實現復雜且有一定性能開銷,因此一般不建議設計多繼承結構,更不要設計菱形繼承。

  2. 多繼承被視為 C++ 的缺陷之一,很多后續面向對象語言(如 Java)均不支持多繼承。

  3. 繼承與組合關系:公有繼承(public inheritance)是is-a關系,即每個派生類對象都是一個基類對象。組合是has-a關系,表示一個類內部包含另一個類的對象。例如:

// is-a 關系
class Car
{
protected:string _colour = "白色";  // 顏色string _num = "陜ABIT00";  // 車牌號
};class BMW : public Car
{
public:void Drive() { cout << "好開-操控" << endl; }
};class Benz : public Car
{
public:void Drive() { cout << "好坐-舒適" << endl; }
};// has-a 關系
class Tire
{
protected:string _brand = "Michelin";  // 品牌size_t _size = 17;            // 尺寸
};class Car
{
protected:string _colour = "白色";  // 顏色string _num = "陜ABIT00";  // 車牌號Tire _t;                   // 輪胎,組合關系
};
  • 建議優先使用組合以降低耦合度,提高代碼維護性。繼承破壞了基類封裝,變化基類實現對派生類有較大影響,導致耦合度高。

  • 繼承適用于表達“是一個”關系,且需要實現多態時;組合適用于“擁有一個”關系,是更安全、更靈活的復用手段。

筆試面試題舉例

什么是菱形繼承?其問題有哪些?

菱形繼承(Diamond Inheritance)?是指在多繼承中出現的一種特殊繼承結構,其中一個派生類同時繼承自兩個有共同基類的父類,構成菱形結構。具體表現為:

      A/ \B   C\ /D

派生類?D?繼承自?B?和?C,而?BC?又都繼承自同一個基類?A

問題:

  • 數據冗余:派生類?D?會擁有兩個獨立的基類?A?子對象,導致內存中有兩個?A?成員變量,相當于數據重復。
  • 二義性:在訪問基類?A?的成員時,如?D?中調用?A?的成員時是通過?B?繼承得到的,還是通過?C?繼承得到的?編譯器無法確定,導致訪問沖突。

什么是菱形虛擬繼承?如何解決數據冗余和二義性?

菱形虛擬繼承(Virtual Diamond Inheritance)?是解決菱形繼承問題的技術手段。通過在所有繼承公共基類的路徑上使用virtual關鍵字,確保派生類沿各條路徑共享同一個基類子對象,而不是創建多個獨立副本。

例如:

class A { ... };class B : virtual public A { ... };class C : virtual public A { ... };class D : public B, public C { ... };

解決方案:

  • 數據冗余解決:通過虛擬繼承,派生類?D?只保留一個共享的基類?A?實例,消除多份數據冗余。
  • 二義性解決:訪問基類?A?的成員不再因為多繼承路徑而產生歧義,編譯器明確且唯一地解析其位置,無需顯式指定路徑,避免訪問沖突。

虛擬繼承底層通過虛基表(VBT)和虛基表指針(VBPtr)實現,維護偏移量以正確定位唯一基類子對象。

繼承與組合的區別?何時使用繼承,何時使用組合?

特性繼承(is-a 關系)組合(has-a 關系)
關系語義“是一個”關系,例如學生是人“擁有一個”關系,例如車有輪胎
封裝性破壞部分封裝,派生類依賴基類實現封裝良好,只依賴公開接口
耦合度高,基類變化影響派生類低,修改部件不影響整體
靈活性較低,類型固定高,可動態組合不同部件
多態支持支持多態,允許重寫基類接口一般不支持多態,但可通過接口實現類似效果
使用場景需表達“是一個”的類型繼承關系,且關注行為重用組合復雜功能,靈活構建系統,關注模塊化和擴展性

何時使用繼承?

  • 當類之間存在明確的“是一個”關系。
  • 需要通過多態達到動態綁定和接口統一。
  • 想重用或擴展基類行為。

何時使用組合?

  • 當組件之間是“擁有”的關系。
  • 需降低耦合,提高代碼靈活性和可維護性。
  • 希望功能通過組合多個對象實現,便于擴展和替換。

總結:
優先推薦使用組合來實現代碼復用,只有在合理且明確的“是一個”關系且多態需求明確時,才采用繼承。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/79324.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/79324.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/79324.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

移動光貓 UNG853H 獲取超級管理員賬號密碼

注&#xff1a;電腦連接光貓&#xff0c;網線不要接2口&#xff08;2口一般是IPTV網口&#xff09; 首先瀏覽器打開 192.168.1.1&#xff0c;使用光貓背面的用戶名密碼登錄。&#xff08;user用戶名&#xff09; 然后在瀏覽器中另開一個窗口打開以下地址&#xff1a; http://…

ActiveMQ 可靠性保障:消息確認與重發機制(二)

ActiveMQ 重發機制 重發機制的原理與觸發條件 ActiveMQ 的重發機制是確保消息可靠傳輸的重要手段。當消息發送到 ActiveMQ 服務器后&#xff0c;如果消費者由于某些原因未能成功處理消息&#xff0c;ActiveMQ 會依據配置的重發策略&#xff0c;將消息重新放入隊列或主題中&am…

oceanbase設置密碼

docker run -p 2881:2881 --name oceanbase-ce -e MODEmini -d oceanbase/oceanbase-ce:4.2.1.10-110010012025041414 先進入鏡像再連接數據庫的方式 進入鏡像 docker exec -it oceanbase-ce bash 修改數據庫密碼 ALTER USER ‘root’ IDENTIFIED BY ‘123456’; 無密碼 obc…

使用Python和Pandas實現的Azure Synapse Dedicated SQL pool權限檢查與SQL生成用于IT審計

下面是使用 Python Pandas 來提取和展示 Azure Synapse Dedicated SQL Pool 中權限信息的完整過程&#xff0c;同時將其功能以自然語言描述&#xff0c;并自動構造所有權限設置的 SQL 語句&#xff1a; ? 步驟 1&#xff1a;從數據庫讀取權限信息 我們從數據庫中提取與用戶、…

tiktok web X-Bogus X-Gnarly 分析

聲明 本文章中所有內容僅供學習交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包內容、敏感網址、數據接口等均已做脫敏處理&#xff0c;嚴禁用于商業用途和非法用途&#xff0c;否則由此產生的一切后果均與作者無關&#xff01; 逆向過程 部分python代碼 import req…

目標文件的段結構及核心組件詳解

目標文件&#xff08;如 .o 或 .obj&#xff09;是編譯器生成的中間文件&#xff0c;其結構遵循 ELF&#xff08;Linux&#xff09;或 COFF&#xff08;Windows&#xff09;格式。以下是其核心段&#xff08;Section&#xff09;和關鍵機制的詳細解析&#xff1a; 1. 目標文件的…

【軟件設計師:復習】上午題核心知識點總結(一)

一、數據結構與算法(高頻) 1. 線性數據結構 數組與鏈表 數組:隨機訪問(O(1))、插入/刪除(O(n))、內存連續。鏈表:單向鏈表、雙向鏈表、循環鏈表;插入/刪除(O(1))、隨機訪問(O(n))。典型問題: 合并兩個有序鏈表(LeetCode 21)。鏈表反轉(迭代/遞歸實現)。棧與…

【ROS2】 核心概念2——功能包package

官方英文文檔&#xff1a;Creating a package — ROS 2 Documentation: Humble documentation 中文參考&#xff1a;古月ROS2 功能包講解 - 圖書資源 省流&#xff0c;就學習一個命令 ros2 pkg create --build-type <build-type> <package_name> ROS2的重要概念…

Java內存對象實現聚合查詢

文章目錄 什么是聚合查詢excel表格演示插入透視表透視表操作 sql聚合查詢創建表和插入數據按照國家業務類型設備類型統計總銷量按設備類型統計總銷量 Java內存對象聚合查詢普通對象方式創建對象聚合查詢條件查詢方法調用方式結果 Record對象方式Recor對象創建對象聚合查詢條件查…

VSCode開發調試Python入門實踐(Windows10)

我的Windows10上的python環境是免安裝直接解壓的Python3.8.x老版本&#xff0c;可參見《Windows下Python3.8環境快速安裝部署。 1. 安裝VSCode 在Windows 10系統上安裝Visual Studio Code&#xff08;VS Code&#xff09;是一個簡單的過程&#xff0c;以下是詳細的安裝方法與…

Tomcat DOS漏洞復現(CVE-2025-31650)

免責申明: 本文所描述的漏洞及其復現步驟僅供網絡安全研究與教育目的使用。任何人不得將本文提供的信息用于非法目的或未經授權的系統測試。作者不對任何由于使用本文信息而導致的直接或間接損害承擔責任。如涉及侵權,請及時與我們聯系,我們將盡快處理并刪除相關內容。 前…

使用Qt QAxObject解決Visual Fox Pro數據庫亂碼問題

文章目錄 使用Qt QAxObject解決Visual Fox Pro數據庫亂碼問題一、問題背景&#xff1a;ODBC讀取DBF文件的編碼困境二、核心方案&#xff1a;通過QAxObject調用ADO操作DBF1. 技術選型&#xff1a;為什么選擇ADO&#xff1f;2. 核心代碼解析&#xff1a;QueryDataByAdodb函數3. 連…

HTTP知識速通

一.HTTP的基礎概念 首先了解HTTP協議&#xff0c;他是目前主要使用在應用層的一種協議 http被稱為超文本傳輸協議 而https則是安全的超文本傳輸協議 本章節的內容首先就是對http做一個簡單的了解。 HTTP是一種應用層協議&#xff0c;是基于TCP/IP協議來傳遞信息的。 其中…

制作一款打飛機游戲26:精靈編輯器

雖然我們基本上已經重建了Axel編輯器&#xff0c;但我不想直接使用它。我想創建一個真正適合我們當前目的的編輯器&#xff0c;那就是編輯精靈&#xff08;sprites&#xff09;。這將是今天的一個大目標——創建一個基于模板的編輯器&#xff0c;用它作為我們實際編輯器的起點。…

mac下載homebrew 安裝和使用git

mac下載homebrew 安裝和使用git 本人最近從windows換成mac&#xff0c;記錄一下用homebrew安裝git的過程 打開終端 command 空格&#xff0c;搜索終端 安裝homebrew 在終端中輸入下面命令&#xff0c;來安裝homebrew /bin/bash -c "$(curl -fsSL https://raw.githu…

【LeetCode Hot100】圖論篇

前言 本文用于整理LeetCode Hot100中題目解答&#xff0c;因題目比較簡單且更多是為了面試快速寫出正確思路&#xff0c;只做簡單題意解讀和一句話題解方便記憶。但代碼會全部給出&#xff0c;方便大家整理代碼思路。 200. 島嶼數量 一句話題意 求所有上下左右的‘1’的連通塊…

《社交類應用開發:React Native與Flutter的抉擇》

社交類應用以令人目不暇接的速度更新迭代。新功能不斷涌現&#xff0c;從更智能的算法推薦到多樣化的互動形式&#xff0c;從增強的隱私保護到跨平臺的無縫體驗&#xff0c;每一次更新都旨在滿足用戶日益增長且多變的需求。面對如此高頻的更新需求&#xff0c;選擇合適的跨端框…

關于3D的一些基礎知識

什么是2D/3D? 2D&#xff08;二維&#xff09;和3D&#xff08;三維&#xff09;是描述空間維度的概念&#xff0c;它們的核心區別在于空間維度、視覺表現和應用場景。以下是詳細對比&#xff1a; 1. 定義與維度 ? 2D&#xff08;二維&#xff09; ? 定義&#xff1a;僅包…

大連理工大學選修課——機器學習筆記(7):集成學習及隨機森林

集成學習及隨機森林 集成學習概述 泛化能力的局限 每種學習模型的能力都有其上限 限制于特定結構受限于訓練樣本的質量和規模 如何再提高泛化能力&#xff1f; 研究新結構擴大訓練規模 提升模型的泛化能力 創造性思路 組合多個學習模型 集成學習 集成學習不是特定的…

嵌入式產品運行中數據丟失怎么辦?

目錄 1、數據丟失現象與根源分析 2、硬件層優化 3、系統/驅動層優化 4、應用軟件層優化 5、文件系統選型深度解析 5.1、NAND Flash 適用文件系統 5.2、eMMC 適用文件系統 6、系統掛載選項優化實踐 嵌入式系統在運行過程中&#xff0c;尤其是在涉及頻繁數據寫入&#xf…