【C++深度探索】繼承機制詳解(一)

hello hello~ ,這里是大耳朵土土垚~💖💖 ,歡迎大家點贊🥳🥳關注💥💥收藏🌹🌹🌹
在這里插入圖片描述

💥個人主頁:大耳朵土土垚的博客
💥 所屬專欄:C++入門至進階
這里將會不定期更新有關C++的內容,希望大家多多點贊關注收藏💖💖

目錄

  • 1.繼承的概念
  • 2.繼承定義
    • 2.1定義格式
    • 2.2訪問限定符
    • 2.3繼承方式
  • 3.基類和派生類對象賦值轉換
  • 4.繼承中的重定義(隱藏)
  • 5.派生類的默認成員函數
    • ?構造函數
    • ?拷貝構造
    • ?賦值運算符重載
    • ?析構函數
  • 6.結語

1.繼承的概念

繼承(inheritance)機制是面向對象程序設計使代碼可以復用的最重要的手段,它允許程序員在保持原有類特性的基礎上進行擴展,增加功能,這樣產生新的類,稱派生類子類。繼承呈現了面向對象程序設計的層次結構,體現了由簡單到復雜的認知過程。以前我們接觸的復用都是函數復用,繼承是類設計層次的復用。

2.繼承定義

2.1定義格式

下面我們看到Person是父類,也稱作基類。Student是子類,也稱作派生類。

//基類或父類
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter";// 姓名int _age = 18;  //年齡
};//派生類或子類
class Student : public Person
{
protected:int _stuid; // 學號int _major;	//專業
};

在這里插入圖片描述

2.2訪問限定符

C++類的訪問限定符用于控制類的成員(包括成員變量和成員函數)在類的外部的可訪問性。C++中有以下三種訪問限定符:

  • public: 公共訪問限定符,任何地方都可以訪問公共成員。可以在類的外部使用對象名和成員名直接訪問公共成員。

  • private: 私有訪問限定符,只有類內部的其他成員函數可以訪問私有成員。類的外部無法直接訪問私有成員,但可以通過公共成員函數間接訪問私有成員。

  • protected: 保護訪問限定符,只有類內部的其他成員函數和派生類的成員函數可以訪問保護成員。類的外部無法直接訪問保護成員,但可以通過公共成員函數或派生類的成員函數間接訪問保護成員。

需要注意的是,訪問限定符只在類的內部起作用,在類的外部沒有直接的影響。同時,訪問限定符可以用于類的成員變量和成員函數的聲明中,默認情況下,成員變量和成員函數的訪問限定符是private。

2.3繼承方式

在這里插入圖片描述
C++類的繼承方式有以下幾種:

  • 公有繼承(public inheritance):使用關鍵字"public"表示的繼承方式。在公有繼承中,基類的公有成員和保護成員都可以在派生類中訪問,私有成員不能在派生類中直接訪問。
class Base {
public:// 公有成員
protected:// 保護成員
private:// 私有成員
};class Derived : public Base {// 公有繼承
};
  • 保護繼承(protected inheritance):使用關鍵字"protected"表示的繼承方式。在保護繼承中,基類的公有成員和保護成員在派生類中都變為保護成員私有成員不能在派生類中直接訪問。
class Base {
public:// 公有成員
protected:// 保護成員
private:// 私有成員
};class Derived : protected Base {// 保護繼承
};
  • 私有繼承(private inheritance):使用關鍵字"private"表示的繼承方式。在私有繼承中,基類的公有成員和保護成員在派生類中都變為私有成員,私有成員不能在派生類中直接訪問。
class Base {
public:// 公有成員
protected:// 保護成員
private:// 私有成員
};class Derived : private Base {// 私有繼承
};

總結如下:

類成員/繼承方式public繼承protected繼承private繼承
基類的public成員派生類的public成員派生類的protected成員派生類的private成員
基類的protected成員派生類的protected成員派生類的protected成員派生類的private成員
基類的private成員在派生類中不可見在派生類中不可見在派生類中不可見

①基類的其他成員在子類的訪問方式 == Min(成員在基類的訪問限定符,繼承方式),public > protected > private

這些繼承方式可以根據具體的需求選擇合適的方式

