【C++面向對象】--- 繼承 的奧秘(下篇)

個人主頁:平行線也會相交💪
歡迎 點贊👍 收藏? 留言? 加關注💓本文由 平行線也會相交 原創
收錄于專欄【C++之路】💌
本專欄旨在記錄C++的學習路線,望對大家有所幫助🙇?
希望我們一起努力、成長,共同進步。🍓
在這里插入圖片描述

目錄

  • 一、作用域
    • 出個小題
    • 小總結
  • 二、派生類的默認成員函數
    • 構造函數
    • 拷貝構造函數
    • 賦值運算符重載
    • 析構函數
    • 小總結
  • 三、繼承與友元
  • 四、繼承和靜態成員

一、作用域

接下來對C++繼承體系中的作用域展開分析。

在C++繼承體系中,子類和父類有各自的作用域,所以子類和父類可以定義同名的成員

請看針對不同作用域的舉例:

局部域和當前類域在這里插入圖片描述
這里有個小概念:
隱藏/重定義子類和父類有同名成員時,子類的成員隱藏了父類的成員。(如上左圖所示)

指定當前的父域:
在這里插入圖片描述

作用域當然也對成員函數起作用,請看:
在這里插入圖片描述

出個小題

類B和類A中的fun()函數有什么關系。

class A
{
public:void fun(){cout << "fun()" << endl;}
};
class B : public A
{
public:void fun(int i){cout << "fun(int i)" << endl;}
};

B中的fun和A中的fun構成隱藏,成員函數滿足函數名相同就構成隱藏。并不會構成函數重載(因為函數重載針對的是不同的作用域)
在這里插入圖片描述

小總結

  • 在繼承體系中基類和派生類都有獨立的作用域
  • 子類和父類中有同名成員,子類成員將屏蔽父類對同名成員的直接訪問,這種情況叫隱藏,
    也叫重定義。(在子類成員函數中,可以使用基類::基類成員進行顯示訪問,舉個例子就比如說:B b; b.A::fun();
  • 需要注意的是如果是成員函數的隱藏,只需要函數名相同就構成隱藏。
  • 但是其實在實際中在繼承體系里面最好不要定義同名的成員(省的給自己添麻煩)。

二、派生類的默認成員函數

再來回顧一下C++中的6個默認成員函數:構造函數、析構函數、拷貝構造函數、賦值運算符重載、取地址及const取地址運算符重載。

構造函數

class Person
{
public:Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}~Person(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public:Student(const char* name = "李四",int id = 0):_id(0){}
protected:int _id;
};
int main()
{Student s;return 0;
}

運行結果如下:
在這里插入圖片描述
上述代碼中我們并沒有定義Person類對象,但是卻調用了Person類中的默認構造函數,為什么呢?

因為C++規定了派生類必須調用父類的成員函數來初始化父類的成員變量。
在這里插入圖片描述
這里是在初始化列表來調用父類中的默認成員函數的。

在來看下面的情況,請看:
在這里插入圖片描述
解釋在創建Student對象時,先調用Person類的構造函數來初始化Person類的成員變量_name,然后再調用Student類的構造函數來初始化Student類的成員變量_id。
所以這里是Person類中的成員函數先進行初始化,然后再對Student中的成員進行初始化。即派生類的構造函數在執行之前,基類的構造函數必須首先完成。

重點:通過使用初始化列表,并在其中調用基類的構造函數來初始化基類的成員變量,可以確保在派生類的構造函數中正確初始化基類的數據成員這是由于派生類的構造函數在執行之前,基類的構造函數必須首先完成。

拷貝構造函數

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(){cout << "~Person()" << endl;}
protected:string _name; // 姓名
};
class Student : public Person
{
public://構造函數Student(const char* name = "李四",int id = 0):_id(0),Person(name){}//拷貝構造函數Student(const Student& s):Person(s), _id(s._id){}
protected:int _id;
};
int main()
{Student s1;Student s2(s1);return 0;
}

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

如果我們去掉基類拷貝構造函數中的Person(s)會怎樣呢(即沒有顯式調用基類中的拷貝構造函數)?

