最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C++ 繼承從語法陷阱到內存底層的終極復習

 更新時間:2026年05月25日 10:29:53   作者:紐扣667  
本文深入解析C++繼承,從基礎語法到對象模型暗坑,涵蓋訪問控制、內存布局、菱形繼承等難題,教你如何規(guī)避對象切片、名字隱藏、虛基表等常見陷阱,助你寫出健壯高效代碼,感興趣的朋友一起看看吧

C++繼承全解——從基礎語法到對象模型暗坑全吃透

你以為繼承只是簡單的代碼復用?
當你遇到對象切片、名字隱藏、菱形繼承數據冗余、虛基表編譯報錯、多態(tài)析構內存泄漏時,才會明白:
C++ 繼承的本質,是對對象模型、內存布局、訪問控制的終極考驗。

本文帶你從零開始,吃透 C++ 繼承的所有語法、底層原理與暗坑。

一、繼承的本質 —— 從代碼冗余到層次化復用

1.1 為什么需要繼承?

描述 StudentTeacher 時,都會用到姓名、年齡等公共屬性。不使用繼承,會出現大量重復代碼,維護成本極高。

無繼承:代碼冗余,難以維護

#include <iostream>
#include <string>
using namespace std;
class Student {
public:
    string name;
    int age;
    string studentId;
    void study() { cout << name << " 正在學習" << endl; }
};
class Teacher {
public:
    string name;
    int age;
    string course;
    void teach() { cout << name << " 正在授課" << endl; }
};

繼承的解決方案
把公共成員抽取到基類(父類),Student/Teacher 作為**派生類(子類)**復用基類成員。

// 繼承:代碼復用 + 層次清晰
class Person {
public:
    string name;
    int age;
};
// 派生類 : 繼承方式 基類
class Student : public Person {
public:
    string studentId;
    void study() { cout << name << " 正在學習" << endl; }
};
class Teacher : public Person {
public:
    string course;
    void teach() { cout << name << " 正在授課" << endl; }
};

1.2 繼承基本語法

class 派生類名 : 繼承方式 基類名 {
    // 派生類新增成員
};

二、訪問控制 —— 權限的「木桶效應」

繼承的核心難點:基類成員在派生類中的最終權限,不是繼承方式說了算,也不是原權限說了算,而是取更小的那個。

2.1 核心公式(背會這一條就夠)

最終權限 = min(基類成員權限, 繼承方式)
權限強度:public > protected > private

繼承方式基類 public基類 protected基類 private
publicpublicprotected不可訪問
protectedprotectedprotected不可訪問
privateprivateprivate不可訪問
兩個關鍵理解【面試高頻】
  1. 不可訪問 ≠ 不存在
    基類 private 成員依然會被派生類對象包含(占內存),只是語法上禁止直接訪問。
  2. protected 是為繼承而生
    • private:自己能用,子類不能用;
    • protected:自己能用,子類能用,外部不能用。

2.2 默認繼承規(guī)則

  • class 派生:默認 private 繼承
  • struct 派生:默認 public 繼承

2.3 可運行代碼示例

#include <iostream>
using namespace std;
class Base {
public:
    int pub = 1;
protected:
    int pro = 2;
private:
    int pri = 3; // 子類不可直接訪問
};
// public 繼承
class PubDerive : public Base {
public:
    void test() {
        cout << pub << endl;  // ok
        cout << pro << endl;  // ok
        // cout << pri << endl; // error
    }
};
// protected 繼承
class ProDerive : protected Base {
public:
    void test() {
        cout << pub << endl;  // ok,內部可訪問
        cout << pro << endl;  // ok
    }
};
// private 繼承
class PriDerive : private Base {
public:
    void test() {
        cout << pub << endl;  // ok,內部可訪問
        cout << pro << endl;  // ok
    }
};
int main() {
    PubDerive p;
    p.pub;        // ok
    // p.pro;    // error
    ProDerive pr;
    // pr.pub;   // error,外部無法訪問
    PriDerive pv;
    // pv.pub;   // error,外部無法訪問
    return 0;
}