②基類private成員在派生類中無論以什么方式繼承都是不可見的。這里的不可見是指基類的私有成員還是被繼承到了派生類對象中,但是語法上限制派生類對象不管在類里面還是類外面都不能去訪問它

例如:

//基類或父類
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}//保護成員
protected:string _name = "tutu";// 姓名int _age = 20;  //年齡//私有成員
private:string _tele = "123456";};//派生類或子類
class Student : public Person
{
public:void sPrint(){Person::Print();	//可以使用父類的公有成員}
protected:int _stuid; // 學號string _sname = _name;//可以訪問父類的保護成員_namestring _stele = _tele; //不可以訪問父類的私有成員_tele
};

結果如下:
在這里插入圖片描述

上述父類Person中成員有三種訪問限定分別是public、protected、private,而子類Student使用public繼承父類,那么對于父類的公有成員在子類中的訪問方式還是public,protected成員訪問方式選擇繼承方式public和protected中較小的protected,同理父類的private成員繼承到子類中也是選擇private方式,在子類中不可訪問

對于私有成員也是被繼承到子類中,只是不可訪問:

在這里插入圖片描述

③基類private成員在派生類中是不能被訪問,如果基類成員不想在類外直接被訪問,但需要在派生類中能訪問,就定義為protected。可以看出保護成員限定符是因繼承才出現的。

④ 使用關鍵字class時默認的繼承方式是private,使用struct時默認的繼承方式是public,不過最好顯示的寫出繼承方式。

⑤在實際運用中一般使用都是public繼承,幾乎很少使用protetced/private繼承,也不提倡使用protetced/private繼承,因為protetced/private繼承下來的成員都只能在派生類的類里面使用,實際中擴展維護性不強。

3.基類和派生類對象賦值轉換

  • 派生類對象可以賦值給基類的對象 / 基類的指針 / 基類的引用。這里有個形象的說法叫切片或者切割。寓意把派生類中父類那部分切來賦值過去。

如下圖所示:

在這里插入圖片描述

  • 基類對象不能賦值給派生類對象

例如下面代碼:

//父類
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;//error
}

在這里插入圖片描述

4.繼承中的重定義(隱藏)

  • 在繼承體系中基類和派生類都有獨立的作用域。
  • 子類和父類中有同名成員,子類成員將屏蔽對父類同名成員的直接訪問,這種情況叫隱藏,也叫重定義。

當一個類繼承另一個類時,它可以重定義繼承的成員函數或者成員變量。
需要注意的是如果是成員函數的隱藏,只需要函數名相同就構成隱藏。

  • 如果要訪問被隱藏的父類的同名成員,可以在子類成員函數中,使用 父類::父類成員來顯示訪問

注意在實際中在繼承體系里面最好不要定義同名的成員。

例如:

// Student的_num和Person的_num構成隱藏關系,可以看出這樣代碼雖然能跑,但是非常容易混淆
//父類
class Person
{
protected:string _name = "胡土土"; // 姓名int _num = 1234; // 身份證號
};//子類
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();
};

結果如下:

在這里插入圖片描述

我們發現當子類與父類有隱藏關系時,對于同名變量_num的調用,除非顯示使用Person::_num 調用的是父類的成員變量,其他情況_num表示的都得子類中定義的變量,這是因為它們有不同的作用域,在子類中調用變量都是先從子類這個作用域中尋找。

再看下面的例子:

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);
}

這里 B中的fun和A中的fun不是構成重載,因為不是在同一作用域
B中的fun和A中的fun構成隱藏,成員函數滿足函數名相同就構成隱藏。

結果如下:

在這里插入圖片描述
如果Test函數中:

void Test()
{B b;b.fun();//這里沒有給參數
}

結果如下:

在這里插入圖片描述

使用對象b調用fun()沒有給參數,這樣編譯是不通過的,因為這樣調用是調用的類B中的成員函數fun是需要傳參的,如果要調用基類中的fun函數就必須顯示調用,代碼如下:

void Test()
{B b;b.A::fun();//顯示調用A中的fun函數
}

結果如下:
在這里插入圖片描述

5.派生類的默認成員函數

在這里插入圖片描述

6個默認成員函數,“默認”的意思就是指我們不寫,編譯器會變我們自動生成一個,那么在派生類中,(先不考慮取地址重載)這幾個成員函數是如何生成的呢?

