CPP繼承

繼承

一、繼承概述

1、為什么需要繼承

如下示例,Person 類、Student 類、Teacher 類有大量重復的代碼,造成代碼冗余,降低開發效率。

在這里插入圖片描述

我們可以通過繼承來解決這一問題。在面向對象的編程語言中,繼承是一個核心概念。主要作用將重復的代碼統一定義在父類中,子類從父類繼承,同時繼承也是實現多態的重要條件。

2、什么是繼承

繼承就是一個新類從現有類派生的過程。新類稱之為派生類或子類,原有的類稱之為基類或父類;子類可以繼承父類中的成員,從而可以提高代碼的可重用性。

在這里插入圖片描述
繼承關系下,子類和父類存在 is a 的 關系。例如,狗是動物,貓是動物,老虎是一個動物等等。那么可以說動物類是一個父類,老虎、貓、狗都是動物類的子類。

在這里插入圖片描述
在繼承關系下父類更通用,子類更具體。也就是說父類擁有子類的共同特性,子類可以具備獨有的特性。
在這里插入圖片描述

二、繼承的實現

C++ 中類實現繼承的形式如下:

class 派生類名:[繼承方式]基類名 //默認是private繼承方式
{
}

繼承方式有 3 種類型,分別為共有型 (public),保護型 (protected) 和私有型 (private);: 表示基類和派生類之間的繼承關系的符號。

示例:

Person 類

Person 類作為父類,其包含了 public 修飾的屬性:

#pragma once
#include <string>
class Person
{
public:
std::string name;
int age;
};

Student 類

繼承了 Person 類,子類從父類繼承 public 成員

Student.h

#pragma once
#include "Person.h"
class Student: public Person
public:
void show();
};

Student.cpp

#include "Student.h"
#include <iostream>
using namespace std;
void Student::show()
{
cout << "name:" << name << endl;
cout << "age:" << age << endl;
}

Main.cpp

#include <iostream>
#include "Student.h"
int main()
Student s;
s.name = "張三"; //從父類繼承的成員
s.age = 20;
s.show();
}

三、派生類的訪問控制

在 C++ 中,類成員的訪問權限分為 public (公共)、protected (受保護) 或 private (私有) 3 種。其中父類的 public 和 protected 成員允許子類繼承,private 成員不能被繼承。

以 public 繼承模式為例,訪問控制權限如下:

訪問publicprotectedprivate
同一個類yesyesyes
派生類yesyesno
外部的類yesnono

類 A 的定義:

#pragma once
class A
{
public:
int num_public; //公有的成員任何類都可以訪問
protected:
int num_protected; //受保護的成員可以在當前類和子類中訪問
private:
int num_private; //私有的成員只能在當前類中訪問
};

類 B 繼承于類 A:

#pragma once
#include "A.h"
class B: public A
public:
B();
B(int a, int b);
void print();
};

類 B 中訪問父類中的成員:

#include "B.h"
#include <iostream>
using namespace std;
B::B() {}
B::B(int a, int b)
{
this->num_public = a;
this->num_protected = b;
}
void B::print()
{
cout << "public:" << num_public << endl;
cout << "protected:" << num_protected << endl;
//cout << "private:" << num_private << endl; //編譯錯誤,私有成員,不能被子類繼承
}

四、繼承類型

C++ 支持三種繼承類型,分別是 public、protected 及 private 類型,這些繼承類型影響著基類成員在派生類中的訪問權限。

1、訪問權限變化總覽
基類成員權限            public繼承         protected繼承         private繼承
---------------------------------------------------------------------------
public成員    ───?   public(外部可訪問)  protected(外部不可)   private(外部不可)
protected成員 ───?   protected(外部不可) protected(外部不可)   private(外部不可)
private成員   ───?   不可訪問               不可訪問                不可訪問
  • 繼承方式只會影響基類 public / protected 成員在派生類中的可見性,不會影響派生類對自己新成員的訪問控制。
  • private 成員無論哪種繼承方式,子類都不能直接訪問。