三、內存視角:對象切片(最容易踩的暗坑)

3.1 向上轉型

派生類對象可以賦值/初始化給:

  • 基類對象
  • 基類指針
  • 基類引用

這叫向上轉型(安全、隱式)

#include <string>
using namespace std;
class Person {
public:
    int _age;
    string _name;
};
class Student : public Person {
public:
    string _studentId;
};
int main() {
    Student s;
    // 賦值給基類對象 → 發(fā)生切片
    Person p = s;
    // 指針/引用 → 不切片
    Person* pp = &s;
    Person& rp = s;
    return 0;
}

3.2 底層真相:切片是什么?

派生類對象內存布局:
基類子對象在前,派生類新增成員在后

Student:[ Person(_name, _age) ][ _studentId ]
  • Person p = s只拷貝基類部分,切掉子類獨有成員 → 對象切片
  • 切片后,p 徹底變成 Person,無法訪問子類任何成員

【致命暗坑】
多態(tài)必須用指針/引用,絕對不能用對象賦值,否則切片會破壞多態(tài)。

四、作用域隱藏:同名就藏,不看參數

4.1 核心規(guī)則

基類和派生類是兩個獨立作用域。
只要名字相同,派生類成員會隱藏基類所有同名成員。

?? 不是重載!不是覆蓋!是隱藏!

  • 重載要求:同一作用域 + 同名不同參;
  • 隱藏要求:不同作用域 + 同名即可。

4.2 變量隱藏

#include <iostream>
using namespace std;
class Base {
public:
    int num = 100;
};
class Derived : public Base {
public:
    int num = 200; // 隱藏基類 num
    void test() {
        cout << num << endl;         // 200(優(yōu)先子類)
        cout << Base::num << endl;   // 100(指定作用域)
    }
};
int main() {
    Derived d;
    d.test();
    return 0;
}

4.3 函數隱藏

只要函數名相同,無論參數是否一致,基類所有重載函數都會被隱藏。

#include <iostream>
using namespace std;
class Base {
public:
    void func() { cout << "Base func" << endl; }
    void func(int x) { cout << "Base func(int)" << endl; }
};
class Derived : public Base {
public:
    // 隱藏基類兩個 func
    void func() { cout << "Derived func" << endl; }
};
int main() {
    Derived d;
    d.func();          // ok
    // d.func(10);     // error!基類func(int)被隱藏
    d.Base::func(10);  // ok,指定作用域
    return 0;
}

【面試題】
基類有多個重載函數 fun(),子類只要定義一個同名函數,基類所有重載版本全部被隱藏。

4.4 隱藏 / 重寫 / 重載 極簡對比

特性重載隱藏重寫(多態(tài))
作用域同一作用域不同作用域不同作用域
函數名相同相同相同
參數不同可同可不同必須相同
virtual不需要不需要必須有
綁定類型靜態(tài)綁定靜態(tài)綁定動態(tài)綁定

五、派生類生命周期:構造 & 析構

5.1 構造函數規(guī)則

  1. 派生類必須初始化基類子對象
  2. 若不顯示調用基類構造,編譯器會隱式調用基類默認構造
  3. 基類無默認構造 → 直接編譯報錯
#include <string>
using namespace std;
class Person {
public:
    // 無默認構造函數
    Person(const string& name) : _name(name) {}
private:
    string _name;
};
class Student : public Person {
public:
    // 正確:必須顯式調用基類構造
    Student(const string& name, const string& id)
        : Person(name), _id(id) {}
private:
    string _id;
};

【暗坑】
基類構造調用順序 = 繼承列表順序,與初始化列表順序無關。

5.2 析構函數:內存泄漏的重災區(qū)

  1. 調用順序:先析構子類 → 再析構父類
  2. 普通析構:不構成多態(tài),基類指針刪除子類對象 → 只調用基類析構 → 內存泄漏