例如如下父類:

//有如下Person父類
class Person
{
public:Person(const char* name = "tutu"): _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; // 姓名
};

?構造函數

  • 派生類的構造函數必須調用基類的默認構造函數初始化基類的那一部分成員。如果基類沒有默認的構造函數,則必須在派生類構造函數的初始化列表階段顯示調用基類的構造函數。
//基于上面Person的派生類Student
class Student : public Person
{
protected:int _num; //學號
};
int main()
{Student s;return 0;
}

結果如下:
在這里插入圖片描述

在這里插入圖片描述

我們發現對于父類中的成員它會自動調用父類Person的默認構造函數與析構函數

  • 如果父類Person沒有默認構造函數,那么我們就需要在初始化列表里顯示調用父類的構造函數
    例如:
    在這里插入圖片描述

當我們將基類的默認構造函數中的缺省值"tutu",去掉,它就不再是默認構造函數,那么在創建子類Student對象時就不會自動調用默認構造函數,會保錯,那么這時我們就需要在初始化列表里顯示調用

代碼如下:

class Student : public Person
{
public:Student(const char* name, int num):Person(name)	//顯示調用父類構造函數, _num(num){}	
protected:int _num; //學號
};int main()
{Student s("tutu", 111);;return 0;
}

結果如下:
在這里插入圖片描述

還有一種顯示調用情況:

在這里插入圖片描述

這種情況是不可取的,這是因為規定在初始化列表中是不可以使用父類的成員的

?拷貝構造

  • 派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化。

默認生成拷貝構造一般情況下夠用,只有當子類成員涉及深拷貝時就必須自己實現拷貝構造

這里也可以自己顯示實現一下拷貝構造:

class Student : public Person
{
public://構造函數Student(const char* name, int num):Person(name)	//顯示調用父類構造函數, _num(num){}	//拷貝構造Student(const Student& st):Person(st)	//利用前面學習的基類與派生類的賦值轉換,_num(st._num){}
protected:int _num; //學號
};

注意這里Person(st)中調用Person中的拷貝構造實現賦值兼容

?賦值運算符重載

  • 派生類的operator=必須要調用基類的operator=完成基類的復制。

?析構函數

  • 派生類的析構函數會在被調用完成后自動調用基類的析構函數清理基類成員。因為這樣才能保證派生類對象先清理派生類成員再清理基類成員的順序。

如果自己顯示寫析構函數:

//析構函數
~Student()
{~Person();//這樣寫是錯誤的
}

因為多態的原因,析構函數的名字會被統一處理為destructor(),所以這里調用會構成隱藏,會循環調用,所以要指定作用域:

//析構函數
~Student()
{Person::~Person();cout << "~Student()" << endl;
}

但是我們發現Person的析構函數居然調用了兩次:

在這里插入圖片描述

這是因為析構函數具有特殊性,在子類析構函數調用完之后會自動調用父類的析構函數,所以即便是自己顯示實現了子類的析構函數也不需要自己主動調用父類的析構函數

所以不需要自己主動調用父類的析構函數,否則會報錯

其核心原因在于初始化時先構造父類再構造子類,而析構時先析構子類再析構父類,因為子類析構時是可能用到父類成員的,先父后子可能會出錯

所以為了保證先析構子類再析構父類,編譯器會在析構了子類后自動調用父類的析構函數

總結如下:

默認成員函數\子類成員內置成員自定義成員子類中的父類成員(整體)
默認生成的構造不做處理調用自定義類型的默認構造調用父類的默認構造
默認生成的拷貝構造值拷貝調用自定義類型的拷貝構造調用父類的拷貝構造
默認生成的賦值重載直接賦值調用自定義類型的賦值重載調用父類的賦值重載
默認生成的析構函數不做處理調用自定義類型的析構函數自動調用父類的析構函數

對于構造和析構:
派生類對象初始化先調用基類構造再調派生類構造。
派生類對象析構清理先調用派生類析構再調基類的析構

6.結語