直觀理解:

  • public繼承:原汁原味 —— public 還是 public,protected 還是 protected。
  • protected繼承:降一級 —— public 變 protected,protected 不變。
  • private繼承:全收進屋 —— public 和 protected 全變 private。
2、基類定義
#pragma once
#include <iostream>
using namespace std;class Base
{
public:void func_public() { cout << "Base::func_public()" << endl; }protected:void func_protected() { cout << "Base::func_protected()" << endl; }private:void func_private() { cout << "Base::func_private()" << endl; }
};
3、公有繼承 (Public Inheritance)
規則
  • 基類 public 成員 → 派生類 public
  • 基類 protected 成員 → 派生類 protected
  • 外部依然可以訪問繼承的 public 成員
代碼示例

SubPublic.h

#pragma once
#include "Base.h"
class SubPublic : public Base
{
public:void func();
};

SubPublic.cpp

#include "SubPublic.h"
#include <iostream>
using namespace std;void SubPublic::func()
{cout << "[public繼承] 子類內部可以訪問父類 public + protected 成員" << endl;func_public();    // ?func_protected(); // ?
}

測試

SubPublic pub;
pub.func();
pub.func_public(); // ? 外部可訪問
4、私有繼承 (Private Inheritance)
規則
  • 基類 public 成員 → 派生類 private
  • 基類 protected 成員 → 派生類 private
  • 外部無法訪問這些繼承的成員
代碼示例

SubPrivate.h

#pragma once
#include "Base.h"class SubPrivate : private Base
{
public:void func();
};

SubPrivate.cpp

#include "SubPrivate.h"
#include <iostream>
using namespace std;void SubPrivate::func()
{cout << "[private繼承] 子類內部可以訪問父類 public + protected 成員" << endl;func_public();    // ?func_protected(); // ?
}

測試

SubPrivate pri;
pri.func();
// pri.func_public(); // ? 外部不可訪問
5、保護繼承 (Protected Inheritance)
規則
  • 基類 public 成員 → 派生類 protected
  • 基類 protected 成員 → 派生類 protected
  • 外部無法直接訪問,但派生類的子類可以訪問
代碼示例

SubProtected.h

pragma once
#include "Base.h"class SubProtected : protected Base
{
public:void func();
};

SubProtected.cpp

#include "SubProtected.h"
#include <iostream>
using namespace std;void SubProtected::func()
{cout << "[protected繼承] 子類內部可以訪問父類 public + protected 成員" << endl;func_public();    // ?func_protected(); // ?
}

6、保護繼承的子類

Subclass.h

#pragma once
#include "SubProtected.h"class Subclass : public SubProtected
{
public:void test();
};

Subclass.cpp

#include "Subclass.h"
#include <iostream>
using namespace std;void Subclass::test()
{cout << "[保護繼承的子類] 仍然可以訪問父類的 public + protected 成員" << endl;func_public();    // ?func_protected(); // ?
}

測試

Subclass subc;
subc.test();
// subc.func_public(); // ? 外部不可訪問

五、繼承中的構造函數與析構函數

基類中的構造函數、析構函數和拷貝構造函數不能被派生類繼承。

1、構造函數和析構函數的執行順序

繼承關系下:

  • 當派生類對象被創建時,先調用基類的構造函數,然后再調用派生類的構造函數。
  • 析構時順序相反,先調用派生類的析構函數,再調用基類的析構函數。
  • 基類的構造函數、析構函數以及拷貝構造函數不會被繼承到派生類。
#include <iostream>
using namespace std;class A
{
public:A(){cout << "A類構造函數" << endl;}~A(){cout << "A類析構函數" << endl;}
};class B : public A
{
public:B(){cout << "B類構造函數" << endl;}~B(){cout << "B類析構函數" << endl;}
};class C : public B
{
public:C(){cout << "C類構造函數" << endl;}~C(){cout << "C類析構函數" << endl;}
};int main()
{C c;  // 創建C類對象return 0;
}

程序輸出