解析:去掉Person(s)將導致基類Person的成員變量_name不會被復制,而是會調用基類中的默認構造函數,而倘若此時基類也沒有提供默認構造函數的話就會直接報錯。
在這里插入圖片描述
所以,我們應該顯式調用拷貝構造函數。如下:

//拷貝構造函數
Student(const Student& s):Person(s)//這里要顯式調用拷貝構造函數,否則會調用基類中的默認構造函數, _id(s._id)
{}

一句話總結派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化。

賦值運算符重載

//父類賦值運算符重載
Person& operator=(const Person& p)
{cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;
}
//子類賦值運算符重載
Student& operator=(const Student& s)
{cout << "Student& operator= (const Student& s)" << endl;if (this != &s){Person::operator=(s);_id = s._id;}return *this;
}

運行結果如下:
在這里插入圖片描述
在這里插入圖片描述
這里有的小伙伴看到Student s2 = s1;可能會產生疑惑,為什么這里不調用賦值運算符重載函數。
解答

因為在語句Student s2 = s1;中,發生的是對象的初始化,而不是賦值操作
當使用Student s2 = s1;來初始化一個已存在的對象s2時,會調用拷貝構造函數而不是賦值運算符重載函數。拷貝構造函數用來創建一個新對象,并將其內容初始化為另一個同類型對象的副本。
如果要調用賦值運算符重載函數,需要使用賦值操作符=來對已存在的對象進行賦值,例如s2 = s1;。這樣才會調用賦值運算符重載函數,將s1的值賦給s2。

析構函數

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

在C++中,無法顯式調用父類的析構函數。當一個派生類對象被銷毀時,首先會自動調用派生類的析構函數,然后再自動調用基類的析構函數(即按照先父后子的順序來完成對對象的析構)
如果要顯式調用是沒有辦法保證先子后父進行析構的。

小總結

  • 派生類的構造函數必須調用基類的構造函數初始化基類的那一部分成員。如果基類沒有默認
    的構造函數,則必須在派生類構造函數的初始化列表階段顯示調用。
  • 派生類的拷貝構造函數必須調用基類的拷貝構造完成基類的拷貝初始化。
  • 派生類的operator=必須要調用基類的operator=完成基類的復制。
  • 派生類的析構函數會在被調用完成后自動調用基類的析構函數清理基類成員。即按照先清理派生類對象,再清理基類對象的順序
  • 派生類對象初始化先調用基類構造再調派生類構造;同時派生類對象初始化先調用基類構造再調派生類構造。

三、繼承與友元

友元關系不能繼承,即基類友元不能訪問子類私有和保護成員,基類的友元只能訪問基類的成員而不能訪問派生類的成員。

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

在這里插入圖片描述

解釋:Person類和Student類互相引用對方作為友元函數,因此需要先進行一次前向聲明(即開頭的class Student;。這樣可以確保在實際定義這兩個類的成員函數之前,編譯器已經知道這兩個類的存在。

四、繼承和靜態成員

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; // 研究科目
};
int main()
{Person p;Student s;cout << &p._name << endl;cout << &s._name << endl;cout << &p._count << endl;cout << &s._count << endl;cout << &Person::_count << endl;cout << &Student::_count << endl;
}

運行結果如下:
在這里插入圖片描述
靜態成員變量是一種屬于類而不是類的實例的變量。它在所有類的實例之間共享,并且在整個程序的生命周期中只存在一個副本。靜態成員變量是在類定義外部進行初始化的

靜態成員變量適用于在類的多個實例之間共享數據,并且可以通過類名直接訪問,而無需實例化類對象。它們在數據共享和數據統計方面非常有用。需要注意的是,靜態成員變量僅屬于類,而不屬于類的任何特定實例。

靜態成員變量的訪問方式:靜態成員變量可以使用類名::成員變量名的方式進行訪問(即類名::成員變量名),例如Person::_count

下面請看下面代碼,要統計Person類及其Person派生類對象總共創建了多少個

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; // 研究科目
};
int main()
{Person p;Student s1;Student s2;Student s3;Graduate s4;cout << Person::_count << endl;
}