繼承可以分為公有繼承(public inheritance)、保護繼承(protected inheritance)和私有繼承(private inheritance)。繼承在C++中的應用非常廣泛,可以用于構建復雜的類層次結構,提供代碼的復用性和靈活性。但是,在使用繼承時也需要注意避免多層次的繼承導致的類關系復雜性增加,以及合理設計基類和派生類之間的關系。以上就是今天的所以內容啦~ 完結撒花~ 🥳🎉🎉

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

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

相關文章

代碼托管服務:GitHub、GitLab、Gitee

目錄 引言GitHub&#xff1a;全球最大的代碼托管平臺概述功能特點適用場景 GitLab&#xff1a;一體化的開發平臺概述功能特點適用場景 Gitee&#xff08;碼云&#xff09;&#xff1a;中國本土化的代碼托管服務概述功能特點適用場景 功能對比結論 引言 在現代軟件開發中&#…

numpy - array(3)

arr1 np.array([[(1000, 1001, 1002, 1003), (1010, 1011, 1012, 1013), (1020, 1021, 1022, 1023)],[(1100, 1101, 1102, 1103), (1110, 1111, 1112, 1113), (1120, 1121, 1122, 1123)]], dtypeint) (1) 根據坐標訪問元素或內容,更改訪問的內容&#xff0c;array也會更改。“…

C++操作系列(一):MinGW環境安裝與配置(無報錯版)

本文選擇MinGW作為安裝對象。 1. 下載MinGW 進入官網&#xff1a;MinGW - Minimalist GNU for Windows download | SourceForge.net 點擊File&#xff1a; 劃到最下面&#xff1a; &#xfeff; Windows 64位系統下載seh結尾的安裝包&#xff1a; 2. 安裝MinGW 解壓MinGW&am…

力扣第218題“天際線問題”

在本篇文章中&#xff0c;我們將詳細解讀力扣第218題“天際線問題”。通過學習本篇文章&#xff0c;讀者將掌握如何使用掃描線算法和堆來解決這一問題&#xff0c;并了解相關的復雜度分析和模擬面試問答。每種方法都將配以詳細的解釋&#xff0c;以便于理解。 問題描述 力扣第…

【CSS】深入理解CSS 的steps()函數

在CSS動畫中&#xff0c;steps()函數是一個時間函數&#xff0c;它主要用于animation-timing-function屬性&#xff0c;以定義動畫的步進方式。當你想要動畫的某個屬性&#xff08;如background-position或transform: translateX()&#xff09;在特定的步驟之間變化時&#xff…

探索 ES6:現代 JavaScript 的新特性

隨著 JavaScript 的不斷演進&#xff0c;ECMAScript 2015&#xff08;簡稱 ES6&#xff09;作為 JavaScript 的一次重大更新&#xff0c;帶來了許多新特性和語法改進&#xff0c;極大地提升了開發體驗和代碼質量。本文將詳細介紹 ES6 的主要新特性&#xff0c;并展示如何在實際…

NLTK:原理與使用詳解

文章目錄 NLTK簡介NLTK的核心功能1. 文本處理2. 詞匯處理3. 語法分析4. 語義分析5. 情感分析 NLTK的使用1. 安裝NLTK2. 導入NLTK庫3. 下載NLTK數據集4. 文本處理示例5. 情感分析示例 總結 NLTK簡介 NLTK是一個開源的Python庫&#xff0c;用于處理和分析人類語言數據。它提供了…

扛鼎中國AI搜索,天工憑什么?

人類的創作不會沒有瓶頸&#xff0c;但AI的熱度可不會消停。 大模型之戰依舊精彩&#xff0c;OpenAI選擇在Google前一天舉行發布會&#xff0c;兩家AI企業之間的拉扯賺足了熱度。 反觀國內&#xff0c;百模大戰激發了大家對于科技變革的熱切期盼&#xff0c;而如今行業已逐漸…

【操作系統期末速成】 EP01 | 學習筆記(基于五道口一只鴨)

文章目錄 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文&#xff1a;??????1.1 考點一&#xff1a;操作系統的概率及特征 三、總結&#xff1a;&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&#x1f680; ?? 回報不在行動…

文章浮現之單細胞VDJ的柱狀圖

應各位老師的需求復現一篇文章的中的某個圖 具體復現圖5的整個思路圖&#xff0c;這里沒有原始數據&#xff0c;所以我使用虛擬生產的metadata進行畫圖 不廢話直接上代碼&#xff0c;先上python的代碼的結果圖 import matplotlib.pyplot as plt import numpy as np# 數據&#…

