C++——繼承

在這里插入圖片描述

文章目錄

  • 🦜1. 什么是繼承
    • 🐊1.1 概念
    • 🐊1.2 格式
    • 🐊1.3 繼承方式 & 訪問限定符
  • 🐦2. 派生類和基類的賦值問題
  • 🦩3. 派生類和基類同名成員問題
  • 🐓4.派生類默認成員函數
    • 🐉4.1 構造函數
    • 🐉4.2 拷貝構造
    • 🐉4.3 賦值運算符重載
    • 🐉4.4 析構函數
  • 🐥5. 友元和靜態成員
  • 🐧6. 多繼承

🦜1. 什么是繼承

🐊1.1 概念

在現實生活中,談起繼承,就會聯想到繼承家業、家產。

而在編程世界中,繼承也是如此,一個類(稱子類或者派生類),可以繼承另一個類(稱父類基類)的屬性和行為。

//定義一個人的屬性	基類
class Person
{
public:Person(string name = "Kangkang", string gender = "male", int age = 18):_name(name),_gender(gender),_age(age){cout << "Person()" << endl;}void Print(){cout << "name:" << _name << endl;cout << "gender:" << _gender << endl;cout << "age:" << _age << endl;}
protected:string _name;	//	姓名string _gender;	//	姓別int _age;	//年齡
};
//定義一個學生類,繼承人的屬性		子類
class Student :public Person
{
public:Student(string name = "Lihua", string gender = "female", int age = 20, int id=111):Person(name,gender,age),_stuId(id){};
protected:int _stuId;	//學號
};
int main()
{Person p;Student stu("Lisa","female",20,20230812);p.Print();stu.Print();return 0;
}

🐊1.2 格式

class 子類 : 繼承方式 基類
{};

image-20230813211527815

🐊1.3 繼承方式 & 訪問限定符

image-20230813212440542

繼承方式public繼承protected繼承private繼承
父類public成員子類的public成員子類的protected成員子類的private成員
父類的protected成員子類的protected成員子類的protected成員子類的private成員
父類的private成員子類不可見子類不可見子類不可見

這里其實很好分辨,我們只需要取權限小的即可:public>protected>private

對于public成員,我們可以直接在類的外面訪問調用,而對于protected成員,可在類里面通過this指針訪問,而private成員,雖然繼承到了派生類對象中,但無法訪問到,也可以理解為將父類的成員設為private就是不想讓其他類繼承

image-20230813220139015

但是在實際應用過程中,一般都是采用的public繼承方式

Tips:

關鍵字class不指定繼承方式時,默認繼承方式為private

而使用struct關鍵字時,默認繼承方式為public

但這里還是建議,每次都顯示繼承方式

class A
{
public:void func1() { cout << "func1()" << endl; }
protected:void func2(){ cout << "func2()" << endl; }
private:void func3(){ cout << "func3()" << endl; }int _a = 0;
};
class B :public A
{
public:void Print(){this->func1();this->func2();}int _b = 1;};
int main()
{B b;b.Print();
}

🐦2. 派生類和基類的賦值問題

派生類和基類之間的賦值操作涉及到對象切片的問題。派生類的對象可以賦值給基類對象/基類指針/基類引用 ,但反過來(將基類對象賦值給派生類對象)是不合法的,因為這可能導致對象切片,即派生類對象的額外成員信息丟失

image-20230813222919821

這就好比,學習C++,C++是在C語言的基礎上衍生出來的,可以理解問C++繼承了C語言的衣缽,C++的代碼可以兼容C的代碼;反之,C的代碼卻不能卻不能兼容C++。

image-20230813223945086

🦩3. 派生類和基類同名成員問題

class A
{
public:int _x=1;int _y=2;void Print(){cout << "A()" << endl;}
};
class B :public A
{
public:int _x = 3;int _y = 4;void Print(){cout << "B()" << endl;}
};
int main()
{B b;cout << b._x << endl;	// 3cout << b._y << endl;	// 4b.Print();	// B()
}

這段代碼基類A和派生類B,成員名都是相同的,但我們輸出發現,輸出的是派生類的成員,那這里是否繼承了A的這些成員呢?

image-20230813225150530

通過監視窗口發現,這里A是被B繼承了,但是由于成員名相同,A被B給隱藏了,這也叫重定義

如果要訪問基類的成員,可使用基類:基類成員顯示訪問,這也可理解問他們都有著獨立的作用域

image-20230813225536058

