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

C++ 繼承從語法陷阱到內(nèi)存底層的終極復(fù)習(xí)

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

C++繼承全解——從基礎(chǔ)語法到對(duì)象模型暗坑全吃透

你以為繼承只是簡(jiǎn)單的代碼復(fù)用?
當(dāng)你遇到對(duì)象切片、名字隱藏、菱形繼承數(shù)據(jù)冗余、虛基表編譯報(bào)錯(cuò)、多態(tài)析構(gòu)內(nèi)存泄漏時(shí),才會(huì)明白:
C++ 繼承的本質(zhì),是對(duì)對(duì)象模型、內(nèi)存布局、訪問控制的終極考驗(yàn)。

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

一、繼承的本質(zhì) —— 從代碼冗余到層次化復(fù)用

1.1 為什么需要繼承?

描述 StudentTeacher 時(shí),都會(huì)用到姓名、年齡等公共屬性。不使用繼承,會(huì)出現(xiàn)大量重復(fù)代碼,維護(hù)成本極高。

無繼承:代碼冗余,難以維護(hù)

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

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

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

1.2 繼承基本語法

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

二、訪問控制 —— 權(quán)限的「木桶效應(yīng)」

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

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

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

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

2.2 默認(rèn)繼承規(guī)則

  • class 派生:默認(rèn) private 繼承
  • struct 派生:默認(rèn) public 繼承

2.3 可運(yùn)行代碼示例

#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,內(nèi)部可訪問
        cout << pro << endl;  // ok
    }
};
// private 繼承
class PriDerive : private Base {
public:
    void test() {
        cout << pub << endl;  // ok,內(nèi)部可訪問
        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;
}

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

3.1 向上轉(zhuǎn)型

派生類對(duì)象可以賦值/初始化給:

  • 基類對(duì)象
  • 基類指針
  • 基類引用

這叫向上轉(zhuǎn)型(安全、隱式)。

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

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

派生類對(duì)象內(nèi)存布局:
基類子對(duì)象在前,派生類新增成員在后

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

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

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

4.1 核心規(guī)則

基類和派生類是兩個(gè)獨(dú)立作用域。
只要名字相同,派生類成員會(huì)隱藏基類所有同名成員。

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

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

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 函數(shù)隱藏

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

#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:
    // 隱藏基類兩個(gè) 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;
}

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

4.4 隱藏 / 重寫 / 重載 極簡(jiǎn)對(duì)比

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

五、派生類生命周期:構(gòu)造 & 析構(gòu)

5.1 構(gòu)造函數(shù)規(guī)則

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

【暗坑】
基類構(gòu)造調(diào)用順序 = 繼承列表順序,與初始化列表順序無關(guān)。

5.2 析構(gòu)函數(shù):內(nèi)存泄漏的重災(zāi)區(qū)

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

基類析構(gòu)函數(shù)必須加 virtual

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

【鐵律】
一個(gè)類只要可能被繼承,析構(gòu)函數(shù)就必須是 virtual。

六、多繼承與菱形繼承:底層災(zāi)難與虛繼承

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. 數(shù)據(jù)冗余Assistant 包含兩份 Person
  2. 訪問二義性a._name 編譯報(bào)錯(cuò),不知道用哪一份

6.3 虛繼承:解決菱形繼承

中間派生類加上 virtual

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

最終:整個(gè)繼承體系中只有一份 Person

工程建議

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

七、類模板繼承

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

我們以stack繼承vector為例子
代碼實(shí)現(xiàn)

#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,因?yàn)榫幾g器在沒有實(shí)例化之前是不會(huì)進(jìn)入到類模板中的,即訪問不到vector內(nèi)部的函數(shù)和成員,需要指定類域,要求編譯器去vector里面尋找
		this->push_back(data);
		//或
		vector<T>::push_back(data)
	}
};

7.2.模板與虛函數(shù)

模板函數(shù)是不允許繼承的,因?yàn)樘摵瘮?shù)在繼承之前就需要計(jì)算虛函數(shù)表的大小,而模板導(dǎo)致無法計(jì)算

八、繼承 vs 組合

8.1.定義

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

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

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

九、如何終結(jié)繼承

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

9.1.C++11方案:final

在不期望被繼承的類和函數(shù)后面加上final關(guān)鍵字

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

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

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

9.2.C++98方案:構(gòu)造函數(shù)私有

原理:派生類在構(gòu)造時(shí)必定要調(diào)用基類的構(gòu)造函數(shù),基類的構(gòu)造函數(shù)私有化就導(dǎo)致派生類構(gòu)造不了,這樣也就沒辦法繼承了,不如final直觀,較為麻煩

class Base{
private:
	Base(){}
public:
	//讓外面能夠?qū)嵗?
	static Base* create(){
		return new Base();
	}
}
// Base b; error
// Base d = Base::create();ok
// class D = Base(); error

結(jié)語:給讀者的總結(jié)

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

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

掌握了這些底層原理,你才能在面對(duì)C++復(fù)雜的對(duì)象模型時(shí)游刃有余,寫出健壯、高效、易維護(hù)的面向?qū)ο蟪绦颍?/p>

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

相關(guān)文章

最新評(píng)論

铅山县| 延边| 布尔津县| 柯坪县| 射阳县| 古蔺县| 锦屏县| 富顺县| 湖南省| 台中县| 托克托县| 秦皇岛市| 冕宁县| 淳安县| 澄城县| 乐平市| 佛教| 江门市| 庄河市| 望城县| 保定市| 云霄县| 南川市| 元江| 五原县| 六安市| 兴国县| 石景山区| 方城县| 新民市| 和林格尔县| 鹤壁市| 井研县| 德昌县| 金沙县| 新化县| 霍山县| 大埔区| 会泽县| 古田县| 清水县|