架構師篇-8、運用事件風暴進行業務領域建

如何成為優秀架構師&#xff1f; 需要有一定的技術積累&#xff0c;但是核心是懂業務。 具備一定的方法&#xff0c;并且有很強的業務理解能力。 技術架構師&#xff1a;形成技術方案&#xff0c;做的更多的是底層的平臺&#xff0c;提供工具。 業務架構師&#xff1a;解決方…

兩數之和你會,三數之和你也會嗎?o_O

前言 多少人夢想開始的地方&#xff0c;兩數之和。 但是今天要聊的不是入門第一題&#xff0c;也沒有面試官會考這一題吧…不會真有吧&#xff1f; 咳咳不管有沒有&#xff0c;今天的豬腳是它的兄弟&#xff0c;三數之和&#xff0c;作為雙指針經典題目之一&#xff0c;也是常…

Vue3中Element Plus組件庫el-eialog彈框中的input無法獲取表單焦點的解決辦法

以下是vue.js官網給出的示例 <script setup> import { ref, onMounted } from vue// 聲明一個 ref 來存放該元素的引用 // 必須和模板里的 ref 同名 const input ref(null)onMounted(() > {input.value.focus() }) </script><template><input ref&qu…

如何在Vue3項目中使用Pinia進行狀態管理

**第一步&#xff1a;安裝Pinia依賴** 要在Vue3項目中使用Pinia進行狀態管理&#xff0c;首先需要安裝Pinia依賴。可以使用以下npm命令進行安裝&#xff1a; bash npm install pinia 或者如果你使用的是yarn&#xff0c;可以使用以下命令&#xff1a; bash yarn add pinia *…

Tomcat的安裝和虛擬主機和context配置

一、 安裝Tomcat 注意&#xff1a;安裝 tomcat 前必須先部署JDK 1. 安裝JDK 方法1&#xff1a;Oracle JDK 的二進制文件安裝 [rootnode5 ~]# mkdir /data [rootnode5 ~]# cd /data/ [rootnode5 data]# rz[rootnode5 data]# ls jdk-8u291-linux-x64.tar.gz [rootnode5 data]…

C++:std::function的libc++實現

std::function是個有點神奇的模板&#xff0c;無論是普通函數、函數對象、lambda表達式還是std::bind的返回值&#xff08;以上統稱為可調用對象&#xff08;Callable&#xff09;&#xff09;&#xff0c;無論可調用對象的實際類型是什么&#xff0c;無論是有狀態的還是無狀態…

【C++】string基本用法(常用接口介紹)

文章目錄 一、string介紹二、string類對象的創建&#xff08;常見構造&#xff09;三、string類對象的容量操作1.size()和length()2.capacity()3.empty()4.clear()5.reserve()6.resize() 四、string類對象的遍歷與訪問1.operator[ ]2.正向迭代器begin()和end()3.反向迭代器rbeg…

QTableView與QSqlQueryModel的簡單使用

測試&#xff1a; 這里有一個sqlite數據庫 存儲了10萬多條數據&#xff0c;col1是1,col2是2. 使用QSqlQueryModel和QTableView來顯示這些數據&#xff0c;也非常非常流暢。 QString aFile QString::fromLocal8Bit("E:/桌面/3.db");if (aFile.isEmpty())return;//打…

關于考摩托車駕照

剛通過了摩托車駕照考試&#xff0c;說兩句。 1、在哪兒考試就要搞清楚當地的規定&#xff0c;不要以為全國要求都一樣。 2、首先是報駕校。雖然至少有些地方允許自學后&#xff08;不報駕校&#xff09;考試&#xff0c;但報駕校聽聽教練說的&#xff0c;還是能提高通過率&a…

計算機圖形學筆記----矩陣

矩陣和標量的運算 ,則 矩陣與矩陣相乘 的矩陣A&#xff0c;的矩陣B。兩矩陣&#xff0c;結果為的矩陣&#xff0c;第一個矩陣的列數必須和第二個矩陣的行數相同&#xff0c;否則不能相乘 &#xff0c;中的每個元素等于A的第i行所對應的矢量和B的第j列所對應的矢量進行矢量點…