🐓4.派生類默認成員函數

🐉4.1 構造函數

派生類的構造函數必須調用基類的構造函數來初始化繼承下來的那部分成員;如果基類沒有默認構造,那在派生類構造函數的初始化列表顯示調用

class Person
{
public://全缺省,默認構造Person(string name = "Kangkang"):_name(name){}
protected:string _name;
};
class Student : public Person
{
public:Student(string name, int id):Person(name),_id(id){}void Print(){cout << "name:" << _name << endl;cout << "id:" << _id << endl;}
protected:int _id;
};
int main()
{Student stu("Lisa",2023);stu.Print();return 0;
}

🐉4.2 拷貝構造

派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化,但我們可以直接傳子類對象,因為調用父類的拷貝構造時,父類會自動切片拿到父類中的對象

父類拷貝構造

Person(const Person& p):_name(p._name)
{}

子類拷貝構造

Student(const Student&stu):Person(stu._name),_id(stu._id)
{}

調用

Student stu("Lisa", 2023);
stu.Print();
Student stu2(stu);
stu2.Print();

🐉4.3 賦值運算符重載

子類的operator=必須要調用父類的operator=完成基類的復制;但是因為賦值運算符重載了=,那么子類和父類的名字都是一樣,這樣就造成了子類隱藏了父類的operator=。所以需要顯示調用父類的operator=