錯誤示例
#include <iostream>
using namespace std;
class Base {
public:
    ~Base() { cout << "基類析構" << endl; }
};
class Derived : public Base {
public:
    ~Derived() { cout << "子類析構" << endl; }
};
int main() {
    Base* ptr = new Derived;
    delete ptr;
    // 輸出:僅 基類析構 → 子類資源泄漏!
    return 0;
}
正確寫法

基類析構函數必須加 virtual

class Base {
public:
    virtual ~Base() { cout << "基類析構" << endl; }
};

【鐵律】
一個類只要可能被繼承,析構函數就必須是 virtual。

六、多繼承與菱形繼承:底層災難與虛繼承

6.1 多繼承語法

class Derived : public Base1, public Base2 {};

6.2 菱形繼承的模型圖

        Person
       /      \
   Student     Teacher
       \      /
     Assistant
#include <string>
using namespace std;
class Person { public: string _name; };
class Student : public Person {};
class Teacher : public Person {};
class Assistant : public Student, public Teacher {};
兩大致命問題
  1. 數據冗余Assistant 包含兩份 Person
  2. 訪問二義性a._name 編譯報錯,不知道用哪一份

6.3 虛繼承:解決菱形繼承

中間派生類加上 virtual

class Student : virtual public Person {};
class Teacher : virtual public Person {};
class Assistant : public Student, public Teacher {};
底層原理
  • 增加 vbptr(虛基表指針)
  • 指向 vbtable(虛基表)
  • 表中存放:當前指針到唯一虛基類子對象的偏移

最終:整個繼承體系中只有一份 Person

工程建議

盡量避免菱形繼承,邏輯復雜、性能略低、可讀性差。

七、類模板繼承

7.1.類模板繼承需要指定類域

我們以stack繼承vector為例子
代碼實現

#include<iostream>
#include<vector>
using namespace std;
template<class T>
class Mystack:pubilc vector<T>{
	void push(const T& data){
		//push_back(data) error:找不到push_back,因為編譯器在沒有實例化之前是不會進入到類模板中的,即訪問不到vector內部的函數和成員,需要指定類域,要求編譯器去vector里面尋找
		this->push_back(data);
		//或
		vector<T>::push_back(data)
	}
};

7.2.模板與虛函數

模板函數是不允許繼承的,因為虛函數在繼承之前就需要計算虛函數表的大小,而模板導致無法計算

八、繼承 vs 組合

8.1.定義

  • 繼承(is-a):Derived是一種Base,例如狗是一種動物
  • 組合(has-a):Class包含另一個類作為成員,例如車有引擎

8.2.優(yōu)先使用組合

  1. 白箱 vs 黑箱:繼承是一種白箱調用,派生類能夠訪問基類的public成員,甚至protected成員,導致基類的實現細節(jié)暴露,基類一變動,可能影響所有的派生類,而組合是一種黑箱調用,只能通過特點的函數接口訪問成員變量,屏蔽了底層細節(jié),耦合度較低
  2. 繼承存在濫用:某些類的關系可能并不符合is-a的情況,比如stack和vector,stack并不支持insert等等接口,可能會破壞類的特點

九、如何終結繼承

有些類可能并不期望被繼承,那么應該怎么做呢?

9.1.C++11方案:final

在不期望被繼承的類和函數后面加上final關鍵字

class Nonderived final{
	//
}
// class Bad : public NonDerived {};  // ? 編譯錯誤

final也能修飾函數,讓派生類無法重寫
示例代碼

class Base {
public:
    virtual void show() final {  // 虛函數 + final
        cout << "Base" << endl;
    }
};
class Derived : public Base {
public:
    // 錯誤!無法重寫被 final 修飾的虛函數
    void show() override { 
        cout << "Derived" << endl;
    }
};

9.2.C++98方案:構造函數私有

原理:派生類在構造時必定要調用基類的構造函數,基類的構造函數私有化就導致派生類構造不了,這樣也就沒辦法繼承了,不如final直觀,較為麻煩