A類構造函數
B類構造函數
C類構造函數C類析構函數   
B類析構函數
A類析構函數
  • 當你定義(創建)一個對象時,系統會自動調用該對象所屬類的構造函數,用來完成對象的初始化。
  • 當對象的生命周期結束時,系統會自動調用對應類的析構函數,用來完成清理工作(比如釋放內存、關閉文件等)。
2、子類中調用父類構造
  • 當基類只提供帶參數的構造函數且沒有無參構造函數時,派生類必須在其初始化列表中顯式調用基類的有參構造函數,否則編譯會報錯。
  • 如果基類有無參構造函數,則派生類會默認調用基類的無參構造函數。

基類 Person 示例

Person.h

#pragma once
#include <string>class Person
{
public:Person(std::string name);std::string getName();private:std::string name;
};

Person.cpp

#include "Person.h"Person::Person(std::string name) : name(name)
{
}std::string Person::getName()
{return name;
}

派生類 Student 示例

Student.h

#pragma once
#include "Person.h"class Student : public Person
{
public:Student();Student(std::string name);
};

Student.cpp

#include "Student.h"// 當基類無默認構造時,派生類必須顯示調用基類有參構造函數
Student::Student() : Person("")
{
}Student::Student(std::string name) : Person(name)
{
}

測試 main.cpp

#include <iostream>
#include "Student.h"
using namespace std;int main()
{Student s("張三");cout << s.getName() << endl;  // 輸出:張三return 0;
}

Student::Student() : Person("") { }
這是 Student 的無參構造函數,寫法表示:

  • 當創建 Student 對象時,先調用基類 Person 的構造函數,傳入空字符串 "" 初始化 Person 部分。
  • 然后執行 Student 自己的構造函數體(這里為空)。

Student::Student(std::string name) : Person(name) { }
這是帶參數的構造函數,表示:

  • 創建 Student 對象時,先調用基類 Person 的構造函數,傳入參數 name
  • 然后執行 Student 自己的構造函數體(這里為空)。

如果基類沒有無參構造函數,編譯器就不知道用什么參數去初始化基類部分,編譯會失敗。 所以派生類構造函數中必須用初始化列表顯示調用基類構造函數,告訴它該怎么初始化基類。

3、調用順序原因
一、構造函數調用順序:先基類后派生類
  • 當你創建一個派生類對象時,派生類通常會用到基類的成員(包括數據和方法)。
  • 如果基類還沒初始化,派生類就無法安全使用基類的內容。
  • 所以必須先調用基類的構造函數,完成基類部分的初始化,再調用派生類構造函數來初始化派生類自己新增的成員。

這樣做保證了派生類擁有一個“完整且有效”的基類部分,避免使用未初始化數據帶來的錯誤。


二、析構函數調用順序:先派生類后基類
  • 對象銷毀時,派生類先清理自己新增的資源(比如動態申請的內存、打開的文件等)。
  • 清理完派生類資源后,再去銷毀基類成員。
  • 如果先銷毀基類,派生類成員還沒清理完,就會出現訪問已銷毀資源的錯誤。

所以析構時先調用派生類析構函數釋放派生類資源,再調用基類析構函數釋放基類資源,符合“從內到外”的釋放原則。

三、簡單比喻 ---- 把對象想象成建房子

構造(建房子)
蓋房子的時候,先打好地基(基類),確保基礎穩固,
然后再蓋樓層(派生類),一層一層往上建。
先有地基,樓層才能安全搭建。

析構(拆房子)
拆房子時,先拆樓層(派生類),再拆地基(基類),
這樣避免樓層倒塌砸到地基,也保證拆除順序安全有序。

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

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

相關文章

模塊 PCB 技術在未來通信領域的創新突破方向

未來通信領域對數據傳輸速率、信號穩定性及設備集成度的要求持續攀升&#xff0c;模塊 PCB 作為通信設備的關鍵組件&#xff0c;其技術創新成為推動行業發展的核心動力。獵板 PCB 憑借深厚的技術積累與持續的研發投入&#xff0c;在模塊 PCB 技術創新方面取得諸多突破&#xff…