//operator=
Person& operator=(const Person& p)
{if (this != &p){_name = p._name;}return *this;
}
Student& operator=(Student& stu)
{if (this != &stu){//指定調用父類Person::operator =(stu);_id = stu._id;}return *this;
}

🐉4.4 析構函數

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

Tips:

切記,這里是自動調用父類的析構,所以我們不需要在子類的析構函數中調用父類的析構函數

如果這里有指針,同一塊區域釋放兩次,會造成未定義行為

🐥5. 友元和靜態成員

在繼承中,友元關系是不可以被繼承的,就好比咱們朋友的朋友,不一定是咱們的朋友。

對于靜態成員,這里繼承的是它的使用權,就比如家里有三個孩子,一個大哥哥,兩個小弟弟,這個哥哥是他兩“共用的”,并不會說2個弟弟必須有2個哥哥。

class A
{
public:static int _sa;int _a;
};
int A::_sa = 1;
class B :public A
{
public:int _b;
};int main()
{A a;B b;cout <<"a._a:" << &a._a << endl;cout <<"b._a:" << &b._a << endl;cout <<"a._sa:" << &A::_sa << endl;cout <<"b._sa:" << &B::_sa<< endl;
}

這里也可以驗證,對于靜態成員,父類和子類是共用的(可用于計算父類有多少個派生類)。

🐧6. 多繼承

對于一個子類只有一個直接父類,這種關系稱為單繼承

//單繼承
class A
{};
class B:public A
{};
class C :public B
{};

而對于一個子類有多個直接父類,這種關系稱為多繼承

//多繼承
class A
{};
class B
{};
class C :public A, public B
{};

多繼承會引發一個很麻煩的問題——菱形繼承

image-20230814002934232

我們先來上代碼

class A
{
public:int _a;
};
class B:public A
{
public:int _b;
};
class C :public A
{
public:int _c;
};
class D :public B, public C
{
public:int _d;
};
int main()
{D d;d._a = 1;	//errord._b = 2;return 0;
}

這段代碼,直接報錯,_a的指定不明確,因為D類繼承了B類和C類,編譯器不知道這個_a是屬于繼承的哪個類,從而產生二義性的問題。

image-20230814003431003

當然,前面也提到過,可以通過指定類域,來明確告訴編譯器,這屬于哪個類

D d;
d.B::_a = 1;
d.C::_a = 2;
d._b = 3;
d._c = 4;
d._d = 5;

這樣雖然解決了二義性的問題,但是這樣看的數據十分冗余,很容易分不清哪個是哪個

image-20230814004447944

為了填補這個坑,推出了一種名為虛擬繼承的繼承方式(僅限菱形繼承使用,其他地方不要使用)

class A
{
public:int _a;
};
class B:virtual public A
{
public:int _b;
};
class C :virtual public A
{
public:int _c;
};
class D :public B, public C
{
public:int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;d._a = 6;return 0;
}

使用虛擬繼承之后,我們發現這里的_a,只有一份了,而且我們查看內存發現,數據并不是連在一起,多了一些地址

image-20230814011531088

這叫做虛基表,用來尋找基類偏移量的表,虛擬繼承的派生類里面就包含了這個虛表,這個虛表記錄著距離基類的偏移量,如果要用到基類的數據,加上這個距離就能找到,這樣就解決了數據的二義性和數據冗余的問題。

但是在實際過程中,這個模型十分雞肋且復雜,所以一般都不會采用這種繼承方式。


多繼承就屬于C++語法復雜的一個體現,而繼承雖然可以復用,但是繼承的耦合度十分高,代碼直接的依賴關系很強,這樣就造成了代碼的不便于維護。但又涉及到多態必須使用繼承,所以在實際之中,代碼要復用的話,我們得分場景。
那本期的方向就到這咯,我們下期再見,如果有下期的話。

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

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

相關文章

React源碼解析18(1)------ React.createElement 和 jsx

1.React.createElement 我們知道在React17版本之前&#xff0c;我們在項目中是一定需要引入react的。 import React from “react” 即便我們有時候沒有使用到React&#xff0c;也需要引入。原因是什么呢&#xff1f; 在React項目中&#xff0c;如果我們使用了模板語法JSX&am…

使用OkHttp發送POST請求的幾種方式

使用OkHttp發送POST請求的幾種方式 介紹pom依賴基本的POST請求帶授權的POST請求POST方式發送JSON數據Multipart POST 請求 介紹 本文將介紹 OkHttp 客戶端的基本用法。 主要介紹 OkHttp 3.x 版本中發送Post請求的幾種方式。 pom依賴 <dependency><groupId>com.sq…

單調遞增的數字——力扣738

文章目錄 題目描述解法題目描述 解法 #include<iostream> #include<string>using namespace std;int monotoneIncreasingDigits

【學習】若依源碼(前后端分離版)之 “ 異常處理”

大型紀錄片&#xff1a;學習若依源碼&#xff08;前后端分離版&#xff09;之 “ 異常處理” 前言1、統一返回實體定義2、定義登錄異常定義3、基于ControllerAdvice注解的Controller層的全局異常統一處理4、測試訪問請求結語 前言 通常一個web框架中&#xff0c;有大量需要處理…

中小企業項目管理軟件推薦:選擇適合的工具提升項目效率!

中小企業項目管理軟件有哪些&#xff1f;Zoho Projects是一款好用無廣告的項目管理軟件。當個小創業者是真的不容易&#xff0c;不僅要管理團隊&#xff0c;還要管理團隊項目。很多團隊之前用了好多項目管理的軟件&#xff0c;但是都不太滿意。但是如果你經常參加創業者聚會上&…

常見的路由協議之RIP協議與OSPF協議

目錄 RIP OSPF 洪泛和廣播的區別 路由協議是用于在網絡中確定最佳路徑的一組規則。它們主要用于在路由器之間交換路由信息&#xff0c;以便找到從源到目標的最佳路徑。 常見的路由協議&#xff1a; RIP (Routing Information Protocol)&#xff1a;RIP 是一種基于距離向量算…

Mac os 上的apt-get install 就是brew install

Mac os 上面不支持apt-get install ,但是有個 brew install可以代替。 Homebrew是Mac OS的包管理器&#xff0c;可以方便地安裝各種需要的軟件。 1.1 安裝Homebrew 如果沒有安裝Homebrew&#xff0c;需要在終端輸入以下命令進行安裝&#xff1a; /usr/bin/ruby -e "$(…

使用wxPython和PyMuPDF在Python中顯示PDF目錄的實現

展示如何使用wxPython和PyMuPDF庫在Python中選擇PDF文件并將目錄顯示在列表框中。 簡介&#xff1a; 在本篇教程中&#xff0c;我們將學習如何使用wxPython和PyMuPDF庫在Python中選擇PDF文件&#xff0c;并將其目錄顯示在一個列表框中。這將使用戶能夠方便地瀏覽PDF文檔的目錄…

c#實現設配器模式

下面是一個使用C#實現適配器模式的示例代碼&#xff1a; using System;// 目標接口 public interface ITarget {void Request(); }// 目標類 public class Target : ITarget {public void Request(){Console.WriteLine("目標類的請求");} }// 需要適配的類 public c…

Golang 局部變量、全局變量 聲明

文章目錄 一、局部變量二、全局變量 一、局部變量 四種聲明方式 多變量聲明&#xff1a; package mainimport "fmt"//局部變量聲明 func main() {//方法一: 聲明一個變量和數據類型&#xff0c;不初始化值&#xff1b;默認值為0&#xff1b;var lvA intfmt.Printl…

【MybatisPlus】LambdaQueryWrapper和QueryWapper的區別

個人主頁&#xff1a;金鱗踏雨 個人簡介&#xff1a;大家好&#xff0c;我是金鱗&#xff0c;一個初出茅廬的Java小白 目前狀況&#xff1a;22屆普通本科畢業生&#xff0c;幾經波折了&#xff0c;現在任職于一家國內大型知名日化公司&#xff0c;從事Java開發工作 我的博客&am…

可視化應用:提升教育領域的學習與理解

在教育領域&#xff0c;可視化應用作為一種強大的工具&#xff0c;已經開始發揮著重要的作用。通過將抽象的概念和復雜的數據轉化為直觀的圖形和圖表&#xff0c;可視化應用能夠提升學生的學習效果和理解能力。本文將探討可視化應用在教育領域中的重要性&#xff0c;以及它在不…

電路基礎之電容

電容器&#xff08;Capacitor&#xff09;是由兩個導體電極之間夾著一個電介質而組成的元件。這兩個電極可以是金屬板、箔片、涂層等&#xff0c;而電介質則是放置在電極之間的絕緣材料。電容器的基本構成包括以下幾個要素&#xff1a; 電極&#xff1a;電容器的電極是兩個導體…

Ubuntu系統kubeadm安裝K8S_v1.25.x容器使用docker(K8S_v1.24版本以后依然使用docker容器管理)

安裝所需要的全部文檔請點擊這里下載 系統是&#xff1a; rootk8s-master:~# cat /etc/lsb-release DISTRIB_IDUbuntu DISTRIB_RELEASE22.04 DISTRIB_CODENAMEjammy DISTRIB_DESCRIPTION“Ubuntu 22.04.3 LTS” rootk8s-master:~# uname -a Linux k8s-master 5.15.0-76-generi…

js合并數組對象(將數組中具有相同屬性對象合并到一起,組成一個新的數組)

一、根據數組對象中某一key值&#xff0c;合并相同key值的字段&#xff0c;到同一個數組對象中&#xff0c;組成新的數組 1.原數組&#xff1a; var array [{ id: 1, name: Alice },{ id: 2, name: Bob },{ id: 1, age: 25 },{ id: 3, name: Charlie, age: 30 } ];2.合并后數…

C++隱式調用和explicit關鍵字

隱式類型轉換 #include <iostream> using namespace std;class Point { public:int x, y;Point(int x 0, int y 0): x(x), y(y) {} };void displayPoint(const Point& p) {cout << "(" << p.x << "," << p.y <&l…

接口測試實戰,Jmeter正則提取響應數據-詳細整理,一篇打通...

目錄&#xff1a;導讀 前言一、Python編程入門到精通二、接口自動化項目實戰三、Web自動化項目實戰四、App自動化項目實戰五、一線大廠簡歷六、測試開發DevOps體系七、常用自動化測試工具八、JMeter性能測試九、總結&#xff08;尾部小驚喜&#xff09; 前言 在測試時&#xf…

服務器安裝JDK

三種方法 方法一&#xff1a; 方法二&#xff1a; 首先登錄到Oracle官網下載JDK JDK上傳到服務器中&#xff0c;記住文件上傳的位置是在哪里&#xff08;我放的位置在/www/java&#xff09;&#xff0c;然后看下面指示進行安裝 方法三&#xff1a; 首先登錄到Oracle官網下載…

Skywalking-9.6.0系列之本地源碼編譯并啟動

Skywalking相信有很多人使用過&#xff0c;通過容器或者下載安裝包進行安裝的&#xff0c;今天從源代碼角度&#xff0c;拉取、構建、啟動。 官方文檔步驟簡潔明了&#xff0c;我這邊會結合自己遇到的一些問題做出總結。 當前構建資源版本&#xff1a; MAC 10.15.7IDEA 2021.…

基于STM32CUBEMX驅動TMOS模塊STHS34PF80(1)----獲取ID

基于STM32CUBEMX驅動TMOS模塊STHS34PF80----1.獲取ID 概述樣品申請視頻教程所有功能接口最小系統圖生成STM32CUBEMX串口配置IIC配置IO口設置串口重定向 模塊地址參考demoIIC寫函數IIC讀函數參考程序初始化獲取ID主函數 概述 STHS34PF80 是一款非冷卻、工廠校準的紅外運動和存在…