運行結果:Person類及其派生類對象總共創建了4個對象

解釋:在代碼中,將_count定義為靜態成員變量是為了在整個類層級中共享同一個計數變量。當創建派生類對象時,構造函數會依次調用每個類的構造函數,包括父類的構造函數。所以在父類的構造函數中進行++_count操作,可以確保每個派生類對象的創建都能正確地增加計數。

好了,本文到這里就結束了,希望對大家學習C++繼承體系有所幫助。
再見啦,友友們!!!

在這里插入圖片描述

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

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

相關文章

Vim基本使用

Vim基本使用 概念模式類型常規模式編輯模式命令模式 概念 vim 是一款功能豐富、高度可定制和高效的文本編輯器&#xff0c;適用于處理各種文本文件和編程任務。熟練使用vim幫助提高編輯效率&#xff0c;并為用戶提供更多的操作選項。 模式類型 常規模式 使用vim打開一個文件…

Postman接口自動化測試實戰,從0到1一篇徹底打通...

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

【網絡基礎】傳輸層

【網絡基礎】傳輸層 文章目錄 【網絡基礎】傳輸層1、端口號1.1 工具 2、UDP協議2.1 協議端格式2.2 UDP特點2.3 傳輸數據報2.4 緩沖區2.5 基于UDP應用層協議2.6 使用注意事項 3、TCP協議3.1 協議段格式3.2 ACK機制3.3 超時重傳機制3.4 連接管理機制3.5 滑動窗口3.6 流量控制3.7 …

207、仿真-51單片機脈搏心率與血氧報警Proteus仿真設計(程序+Proteus仿真+配套資料等)

畢設幫助、開題指導、技術解答(有償)見文未 目錄 一、硬件設計 二、設計功能 三、Proteus仿真圖 四、程序源碼 資料包括&#xff1a; 需要完整的資料可以點擊下面的名片加下我&#xff0c;找我要資源壓縮包的百度網盤下載地址及提取碼。 方案選擇 單片機的選擇 方案一&a…

猿輔導Motiff與IXDC達成戰略合作,將在UI設計領域推動AI革新更多可能性

近日&#xff0c;“IXDC 2023國際體驗設計大會”在北京國家會議中心拉開序幕&#xff0c;3000設計師、1000企業、200全球商業領袖&#xff0c;共襄為期5天的用戶體驗創新盛會。據了解&#xff0c;此次大會是以“設計領導力”為主題&#xff0c;分享全球設計、科技、商業的前沿趨…

報錯解決:matlab機器人工具箱不支持將腳本 DHFactor 作為函數執行

matlab使用機器人工具箱出現報錯&#xff1a; 不支持將腳本 DHFactor 作為函數執行: D:\MATLAB\install\toolbox\rvctools\robot\DHFactor.m 解決辦法&#xff1a;重新到上圖的rvctool重重新安裝一下工具箱就好了。 到目錄"$機器人工具箱路徑$\rvctools" 在matlab命…

使用Scanner接收用戶輸入

掃描輸入的兩種方式 Scanner主要提供了兩個方法來掃描輸入&#xff1a; &#xff08;1&#xff09;hasNextXxx()&#xff1a;是否還有下一個輸入項&#xff0c;Xxx可以是Int&#xff0c;Long等代表基本數據類型的字符串。 如果只是判斷是否包含下一個字符串&#xff0c;則直…

新手開抖店多久可以出單?

?開抖店是一種越來越流行的創業方式&#xff0c;在社交媒體平臺上開店銷售各種商品&#xff0c;比如服裝、配飾、美妝和家居用品等等。對于新手來說&#xff0c;他們可能會很關心自己開抖店能夠多久出單。雖然這個問題沒有一個固定的答案&#xff0c;但是以下是一些關鍵的運營…

【boost網絡庫從青銅到王者】第三篇:asio網絡編程中的buffer緩存數據結構