mysql的InnoDB索引總結

MySQL InnoDB索引知識點總結 1. 索引類型 1.1 聚簇索引&#xff08;Clustered Index&#xff09; 定義與特性 定義&#xff1a;聚簇索引是InnoDB的默認存儲方式&#xff0c;數據行按照主鍵的順序物理存儲在磁盤上特性&#xff1a; 每個InnoDB表只能有一個聚簇索引數據頁中的記錄…

C++模板的補充

類模板(上一篇沒講到類模板C/C內存管理&函數模板-CSDN博客&#xff09; 類模板的定義&#xff1a; template<class T1, class T2, ..., class Tn> class 類模板名 {// 類內成員定義 }; 用一個簡單的棧例子講類模板 #define _CRT_SECURE_NO_WARNINGS #include &l…

用JOIN替代子查詢的查詢性能優化

一、子查詢的性能瓶頸分析?重復執行成本?關聯子查詢會導致外層每行數據觸發一次子查詢&#xff0c;時間復雜度為O(M*N)sql-- 典型低效案例 SELECT e.employee_id, (SELECT d.department_name FROM departments d WHERE d.department_id e.department_id) FROM employees e; …

【設計模式】訪問者模式模式

訪問者模式&#xff08;Visitor Pattern&#xff09;詳解一、訪問者模式簡介 訪問者模式&#xff08;Visitor Pattern&#xff09; 是一種 行為型設計模式&#xff08;對象行為型模式&#xff09;&#xff0c;它允許你在不修改對象結構的前提下&#xff0c;為對象結構中的元素添…

比特幣現貨和比特幣合約的區別與聯系

一、基本定義項目現貨&#xff08;Spot&#xff09;合約&#xff08;Futures / Perpetual&#xff09;本質直接買賣比特幣本身買賣比特幣價格的衍生品合約所得資產真實的 BTC合約頭寸&#xff08;沒有直接持有 BTC&#xff09;結算方式交割比特幣現金結算&#xff08;多數平臺&…

Qt/C++開發監控GB28181系統/實時監測設備在線離線/視頻預覽自動重連/重新點播取流/低延遲

一、前言說明 一個好的視頻監控系統&#xff0c;設備掉線后能夠自動重連&#xff0c;也是一個重要的功能指標&#xff0c;如果監控系統只是個rtsp流地址&#xff0c;那非常好辦&#xff0c;只需要重新打開流地址即可&#xff0c;而gb28181中就變得復雜了很多&#xff0c;需要多…

此芯p1開發板使用OpenHarmony時llama.cpp不同優化速度對比(GPU vs CPU)

硬件環境 Cix P1 SoC 瑞莎星睿 O6 開發板 rx580顯卡 產品介紹&#xff1a; https://docs.radxa.com/orion/o6/getting-started/introduction OpenHarmony 5.0.0 使用vulkan后端的llama.cpp &#xff08;GPU&#xff09; # ./llama-bench -m /data/qwen1_5-0_5b-chat-q2_k.…

Android 四大布局:使用方式與性能優化原理

一、四大布局基本用法與特點1. LinearLayout&#xff08;線性布局&#xff09;使用方式&#xff1a;<LinearLayoutandroid:orientation"vertical" <!-- 排列方向&#xff1a;vertical/horizontal -->android:layout_width"match_parent"android:…

Redis的BigKey問題

Redis的BigKey問題 什么是大Key問題&#xff1f; 大key問題其實可以說是大value問題&#xff0c;就是某個key對應的value所占據的存儲空間太大了&#xff0c;所以導致我們在操作這個key的時候花費的時間過長&#xff08;序列化\反序列化&#xff09;&#xff0c;從而降低了redi…

TDengine IDMP 產品基本概念

基本概念 元素 (Element) IDMP 通過樹狀層次結構來組織數據&#xff0c;樹狀結構里的每個節點被稱之為元素 (Element)。元素是一個物理的或邏輯的實體。它可以是具體的物理設備&#xff08;比如一臺汽車&#xff09;&#xff0c;物理設備的一個子系統&#xff08;比如一臺汽車的…

專題二_滑動窗口_將x減到0的最小操作數

一&#xff1a;題目解釋&#xff1a;每次只能移除數組的邊界&#xff0c;移除的邊界的總和為x&#xff0c;要求返回你移除邊界的最小操作數&#xff01;也就是說你最少花幾次移除邊界&#xff0c;就能夠讓這些移除的邊界的和為x&#xff0c;則返回這個次數&#xff01;所以這個…

CentOS 7 下通過 Anaconda3 運行llm大模型、deepseek大模型的完整指南

CentOS 7 下通過 Anaconda3 運行llm大模型、deepseek大模型的完整指南A1 CentOS 7 下通過 Anaconda3 運行大模型的完整指南一、環境準備二、創建專用環境三、模型部署與運行四、優化配置常見問題解決B1 CentOS 7 下通過 Anaconda3 使用 CPU 運行 DeepSeek 大模型的完整方案一、…

Flutter應用在Windows 8上正常運行

要讓Flutter應用在Windows 8上正常運行,需滿足以下前提條件,涵蓋系統環境、依賴配置、編譯設置等關鍵環節: 一、系統環境基礎要求 Windows 8版本 必須是 Windows 8.1(核心支持),不支持早期Windows 8(需升級到8.1,微軟已停止對原版Windows 8的支持)。 確認系統版本:右…

Redis實現消息隊列三種方式

參考 Redis隊列詳解&#xff08;springboot實戰&#xff09;_redis 隊列-CSDN博客 前言 MQ消息隊列有很多種&#xff0c;比如RabbitMQ,RocketMQ,Kafka等&#xff0c;但是也可以基于redis來實現&#xff0c;可以降低系統的維護成本和實現復雜度&#xff0c;本篇介紹redis中實現…

【C++動態版本號生成方案:實現類似C# 1.0.* 的自動構建號】

C動態版本號生成方案&#xff1a;實現類似C# 1.0.* 的自動構建號 在C#中&#xff0c;1.0.*版本號格式會在編譯時自動生成構建號和修訂號。本文將介紹如何在C項目中實現類似功能&#xff0c;通過MSBuild自動化生成基于編譯時間的版本號。 實現原理 版本號構成&#xff1a;主版本…

【算法題】:斐波那契數列

用 JavaScript 實現一個 fibonacci 函數&#xff0c;滿足&#xff1a; 輸入 n&#xff08;從0開始計數&#xff09;輸出第 n 個斐波那契數&#xff08;斐波那契數列從 1 開始&#xff1a;1,1,2,3,5,8,13,21…&#xff09; 示例&#xff1a; fibonacci(0) > 1fibonacci(4) &g…

【YOLOv13[基礎]】熱力圖可視化實踐 | 腳本升級 | 優化可視化效果 | 論文必備 | GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM等

本文將進行添加YOLOv13版本的升級版熱力圖可視化功能的實踐,支持圖像熱力圖可視化、優化可視化效果、 可以選擇使用GradCAMPlusPlus, GradCAM, XGradCAM, EigenCAM, HiResCAM, LayerCAM, RandomCAM, EigenGradCAM。一個參數即可設置是否顯示檢測框等。 原圖 結果圖

ElasticSearch相關術語介紹

1.RESTful風格程序REST(英文全稱為:"Representational State Transfer")指的是一組架構約束條件和原則。它是一種軟件架構風格&#xff08;約束條件和原則的集合&#xff0c;但并不是標準&#xff09;。 REST通過資源的角度觀察網絡&#xff0c;以URI對網絡資源進行…

《從零構建大語言模型》學習筆記4,注意力機制1

《從零構建大語言模型》學習筆記4&#xff0c;自注意力機制1 文章目錄《從零構建大語言模型》學習筆記4&#xff0c;自注意力機制1前言一、實現一個簡單的無訓練權重的自注意力機制二、實現具有可訓練權重的自注意力機制1. 分步計算注意力權重2.實現自注意力Python類三、將單頭…