class Base{
private:
	Base(){}
public:
	//讓外面能夠實例化
	static Base* create(){
		return new Base();
	}
}
// Base b; error
// Base d = Base::create();ok
// class D = Base(); error

結語:給讀者的總結

繼承不只是把代碼抄一遍,它涉及到了:

  • 作用域屏蔽(隱藏規(guī)則)
  • 構造析構順序(先父后子,相反析構)
  • 內存布局與切片(對象賦值丟失派生部分)
  • 多態(tài)與虛表(virtual 的神奇之處)
  • 菱形繼承與虛基表(偏移量尋址)
  • 模板繼承中的查找問題(依賴型名稱)

掌握了這些底層原理,你才能在面對C++復雜的對象模型時游刃有余,寫出健壯、高效、易維護的面向對象程序!

到此這篇關于C++ 繼承從語法陷阱到內存底層的終極復習的文章就介紹到這了,更多相關C++ 繼承語法內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C語言編程中實現二分查找的簡單入門實例

    C語言編程中實現二分查找的簡單入門實例

    這篇文章主要介紹了C語言編程中實現二分查找的簡單入門實例,需要的朋友可以參考下
    2015-12-12
  • C++實現掃雷小游戲(控制臺版)

    C++實現掃雷小游戲(控制臺版)

    這篇文章主要為大家詳細介紹了C++實現控制臺版的掃雷小游戲,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-03-03
  • c語言printf實現同一位置打印輸出的實例

    c語言printf實現同一位置打印輸出的實例

    下面小編就為大家?guī)硪黄猚語言printf實現同一位置打印輸出的實例。小編覺得挺不錯的,現在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • c++中ref的作用示例解析

    c++中ref的作用示例解析

    這篇文章主要為大家介紹了c++中ref的作用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-04-04
  • opencv實現機器視覺檢測和計數的方法

    opencv實現機器視覺檢測和計數的方法

    在機器視覺中,有時需要對產品進行檢測和計數。其難點無非是對于產品的圖像分割。本文就來介紹一下機器視覺檢測和計數的實現,感興趣的可以參考一下
    2021-05-05
  • 深入剖析C語言中qsort函數的實現原理

    深入剖析C語言中qsort函數的實現原理

    這篇文章主要介紹了C語言中qsort函數的實現原理,本文將從回調函數,qsort函數的應用,qsort函數的實現原理三個方面進行講解,并通過代碼示例講解的非常詳細,需要的朋友可以參考下
    2024-03-03
  • C語言實現簡單航班管理系統(tǒng)

    C語言實現簡單航班管理系統(tǒng)

    這篇文章主要為大家詳細介紹了C語言實現簡單航班管理系統(tǒng),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-12-12
  • QT實現文件傳輸功能

    QT實現文件傳輸功能

    這篇文章主要為大家詳細介紹了QT實現文件傳輸功能,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • C++ DLL動態(tài)庫的創(chuàng)建與調用(類庫,隱式調用)

    C++ DLL動態(tài)庫的創(chuàng)建與調用(類庫,隱式調用)

    本文主要介紹了C++ DLL動態(tài)庫的創(chuàng)建與調用(類庫,隱式調用),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • C++11 模板參數的“右值引用”是轉發(fā)引用嗎

    C++11 模板參數的“右值引用”是轉發(fā)引用嗎

    這篇文章主要介紹了C++11 模板參數的“右值引用”是轉發(fā)引用嗎,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05

最新評論

阜新市| 长武县| 嵊泗县| 永昌县| 鄂托克前旗| 梁山县| 太保市| 古田县| 宜丰县| 崇州市| 阿克苏市| 玉门市| 沈阳市| 沧州市| 灵丘县| 永登县| 大连市| 青田县| 勃利县| 清河县| 湾仔区| 沐川县| 安新县| 元朗区| 玉山县| 略阳县| 巴里| 那曲县| 辉南县| 建湖县| 东山县| 广水市| 饶阳县| 肃北| 海阳市| 咸宁市| 肇东市| 尉氏县| 老河口市| 宣恩县| 宜阳县|