文章目錄 1、關于buffer數據結構1.1、簡單概括一下&#xff0c;我們可以用buffer() 函數生成我們要用的緩存存儲數據。1.2、但是這太復雜了&#xff0c;可以直接用buffer函數轉化為send需要的參數類型:1.3、output_buf可以直接傳遞給該send接口。我們也可以將數組轉化為send接受…

docker發展歷史

docker 一、docker發展歷史很久以前2013年2014年2015年2016年2017年2018年2019年及未來 二、 docker概述定義&#xff1a;docker底層運行原理:docker簡述核心概念容器特點Docker與虛擬機的區別: 三、容器在內核中支持兩種重要技術四、namespace的六項隔離五、虛擬化產品有哪些1…

CAS 的執行流程 ?CAS 中 ABA 問題如何解決 ?CAS 在 Java 中有哪些實現類 ?

目錄 1. CAS 的執行流程 2. CAS 中的 ABA 問題 3. 如何解決 CAS 中的 ABA 問題 4.CAS 在Java 中的實現類有哪些 1. CAS 的執行流程 CAS 比較并替換的大致流程是這樣的&#xff1a; 它有三個操作單位&#xff1a;V&#xff08;內存值&#xff09;&#xff0c;A&#xff08;…

3D沉浸式旅游網站開發案例復盤【Three.js】

Plongez dans Lyon網站終于上線了。 我們與 Danka 團隊和 Nico Icecream 共同努力&#xff0c;打造了一個令我們特別自豪的流暢的沉浸式網站。 這個網站是專為 ONLYON Tourism 和會議而建&#xff0c;旨在展示里昂最具標志性的活動場所。觀看簡短的介紹視頻后&#xff0c;用戶…

Android 面試筆記整理-Binder機制

作者&#xff1a;浪人筆記 面試可能會問到的問題 從IPC的方式問到Binder的優勢為什么zygote跟其他服務進程的通訊不使用BinderBinder線程池和Binder機制 等等這些問題都是基于你對Binder的理解還有對其他IPC通訊的理解 IPC方式有多少種 傳統的IPC方式有Socket、共享內存、管道…

llvm-dyn_cast模板函數

dyn_cast dyn_cast是LLVM中用于執行安全的向下轉型&#xff08;downcasting&#xff09;的一個模板函數。在C中&#xff0c;向下轉型是將基類的指針或引用轉換為派生類的指針或引用。這種轉型在運行時進行&#xff0c;如果轉型失敗&#xff08;即&#xff0c;如果基類的對象實…

云計算虛擬仿真實訓平臺

一、云計算虛擬仿真系統概述 云計算虛擬仿真系統是一種基于云計算技術和虛擬化技術的系統&#xff0c;用于實現各種仿真和模擬任務。它可以提供強大的計算能力和資源管理&#xff0c;為用戶提供靈活、高效、可擴展的仿真環境。 該系統通常由一組服務器、網絡和存儲設備組成&am…

uniapp開發小程序-有分類和列表時,進入頁面默認選中第一個分類

一、效果&#xff1a; 如下圖所示&#xff0c;進入該頁面后&#xff0c;默認選中第一個分類&#xff0c;以及第一個分類下的列表數據。 二、代碼實現&#xff1a; 關鍵代碼&#xff1a; 進入頁面時&#xff0c;默認調用分類的接口&#xff0c;在分類接口里做判斷&#xff…

Linux c語言字節序

文章目錄 一、簡介二、大小端判斷2.1 聯合體2.2 指針2.3 網絡字節序 一、簡介 字節序&#xff08;Byte Order&#xff09;指的是在存儲和表示多字節數據類型&#xff08;如整數和浮點數&#xff09;時&#xff0c;字節的排列順序。常見的字節序有大端字節序&#xff08;Big En…

神經網絡基礎-神經網絡補充概念-08-邏輯回歸中的梯度下降算法

概念 邏輯回歸是一種用于分類問題的機器學習算法&#xff0c;而梯度下降是優化算法&#xff0c;用于更新模型參數以最小化損失函數。在邏輯回歸中&#xff0c;我們使用梯度下降算法來找到最優的模型參數&#xff0c;使得邏輯回歸模型能夠更好地擬合訓練數據。 邏輯回歸中的梯…