《C++進階之繼承多態》【普通類/模板類的繼承 + 父類子類的轉換 + 繼承的作用域 + 子類的默認成員函數】

【普通類/模板類的繼承 + 父類&子類的轉換 + 繼承的作用域 + 子類的默認構造函數】目錄

  • 前言:
  • ------------------------
  • 一、繼承的定義和使用
    • 1. 什么使繼承?
    • 2. 為什么要引入繼承?
    • 3. 怎么使用繼承?
      • ① 父類(基類)
      • ② 子類(派生類)
      • ③ 繼承方式
    • 4. 使用繼承需要注意什么?
  • ------------------------
  • 二、模板類的繼承
    • 1. 模板類的繼承有哪些?
      • ① 模板類繼承普通類
      • ② 模板類繼承另一個模板類
    • 2. 模板類之間的繼承需要注意什么?
  • ------------------------
  • 三、父類與子類之間的轉換
    • 1. 什么是父類與子類之間的轉換?
    • 2. 父類與子類之間的轉換有哪些類型?
      • ① 向上轉型(子類轉父類)
      • ② 向下轉型(父類轉子類)
  • ------------------------
  • 四、繼承中的作用域
    • 1. 什么是隱藏?
    • 2. 隱藏有哪些?
      • ① 變量隱藏
      • ② 函數隱藏
    • 3. 關于繼承作用域的相關習題
  • ------------------------
  • 五、子類的默認成員函數
    • 1. 默認構造函數
      • ① 介紹
      • ② 使用
      • ③ 初始化
      • ④ 禁用
    • 2. 拷貝構造函數
      • ① 介紹
      • ② 使用
      • ③ 禁用
    • 3. 拷貝賦值運算符重載函數
      • ① 介紹
      • ② 使用
      • ③ 禁用
      • ④ 拷貝構造 vs 拷貝賦值
    • 4. 析構函數
      • ① 注意事項
      • ② 析構順序
    • 子類中常見的四大默認成員函數的大總結:

在這里插入圖片描述

往期《C++初階》回顧:

《C++初階》目錄導航

前言:

hi~ 小伙伴們大家好呀(ノ′ヮ)ノ*: ・゚!今天可是末伏哦?(。???-)?

這意味著三伏天的前兩伏已經悄悄溜走啦🏃?♂?💨(°▽°)/,從今天起我們就正式進入最后一伏啦~ 末伏固定是 10 天📅(??????)??,也就是說,十天之后,夏天的暑氣就會慢慢消散咯(?′ω?)。

那今天呢,我們要開啟《C++ 進階》的第一課啦🎉(≧?≦)/:
【普通類 / 模板類的繼承 + 父類 & 子類的轉換 + 繼承的作用域 + 子類的默認成員函數】 📚💡(。・ω・。)ノ?
內容是關于 “繼承” 的知識點哦🧬,新的知識帶來新的開始?(ノ?ヮ?)ノ*:・゚?
讓我們一起加油學習吧🚀!沖鴨💪🔥(? ??_??)?

------------------------

一、繼承的定義和使用

1. 什么使繼承?

繼承(Inheritance):是面向對象編程(OOP)中的核心概念之一,它允許一個類(稱為子類派生類)直接擁有另一個類(稱為父類基類)的屬性方法,并可以在此基礎上擴展新的功能或修改原有實現。

  • 通過繼承,子類能復用父類的代碼,減少重復開發,同時形成類之間的層次關系,體現 “is-a”(屬于)的邏輯關系。

2. 為什么要引入繼承?

現在大家試想一下,假如說現在要求你設計兩個類:學生類、教師類,具體需求如下:

  • 學生類
    • 成員變量:需包含學生的姓名、年齡、電話、地址、學號
    • 成員函數:需實現學生的身份認證功能,以及進行學習的行為
  • 教師類
    • 成員變量:需包含教師的姓名、年齡、電話、地址、職稱
    • 成員函數:需實現教師的身份認證功能,以及進行授課的行為

小伙伴們,面對這樣的需求,你會如何設計這兩個類呢?

我相信有不少的小伙伴們是像下面這樣進行設計的。

#include <iostream>
using namespace std;/*--------------------------定義“學生類”--------------------------*/
class Student
{
public:/*----------------成員函數(學生的行為)----------------*///1.實現:“身份的驗證邏輯”的函數void identity(){// 實現身份驗證邏輯(如:調用二維碼掃描接口)// ...}//2.實現:“進行學習”的函數void studing(){// 實現學習邏輯(如:記錄學習時間、課程等)// ...}protected:/*----------------成員變量(學生的屬性)----------------*/string _name;int _age;string _tel;string _address;int _stuid;      // 學號(唯一標識學生身份)
};/*--------------------------定義“教師類”--------------------------*/
class Teacher
{
public:/*----------------成員函數(教師的行為)----------------*///1.實現:“身份的驗證邏輯”的函數void identity(){// 實現教師身份驗證邏輯// ...}//2.實現:“進行授課”的函數void teaching(){// 實現授課邏輯(如:記錄課程內容、學生出勤等)// ...}protected:/*----------------成員變量(教師的屬性)----------------*/string _name;int _age;string _tel;string _address;string _title;    // 職稱(如"教授"、"副教授"等)
};int main()
{return 0;
}

從小伙伴們設計的 Student(學生類)和 Teacher(教師類)可以發現,這兩個類存在不少共性:

  • 都有姓名、年齡、電話、地址這些成員變量
  • 也都有 identity(身份認證)這樣的成員函數

而這些共性內容在兩個類里重復定義,造成了代碼冗余。


同時,它們也存在各自的差異:

  • 成員變量方面,學生獨有 “學號”,老師獨有 “職稱”。
  • 成員函數方面,學生有專屬的 “學習” 函數,老師有專屬的 “授課” 函數 。

所以:我們自然會想到一個問題:就是有沒有一種方法,既能避免上述代碼冗余問題,又能兼容它們各自的差異呢?

有,哈哈,沒錯這種方法就是繼承

  • 我們可以將兩個類的公共成員(如:姓名、年齡、電話、地址、identity 身份認證函數等)提取到一個基類 Person(人)中

  • 然后讓 Student(學生類)和 Teacher(教師類)繼承 Person

這樣一來,它們既能復用基類的公共成員,又能各自添加獨有的成員變量(如:學號、職稱)和成員函數(如:學習、授課),完美解決代碼冗余問題。

#include <iostream>
#include <string>
using namespace std;/*--------------------------定義“基類:人類”--------------------------*/
class Person
{
public:/*----------------成員函數(人的行為)----------------*///1.實現:“身份的驗證邏輯”的函數void identity(){cout << "void identity()"<< endl;}protected:/*----------------成員函數(人的屬性)----------------*/string _name;int _age;string _tel;string _address;
};/*--------------------------定義“派生類:學生類”--------------------------*/
class Student : public Person
{
public:/*----------------成員函數(學生的行為)----------------*///1.實現:“進行學習”的函數void studing(){// 實現學習邏輯(如:記錄學習時間、課程等)// ...}protected:/*----------------成員變量(學生的屬性)----------------*/int _stuid;  // 學號(唯一標識學生身份)
};/*--------------------------定義“派生類:教師類”--------------------------*/
class Teacher : public Person
{
public:/*----------------成員函數(教師的行為)----------------*///1.實現:“進行授課”的函數void teaching(){// 實現授課邏輯(如:記錄課程內容、學生出勤等)// ...}protected:/*----------------成員變量(教師的屬性)----------------*/string _title;    // 職稱(如"教授"、"副教授"等)
};int main()
{//1.創建學生和教師對象Student s;Teacher t;//2.調用繼承自Person類的身份認證方法s.identity();t.identity();return 0;
}

在這里插入圖片描述

3. 怎么使用繼承?

想要使用繼承的話,我們首先要知道繼承的格式是什么樣的?

在這里插入圖片描述

① 父類(基類)

父類(基類):被繼承的類,包含子類共有的屬性和方法。

  • 例如:定義 “動物” 類作為父類,包含 “呼吸”“移動” 等通用方法。

② 子類(派生類)

子類(派生類):繼承父類的類,除了擁有父類的成員,還可以添加新成員或重寫父類方法。

  • 例如:“狗” 類作為子類繼承 “動物” 類,新增 “吠叫” 方法,并重寫 “移動” 方法(:“四條腿跑”)

③ 繼承方式

繼承的類型

  • 公有繼承:子類保留父類成員的訪問權限(public 成員在子類中仍為 public,protected 仍為 protected)
  • 保護繼承:父類的 public 和 protected 成員在子類中變為 protected
  • 私有繼承:父類的 public 和 protected 成員在子類中變為 private(子類內部可訪問,但無法繼續繼承

4. 使用繼承需要注意什么?

基類中的私有成員,無論派生類以何種方式繼承,在派生類中都是不可見的。

  • 這里的 “不可見” 指的是:基類私有成員雖然會被繼承到派生類對象的內存空間中,但語法上禁止派生類在類內或類外訪問它們。

另外需要注意:

  • 使用關鍵字class定義派生類時,默認繼承方式為private

  • 使用struct時,默認繼承方式為public

不過,為了代碼清晰性,最好顯式寫出繼承方式。


注意:在實際開發中,通常只使用public繼承,幾乎不推薦使用protectedprivate繼承。


(。・ω・。)ノ? 看到上面的內容,估計大家都會有點懵吧~(???ω???)💦
畢竟我們之前學的訪問權限,一旦和繼承關聯起來,就會變得異常復雜(這可以說是 C++ 設計中不太好的一點啦),超級容易踩坑(╥﹏╥)!不過沒關系喲~ 博主經過無數次踩坑,還綜合了很多人的經驗,總結出了下面的表格,希望這次能一次性解決大家的心頭之痛呀~(≧?≦)ノ?
如果能幫到你,那博主真的會超開心的哦(。???。)?, (?′ω`?) 偷偷舉手~要是能順便關注博主一下,那就更更更開心啦!

在這里插入圖片描述

------------------------

二、模板類的繼承

前面我們看到的繼承都是 普通類的繼承,其實繼承也可以是模板類的繼承那如何實現模板類的繼承呢?

1. 模板類的繼承有哪些?

模板類的繼承的基本形式:

  • 模板類繼承普通類
  • 模板類繼承另一個模板類

① 模板類繼承普通類

代碼示例:實現“模板類繼承普通類”

#include <iostream>
using namespace std;class Base
{
public:void commonMethod(){cout << "Base method" << endl;}
};
template <typename T>
class Derived : public Base  // 類模板繼承普通類
{
public:T value;void templateMethod(){cout << "Template method: " << value << endl;}
};int main()
{// 使用示例Derived<int> d;d.commonMethod();     // 繼承自Based.templateMethod();   // 模板特有的方法return 0;
}

在這里插入圖片描述

② 模板類繼承另一個模板類

代碼示例:實現“模板類繼承另一個模板類”

#include <iostream>
#include <string>
using namespace std;template <typename T>
class BaseTemplate
{
public:T data;void print(){cout << "Base data: " << data << endl;}
};template <typename T, typename U>
class DerivedTemplate : public BaseTemplate<T> // 繼承時需指定Base的模板參數
{
public:U extraData;void extendedPrint(){cout << "Derived data: " << this->data << ", " << extraData << endl;}
};int main()
{// 使用示例DerivedTemplate<int, string> dt;dt.data = 42;         // 繼承自BaseTemplatedt.extraData = "text";// 派生模板特有的屬性dt.print();           // 繼承自BaseTemplatedt.extendedPrint();   // 派生模板特有的方法return 0;
}

在這里插入圖片描述

2. 模板類之間的繼承需要注意什么?

1. 繼承時需顯式指定基類模板的參數

  • 派生模板必須為基類模板提供類型參數(如:BaseTemplate<T>),可以是:

    • 派生模板自身的類型參數(如:T
    • 具體類型 (如:int
    • 其他模板參數 (如:U
  • 示例:

    
    template <typename T>
    class Base 
    {};template <typename T, typename U>
    class Derived : public Base<T>// 使用派生模板的參數T
    {  // ...
    };template <typename T>
    class Derived2 : public Base<int> // 使用具體類型
    {  // ...
    };
    

2. 基類模板的成員訪問

  • 派生模板中訪問基類模板的成員時,需通過this->顯式指定類域,避免編譯錯誤。(如:Base<T>::member

    template <typename T>
    class Base 
    {
    public:T value;
    };template <typename T>
    class Derived : public Base<T> 
    {
    public:void setValue(const T& val) {this->value = val;  // 必須使用this->或Base<T>::value}
    };
    

代碼示例:實現“stack模板類繼承vector類模板”

#include <iostream>
#include <vector>
using namespace std;namespace mySpace
{//stack類模板:基于vector實現的適配器容器template<class T>class stack : public std::vector<T>{public://1.實現“入棧操作:將元素添加到棧頂”void push(const T& x){    vector<T>::push_back(x);  //注意:我們這里繼承的是“類模板”,這里一定要顯示的指定類域// 等價于:this->push_back(x); 或 std::vector<T>::push_back(x);/* 注意事項:*   基類是類模板時,需顯式指定類域(vector<T>::)*   原因:模板實例化是"按需進行"的*         當stack<int>實例化時,vector<int>的框架被實例化*         但vector<int>的成員函數(如:push_back)尚未實例化*         因此直接調用push_back會導致編譯錯誤(找不到標識符)*/}//2.實現:“出棧操作:移除棧頂元素”void pop(){vector<T>::pop_back();}//3.實現:“獲取棧頂元素引用”(只讀)const T& top(){return vector<T>::back();}//4.實現:“判斷棧是否為空”bool empty(){return vector<T>::empty();}};
}int main()
{//1.創建一個存儲int類型的棧對象mySpace::stack<int> stk;//2.壓入元素:1, 2, 3(棧頂為3)stk.push(1);stk.push(2);stk.push(3);//3.后進先出(LIFO)順序彈出元素while (!stk.empty()){cout << stk.top() << " ";stk.pop();}return 0;
}

在這里插入圖片描述

------------------------

三、父類與子類之間的轉換

1. 什么是父類與子類之間的轉換?

在 C++ 的繼承體系中,父類(基類)與子類(派生類)之間的轉換:是指不同類型對象或指針、引用之間的賦值強制類型轉換,其核心遵循 賦值兼容規則(Liskov 替換原則)

  • 父類(基類)與子類(派生類)之間的轉換是面向對象編程中處理類繼承關系的重要機制,主要涉及指針引用對象三種形式的轉換。

2. 父類與子類之間的轉換有哪些類型?

根據轉換方向可以分為兩類:向上轉型(子類轉父類)向下轉型(父類轉子類),二者的規則和使用場景差異顯著。

① 向上轉型(子類轉父類)

向上轉型(子類轉父類):將子類對象或指針/引用轉換為父類對象或指針/引用

特點

  1. 隱式轉換:向上轉型是隱式的,不需要顯式地進行類型轉換,因為子類對象在邏輯上是父類對象的一種特例。

  2. 安全轉換:向上轉型總是安全的,因為子類對象包含父類的所有屬性和方法。


向上轉型之對象轉換:對象直接賦值

  • public繼承的子類,若直接使用子類對象給父類對象賦值,子類中獨有的成員會被 “截斷”,僅保留從父類繼承的部分,這種情況的我們稱為是 對象切片

    在這里插入圖片描述

  • 示例

    class Parent 
    { int x; 
    };class Child : public Parent 
    { int y; 
    };Parent p;
    Child c;
    p = c;    // 切片:c 的 y 成員被丟棄,p 僅保留從 Parent 繼承的 x
    

向上轉型之指針/引用轉換:子類指針/引用可直接賦值給父類指針/引用,指向子類對象的父類部分。

  • 示例

    Child c;Parent* p = &c;  // 合法,p 指向 c 的父類部分
    Parent& ref = c; // 合法,ref 是 c 的父類部分的別名
    

② 向下轉型(父類轉子類)

向下轉型(父類轉子類):將父類對象或指針/引用轉換為子類對象或指針/引用

特點

  1. 顯式轉換:向下轉型是顯式的,無法自動轉換,需使用 static_castdynamic_cast 等強制類型轉換運算符,因為父類對象可能不包含子類的特有屬性和方法。
  2. 存在風險:向下轉型可能會導致運行時錯誤,因為父類對象可能不包含子類的特有成員。

向下轉型的兩種方式:

  • static_cast(非多態場景):用于非多態類型的轉換,編譯期完成,不檢查轉換的有效性。

    Parent* p = new Child();            // 父類指針指向子類對象
    Child* c1 = static_cast<Child*>(p); // 合法(正確轉換)// 危險!需確保父類指針實際指向子類對象
    Parent p_obj;
    Child* c2 = static_cast<Child*>(&p_obj); // 非法(p_obj 是純父類對象,轉換后訪問子類成員會崩潰)
  • dynamic_cast(多態場景):用于多態類型(父類含虛函數),運行時檢查轉換的有效性。

    • 若轉換成功,返回子類指針/引用。
    • 若失敗,指針返回 nullptr,引用拋出 std::bad_cast 異常。
    class Parent
    {virtual void func() {}  // 含虛函數,支持多態
    };class Child : public Parent
    {};Parent* p = new Child();
    Child* c = dynamic_cast<Child*>(p); // 成功,c 非空Parent p_obj;
    Child& ref = dynamic_cast<Child&>(p_obj); // 拋出 std::bad_cast 異常(p_obj 非子類對象)
    

代碼示例:向下轉型(父類轉子類)的錯誤使用案例

#include <iostream>
using namespace std;class Person
{
protected:string _name;     string _sex;  int _age;
};class Student : public Person
{
public:int _No;  // 學號(Student獨有成員)
};int main()
{Student sobj;  // 創建子類對象//1.子類對象可以賦值給父類的指針/引用(向上轉型,合法)Person* pp = &sobj;  // 父類指針指向子類對象(指向子類中的父類部分)Person& rp = sobj;   // 父類引用綁定到子類對象(引用子類中的父類部分)Person pobj = sobj;  // 子類對象賦值給父類對象(發生對象切片,僅復制父類部分)// 注意:此處通過調用Person類的拷貝構造函數完成賦值,//       僅復制_name、_sex、_age,Student的_No成員被截斷//2. 父類對象不能賦值給子類對象(向下轉型,非法)sobj = pobj;  // 編譯錯誤!父類對象無法自動轉換為子類對象// 原因:父類對象不包含子類的獨有成員(如:_No),//       若允許賦值,會導致子類的_No成員未被初始化return 0;
}

在這里插入圖片描述

------------------------

四、繼承中的作用域

1. 什么是隱藏?

隱藏是指 派生類中的同名成員函數變量覆蓋了基類中的同名成員 ,導致基類成員在派生類作用域內不可直接訪問的現象。

2. 隱藏有哪些?

隱藏規則主要分為以下兩種情況:

  • 變量隱藏:派生類變量覆蓋基類變量
  • 函數隱藏:派生類函數覆蓋基類同名函數

① 變量隱藏

變量隱藏:當派生類定義了與基類同名的變量時,基類變量會被隱藏,派生類對象默認訪問自身的變量。

  • 需要注意的是只需要變量名相同就可以構成隱藏。

代碼示例1:變量隱藏

#include <iostream>
using namespace std;class Base
{
protected:int x = 10;
};class Derived : public Base
{
private:int x = 20;  // 隱藏基類的x
public:void print(){cout <<"x=" << x << endl;       // 輸出20(派生類的x)cout << "Base::x=" << Base::x;  // 顯式訪問基類的x(輸出10)}
};int main()
{Derived d;d.print();   return 0;
}

在這里插入圖片描述

代碼示例2:變量隱藏

#include <iostream>
using namespace std;/*-----------------------定義基類:“Person類”表示通用的個人信息-----------------------*/class Person
{
protected:string _name = "張三";  // 姓名int _num = 111;        // 身份證號(基類成員)
};/*-----------------------定義派生類:“Student類”繼承自Person,新增學號信息-----------------------*/class Student : public Person
{
public:void Print(){//1.訪問從Person繼承的姓名cout << "姓名:" << _name << endl;//2.由于“身份證號”被同名的“學號”隱藏了,所以通過類域顯式指定,訪問基類的_num(身份證號)cout << "身份證號:" << Person::_num << endl;//3.直接訪問_num,默認使用派生類隱藏的成員(學號)cout << "學號:" << _num << endl;}
protected:int _num = 999;   // 學號,注意:這里派生類的成員變量和基類的成員變量“同名”了(所以:派生類成員,隱藏基類的_num)
};int main()
{Student s1;s1.Print();return 0;
}

在這里插入圖片描述

② 函數隱藏

函數隱藏:當派生類定義了與基類同名但參數列表不同的函數時,基類的所有同名函數會被隱藏,即使參數不同也無法直接調用。

  • 需要注意的是只需要函數名相同就可以構成隱藏。
#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:void func(double x)  // 隱藏基類的func()和func(int){cout << "Derived::func(double)" << endl;}
};int main()
{Derived d;d.func(3.14);   // 合法,調用Derived::func(double)//d.func();     // 錯誤!基類的func()被隱藏d.func(10);     // 錯誤!基類的func(int)被隱藏d.Base::func(); // 合法,顯式調用基類函數return 0;
}

在這里插入圖片描述

3. 關于繼承作用域的相關習題

#include <iostream>
using namespace std;/*-----------------------定義基類:“A類”-----------------------*/
class A
{
public:void fun(){cout << "A::func()" << endl; }
};/*-----------------------定義派生類:“B類”-----------------------*/
class B : public A
{
public:void fun(int i)  {cout << "B::func(int i): " << i << endl;}
};int main()
{B b;b.fun(10);      b.fun();    return 0;
}

問題 1:A 類和 B 類中的兩個func構成什么關系?( )

A. 重載 B. 隱藏 C. 沒關系


問題 2:上面的代碼編譯運行結果是什么?( )

A. 編譯報錯 B. 運行報錯 C. 正常運行


答案B. 隱藏

分析:

我相信一定會有一部分的小伙伴們回選擇A.重載,他們應該是這么想的:A類中的fun函數是:void fun(),B類中的fun函數是:void fun(int i),咦……這兩個函數不是滿足函數的重載的要求嘛,ok這道題就選A選項了。

一對答案,啊,這道題為什么選B. 隱藏 啊!!!


解析:

  • 基類Afun()與派生類Bfun(int)同名且參數列表不同(標準的函數重載的要求),但是這兩個函數位于不同作用域(基類與派生類)

  • 重載要求函數在同一作用域內,而此處屬于繼承體系中的跨作用域同名函數,因此不構成重載

  • 在 C++ 中,派生類的同名函數會隱藏基類的所有同名函數(無論參數是否相同),這種現象稱為隱藏(Name Hiding)


答案A. 編譯報錯

解析:

  • main函數中,b.fun();會觸發編譯錯誤
    • 派生類Bfun(int)隱藏了基類Afun(),編譯器在B的作用域內找不到無參的fun(),因此報錯。
  • 若想調用基類的fun(),需顯式指定類域:b.A::fun();

在這里插入圖片描述

------------------------

五、子類的默認成員函數

在 C++ 中,當定義一個子類(派生類)時,編譯器會自動生成以下默認成員函數(與基類的默認成員函數行為相關)

  • 這些默認函數的生成規則和行為與基類的構造函數、析構函數、賦值運算符等密切相關。

子類會繼承基類的成員變量函數,但 不會繼承基類的構造函數、析構函數和賦值運算符。編譯器會為子類自動生成以下默認成員函數(若未手動定義):

  1. 默認構造函數
  2. 析構函數
  3. 拷貝構造函數
  4. 拷貝賦值運算符重載函數
  5. 移動構造函數(C++11 新增)
  6. 移動賦值運算符重載函數(C++11 新增)

1. 默認構造函數

① 介紹

在 C++ 中,子類的默認構造函數的行為與普通類有所不同,因為它需要正確處理基類子對象成員變量初始化


子類的默認構造函數:當 子類沒有顯式定義任何構造函數時,編譯器會自動生成一個隱式的默認構造函數

子類的默認構造函數的主要職責是:

  1. 調用基類的默認構造函數
  2. 初始化子類的成員變量(調用它們的默認構造函數)

② 使用

情況一:

  • 父類中有默認構造函數
  • 如果子類沒有顯式定義構造函數

編譯器會生成一個默認構造函數。

#include <iostream>
#include <string>
using namespace std;/*---------------------------定義:“基類:Base類”---------------------------*/
class Base
{
public:Base() // 基類有默認構造函數{cout << "Base()\n";}  
};/*---------------------------定義:“派生類:Derived類”---------------------------*/
class Derived : public Base
{
public:int x;              // 內置類型不初始化string s;           // 成員有默認構造函數// 編譯器自動生成:Derived() : Base(), s() {}
};int main()
{Derived d;  // 輸出 Base(),x是未定義值,s為空字符串
}

情況二:

  • 父類中沒有默認構造函數(如:只有帶參構造函數)
  • 如果子類沒有顯式定義構造函數

編譯器會生成一個默認構造函數,但是:子類的默認構造函數會編譯失敗,需手動定義子類構造函數在初始化列表中顯式調用基類構造函數

#include <iostream>
#include <string>
using namespace std;/*---------------------------定義:“基類:Base類”---------------------------*/
class Base
{
public:Base(int val){std::cout << "Base(int)\n";}
};/*---------------------------定義:“派生類:Derived類”---------------------------*/
class Derived : public Base
{
public:Derived() : Base(42) // 必須顯式調用基類構造函數{}  
};int main()
{Derived d;  // 輸出 Base(int)
}

在這里插入圖片描述

在這里插入圖片描述

③ 初始化

子類自身成員的初始化

  • 內置類型成員:不會自動初始化(如:int、指針,其值未定義)
  • 類類型成員:調用其默認構造函數(如:std::string會初始化為空字符串)
#include <iostream>
#include <string>
using namespace std;/*---------------------------定義:“基類:Base類”---------------------------*/
class Base
{
public:Base(int val){cout << "Base(int)\n";}
};/*---------------------------定義:“派生類:Derived類”---------------------------*/
class Derived : public Base
{
public:int x = 10;  // C++11 成員默認值Derived() : Base(42), x(5) // 構造函數初始化列表優先{}
};int main()
{Derived d;  // 輸出 Base(int)
}

④ 禁用

如果希望禁止子類的默認構造,可以:

  • 將基類或子類的默認構造函數聲明為 = delete
  • 將基類構造函數設為 private
/*------------------------禁用案例1:聲明為=delete------------------------*/#include <iostream>
#include <string>
using namespace std;class Base
{
public:Base() = delete;  // 禁用默認構造
};class Derived : public Base
{
public:Derived() // 錯誤:無法調用 Base(){}  
};int main()
{Derived d;  // 輸出 Base(int)
}/*------------------------禁用案例2:修改為private------------------------*/#include <iostream>
#include <string>
using namespace std;class Base
{
private:Base(){}      // 禁用默認構造
};class Derived : public Base
{
public:};int main()
{Derived d;  // 輸出 Base(int)
}

在這里插入圖片描述

2. 拷貝構造函數

① 介紹

在 C++ 中,子類的拷貝構造函數的行為與普通類不同,因為它需要正確處理基類部分派生類新增成員拷貝


子類的默認拷貝構造函數:如果 子類未定義拷貝構造函數,編譯器會生成一個隱式的拷貝構造函數

子類的默認拷貝構造函數的行為是:

  1. 調用基類的拷貝構造函數(拷貝基類部分)
  2. 對派生類的新增成員逐成員拷貝(調用各自的拷貝構造函數)
#include <iostream>
#include <string>
using namespace std;class Base
{
public:Base() {}Base(const Base&){cout << "Base拷貝構造\n";}
};class Derived : public Base
{
public:string s;  // 類類型成員// 編譯器生成://1.子類的默認構造函數//Derived() {}//2.子類的拷貝構造函數//Derived(const Derived& other)//	: Base(other)//	, s(other.s) //{}
};int main()
{Derived d1;Derived d2 = d1;  // 輸出:Base拷貝構造return 0;
}

在這里插入圖片描述

② 使用

注意一:

子類拷貝構造函數的基本形式:子類的拷貝構造函數必須顯式調用基類的拷貝構造函數,否則基類部分將被默認構造而非拷貝構造

#include <iostream>          
using namespace std;        class Base
{
public:Base() // 默認構造函數(無參){cout << "Base默認構造\n";  }Base(const Base& other) // 拷貝構造函數(參數為基類對象的引用){cout << "Base拷貝構造\n";  }
};class Derived : public Base
{
public:Derived() // 子類默認構造函數(無參){cout << "Derived默認構造\n";   //注意:隱式調用基類的默認構造函數Base()}Derived(const Derived& other) // 子類拷貝構造函數(參數為子類對象的引用): Base(other)  {cout << "Derived拷貝構造\n"; //注意:顯式調用基類的拷貝構造函數,傳入子類對象other(向上轉型為Base&)}
};int main()
{Derived d1;       //創建Derived對象d1,觸發以下構造順序:// 1.調用基類Base的默認構造函數 → 輸出"Base默認構造"// 2.調用子類Derived的默認構造函數 → 輸出"Derived默認構造"Derived d2(d1);   //使用d1拷貝構造d2,觸發以下構造順序:// 1.調用基類Base的拷貝構造函數(傳入d1的Base部分)→ 輸出"Base拷貝構造"// 2.調用子類Derived的拷貝構造函數 → 輸出"Derived拷貝構造"return 0;
}

在這里插入圖片描述

注意二:

深拷貝與淺拷貝問題:當派生類包含指針成員時,需手動實現深拷貝

#include <iostream>
using namespace std;class Base
{
public:int* data;//1.實現:“默認構造函數”Base() : data(new int(0))  //注意:初始化data指針并分配內存{}//2.實現:“拷貝構造函數”(實現深拷貝)Base(const Base& other) : data(new int(*other.data)) //注意:為新對象分配獨立內存并復制原對象的值{}//3.實現:“析構函數”~Base(){delete data; //釋放動態分配的內存}
};class Derived : public Base
{
public:int* more_data;//1.實現:“默認構造函數”Derived() : more_data(new int(0)) //注意:初始化基類部分并為more_data分配內存{}//2.實現:“拷貝構造函數”(實現深拷貝)Derived(const Derived& other): Base(other),more_data(new int(*other.more_data)){}//注意事項:// 1.調用基類拷貝構造函數處理基類部分// 2.為more_data分配新內存并復制原對象的值//3.實現:“析構函數”~Derived() //注意:基類析構函數會被自動調用{delete more_data; //釋放派生類部分的動態內存}
};int main()
{/*--------------測試準備階段:創建一個派生類的對象d1--------------*///1.創建第一個派生類對象Derived d1;//2.設置基類部分的值*d1.data = 10;//3.設置派生類擴展部分的值*d1.more_data = 20;/*--------------測試準備階段:使用拷貝構造函數創建第二個派生類的對象d2--------------*/Derived d2(d1); //期望d2是d1的獨立副本/*--------------驗證拷貝結果:輸出d2的值--------------*/cout << "d2.data: " << *d2.data << endl;       cout << "d2.more_data: " << *d2.more_data << endl;  /*--------------驗證拷貝結果:修改原始對象的值--------------*/*d1.data = 100;       //注意:深拷貝保證這不會影響d2*d1.more_data = 200;  //注意:深拷貝保證這不會影響d2/*--------------驗證深拷貝:d2的值應保持不變--------------*/cout << "After modification:" << endl;cout << "d2.data: " << *d2.data << endl;        cout << "d2.more_data: " << *d2.more_data << endl;  // 對象離開作用域時,析構函數會自動釋放內存// 不會發生內存泄漏return 0;
}

在這里插入圖片描述

③ 禁用

必須顯式調用基類拷貝構造的情況:

  • 如果基類的拷貝構造函數不可訪問= deleteprivate
  • 子類無法生成或使用隱式拷貝構造

注意:若基類沒有可訪問的拷貝構造函數刪除私有),子類的默認拷貝構造函數會被隱式刪除

/*------------------------禁用案例1:刪除------------------------*/#include <iostream>
using namespace std;class Base
{
public:Base() //默認的構造函數{}Base(const Base&) = delete;  // 禁止拷貝
};class Derived : public Base
{
public:Derived(){}//Derived(const Derived&) // 錯誤:無法調用基類拷貝構造//{}  
};int main()
{Derived d1;Derived d2(d1);  // 編譯錯誤:嘗試使用已刪除的拷貝構造函數cout << "拷貝構造已被禁用,無法創建對象副本。" << endl;return 0;
}/*------------------------禁用案例2:私有------------------------*/#include <iostream>
using namespace std;class Base
{
public:Base() //默認的構造函數{}
private:Base(const Base&) // 禁止拷貝{}
};class Derived : public Base
{
public:Derived(){}//Derived(const Derived&) // 錯誤:無法調用基類拷貝構造//{}  
};int main()
{Derived d1;Derived d2(d1);  // 編譯錯誤:嘗試使用已刪除的拷貝構造函數cout << "拷貝構造已被禁用,無法創建對象副本。" << endl;return 0;
}

在這里插入圖片描述

在這里插入圖片描述

3. 拷貝賦值運算符重載函數

① 介紹

在 C++ 中,子類的拷貝賦值運算符 的行為比普通類更復雜,因為它需要正確處理基類部分派生類新增成員賦值


子類的默認拷貝賦值運算符重載函數:如果 子類沒有自定義operator=,編譯器會生成一個隱式的拷貝賦值運算符重載函數

子類的拷貝賦值運算符重載函數的行為是:

  1. 調用基類的operator=(賦值基類部分)
  2. 對派生類的新增成員逐成員賦值(調用各自的 operator=)
#include <iostream>  
#include <string>   
using namespace std;/*---------------------定義:“基類:Base類”---------------------*/
class Base
{
public://1.實現:“拷貝賦值運算符”Base& operator=(const Base&) {cout << "Base::operator=\n";  // 打印賦值操作信息return *this;                 // 返回當前對象的引用}
};/*---------------------定義:“派生類:Derived類”---------------------*/
class Derived : public Base
{string s;  // 類類型成員變量//注意:編譯器會自動生成如下拷貝賦值運算符:// Derived& operator=(const Derived& other) // {//     Base::operator=(other);  // 調用基類的拷貝賦值// //     s = other.s;             // 拷貝派生類成員//     return *this;            // 返回當前對象引用// }
};int main()
{//1.創建兩個Derived對象Derived d1, d2;   //2.執行拷貝賦值操作d1 = d2;  //將調用編譯器自動生成的拷貝賦值運算符return 0;  
}

在這里插入圖片描述

② 使用

注意一:

子類拷貝賦值運算符的基本形式:子類的operator= 必須 顯式調用基類的operator=,否則基類部分不會被正確賦值(只會被默認構造,而不會拷貝)

  • 原因:派生類的 operator= 會隱藏基類的 operator=
  • 因此:若要在派生類中顯式調用基類的 operator=,需要通過指定基類作用域來實現
#include <iostream>  
#include <string>  
using namespace std;/*---------------------定義:“基類:Base類”---------------------*/
class Base 
{
public://1.實現:基類的“拷貝賦值運算符”Base& operator=(const Base& other) {cout << "Base::operator=\n";  // 打印調試信息return *this;                 // 返回當前對象的引用}
};/*---------------------定義:“派生類:Derived類”---------------------*/
/*
* 任務:
*   1.這個類展示了派生類中如何正確實現拷貝賦值運算符,
*   2.必須顯式調用基類的拷貝賦值運算符,
*	3.否則基類部分不會被賦值。
*/
class Derived : public Base
{
public:/** 實現要點:*  1. 必須顯式調用基類的operator=*  2. 然后處理派生類特有的成員拷貝*  3. 返回*this以支持鏈式賦值*///1.實現:派生類的“拷貝賦值運算符”Derived& operator=(const Derived& other) {cout << "開始Derived拷貝賦值操作...\n";//1.首先調用基類的拷貝賦值運算符Base::operator=(other);  //注意:必須顯式調用基類賦值//2.然后處理派生類特有的成員賦值//   如果有成員變量需要拷貝,應該在這里處理//   例如:this->member = other.member;cout << "完成Derived特有成員的賦值\n";//3.返回當前對象的引用return *this;  }
};int main()
{//1.創建兩個派生類的對象cout << "創建Derived對象d1和d2...\n";Derived d1, d2;//2.進行派生類的賦值操作cout << "\n執行拷貝賦值操作(d1 = d2)...\n";d1 = d2;   //調用Derived::operator=cout << "\n程序正常結束\n";return 0;  
}

在這里插入圖片描述

注意二:

深拷貝與淺拷貝問題:如果派生類包含 指針或動態資源,必須手動管理深拷貝

#include <iostream>
using namespace std;/*---------------------定義:“基類:Base類”---------------------*/
class Base 
{
public:int* data;//1.實現:“默認構造函數”---> 初始化并分配內存Base() : data(new int(0)) {}//2.實現:“深拷貝賦值運算符”---> 防止內存泄漏和懸掛指針Base& operator=(const Base& other) {//1.防止自賦值(如 a = a)if (this != &other) {  //2.釋放當前資源delete data;  //3.創建新資源并復制值data = new int(*other.data);  }return *this;  // 返回引用以支持鏈式賦值(如:a = b = c)}//3.實現:“析構函數”---> 釋放動態分配的內存~Base() { delete data; }
};/*---------------------定義:“派生類:Derived類”---------------------*/
class Derived : public Base 
{
public:int* more_data;//1.實現:“默認構造函數”---> 初始化基類和派生類資源Derived() : more_data(new int(0)) {}//2.實現:“深拷貝賦值運算符”---> 先處理基類部分,再處理自身Derived& operator=(const Derived& other) {//1.防止自賦值if (this != &other) {  //2.調用基類的賦值運算符處理基類資源Base::operator=(other); //3.釋放當前派生類資源delete more_data;  //4.創建新資源并復制值more_data = new int(*other.more_data);  }return *this;  // 返回引用以支持鏈式賦值}//3.實現:“析構函數”---> 釋放派生類資源(基類析構函數自動調用)~Derived() { delete more_data; }
};int main() 
{//1.創建兩個派生類的對象并進行賦值操作Derived d1, d2;*d1.data = 10;        // 設置基類部分的值*d1.more_data = 20;   // 設置派生類部分的值//2.調用派生類的賦值運算符d2 = d1;  //3.輸出“修改前”派生類對象d2的成員變量的值cout << "修改前 d2.data: " << *d2.data << endl;       cout << "修改前 d2.more_data: " << *d2.more_data << endl;  //4.修改派生類的對象d1,驗證深拷貝:不會影響 d2*d1.data = 100;*d1.more_data = 200;//5.輸出“修改后”派生類對象d2的成員變量的值cout << "修改后 d2.data: " << *d2.data << endl;        // 輸出: 10cout << "修改后 d2.more_data: " << *d2.more_data << endl;  // 輸出: 20return 0;
}

在這里插入圖片描述

③ 禁用

必須顯式調用基類 operator= 的情況

  • 如果基類的 operator= 不可訪問(如: = deleteprivate
  • 子類無法使用隱式拷貝賦值運算符
/*------------------------禁用案例1:刪除------------------------*/#include <iostream>  
using namespace std;  /*---------------------定義:“基類:Base類”---------------------*/
class Base
{
public:Base& operator=(const Base&) = delete;  // C++11特性:顯式刪除拷貝賦值,通過將拷貝賦值運算符聲明為delete來禁止拷貝賦值操作/** 效果:*      1.禁止Base類的拷貝賦值(Base b1; Base b2; b1 = b2; 會編譯失敗)*      2.任何嘗試繼承Base并實現拷貝賦值的派生類也會失敗*/
};/*---------------------定義:“派生類:Derived類”---------------------*/
class Derived : public Base //注意:派生類Derived,繼承自不可拷貝賦值的Base,由于基類禁止拷貝賦值,這個類也無法實現有效的拷貝賦值運算符
{
public://嘗試實現拷貝賦值運算符會導致編譯錯誤:Derived& operator=(const Derived& other) {//無法調用Base::operator=,因為基類的拷貝賦值已被刪除return *this;}
};int main()
{cout << "創建Base對象..." << endl;Base b1;Base b2;// 以下代碼如果取消注釋會導致編譯錯誤://b1 = b2;  // 錯誤:Base::operator=已被刪除cout << "創建Derived對象..." << endl;Derived d1;Derived d2;// 以下代碼如果取消注釋會導致編譯錯誤:d1 = d2;  //注意:無法使用隱式生成的拷貝賦值運算符return 0; 
}/*----------------------------禁用案例1:私有----------------------------*/#include <iostream>  
using namespace std;  /*---------------------定義:“基類:Base類”---------------------*/
class Base
{
private:Base& operator=(const Base&){return *this;}};/*---------------------定義:“派生類:Derived類”---------------------*/
class Derived : public Base 
{
public:Derived& operator=(const Derived& other) {//無法調用Base::operator=,因為基類的拷貝賦值已被私有return *this;}
};int main()
{cout << "創建Base對象..." << endl;Base b1;Base b2;b1 = b2;  cout << "創建Derived對象..." << endl;Derived d1;Derived d2;// 以下代碼如果取消注釋會導致編譯錯誤:d1 = d2;  //注意:無法使用隱式生成的拷貝賦值運算符return 0; 
}

在這里插入圖片描述

在這里插入圖片描述

④ 拷貝構造 vs 拷貝賦值

特性拷貝構造函數拷貝賦值運算符
調用時機創建新對象時(如 Derived d2 = d1;已存在對象賦值時(如 d2 = d1;
基類處理在初始化列表調用基類拷貝構造顯式調用 Base::operator=
資源管理直接構造新資源需先釋放舊資源,再分配新資源
自賦值檢查不需要(因為是新對象)必須檢查(if (this != &other)

4. 析構函數

在 C++ 中,子類的析構函數 的行為與普通析構函數有所不同,因為它涉及繼承關系多態銷毀問題


子類的默認析構函數:如果 子類沒有顯式定義析構函數,編譯器會自動生成一個 隱式的析構函數

① 注意事項

當基類指針刪除派生類對象時,如果基類析構函數 不是 virtual,通過基類指針刪除派生類對象會導致 未定義行為

  • 通常只調用基類析構函數,派生類部分內存泄漏。

注意:基類析構函數必須為 virtual(多態場景)

#include <iostream>  
using namespace std;  class Base
{
public:~Base(){cout << "~Base()\n";}/** 任務:演示非虛析構函數的問題* *   1.這個類展示了一個關鍵問題:當基類析構函數不是虛函數時*   2.通過基類指針刪除派生類對象會導致派生類析構函數不被調用*/
};class Derived : public Base
{
public:~Derived(){cout << "~Derived()\n";}/** 注意事項:**   1.如果通過基類指針刪除對象,且基類析構函數不是虛函數,*   2.這個析構函數將不會被調用,導致派生類特有的資源泄漏。*/
};int main()
{//1.創建派生類對象,但通過基類指針持有Base* ptr = new Derived();//2.刪除對象 delete ptr;  // 僅輸出 ~Base(),Derived 部分泄漏!return 0;
}

在這里插入圖片描述

解決方案:基類虛析構

在這里插入圖片描述

思考與探究:

現在的大家可以試著想一想:為什么通過基類指針刪除派生類對象會導致派生類析構函數不被調用呢?


原因:在多態場景中,析構函數需要構成 重寫(覆蓋),而重寫的條件之一是函數名相同(這一點將在多態章節中詳細講解)

所以:編譯器會對析構函數名進行特殊處理(統一處理為 destructor()),因此若基類析構函數未聲明為 virtual 的情況下,派生類析構函數與基類析構函數會構成 隱藏 關系(而非重寫)

② 析構順序

析構函數的調用順序(與構造函數相反):

  • 先執行 子類的析構函數體
  • 然后按 聲明順序逆序 析構子類的成員變量
  • 最后自動調用 基類的析構函數(從最底層派生類向頂層基類回溯)

:派生類的析構函數在執行完畢后,會自動調用基類的析構函數來清理基類成員。

這是為了確保派生類對象遵循 “先清理派生類成員,再清理基類成員” 的順序。

#include <iostream>  
using namespace std;  class Member
{
public:~Member() //注意:當包含它的類對象被銷毀時自動調用{cout << "~Member()\n";}
};class Base
{
public:virtual ~Base(){cout << "~Base()\n";}
};/** 注意事項:*      1.派生類 Derived,繼承自 Base*      2.包含 Member 成員對象,演示完整析構順序*/
class Derived : public Base
{
public://1.Member的成員對象Member m;  //2.派生類的析構函數~Derived() {cout << "~Derived()\n";}
};int main()
{Derived d;// 對象離開作用域時,自動析構順序:// 1. 調用 ~Derived() 函數體// 2. 析構成員對象 m(調用 ~Member())(成員逆序析構)// 3. 調用基類析構函數 ~Base()return 0;
}

在這里插入圖片描述

子類中常見的四大默認成員函數的大總結:

#include <iostream>
#include <string>
using namespace std;class Person
{
public://1.實現:“構造函數”---> 初始化 _name,默認值為 "peter"Person(const char* name = "peter"): _name(name){cout << "Person()" << endl;}//2.實現:“拷貝構造函數”Person(const Person& p): _name(p._name){cout << "Person(const Person& p)" << endl;}//3.實現:“賦值運算符重載”Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p){_name = p._name;}return *this;}//4.實現:“析構函數”~Person(){cout << "~Person()" << endl;}protected:string _name;  // 姓名
};class Student : public Person
{
public://1.實現:“構造函數”---> 調用基類構造函數初始化 _name,再初始化 _numStudent(const char* name, int num): Person(name), _num(num){cout << "Student()" << endl;}//2.實現:“拷貝構造函數”---> 調用基類拷貝構造函數,再拷貝 _numStudent(const Student& s): Person(s), _num(s._num){cout << "Student(const Student& s)" << endl;}//3.實現:“賦值運算符重載”Student& operator=(const Student& s){cout << "Student& operator=(const Student& s)" << endl;if (this != &s){Person::operator=(s); //注意:基類的賦值運算符被隱藏,需顯式調用基類作用域的賦值運算符_num = s._num;}return *this;}//4.實現:“析構函數”~Student(){cout << "~Student()" << endl;}protected:int _num;  // 學號
};int main()
{/*--------------測試1:子類的拷貝構造函數--------------*/cout << "-------測試1:子類的拷貝構造函數-------" << endl;cout << "創建第一個 Student 對象 s1" << endl;Student s1("jack", 18);cout << "使用拷貝構造創建 s2" << endl;Student s2(s1);/*--------------測試2:子類的拷貝賦值運算符重載函數--------------*/cout << "-------測試2:子類的拷貝賦值運算符重載函數-------" << endl;cout << "創建第三個 Student 對象 s3" << endl;//3. Student s3("rose", 17);cout << "使用拷貝賦值運算符重載函數為對象s1進行賦值" << endl;//4. s1 = s3;return 0;
}

在這里插入圖片描述

在這里插入圖片描述

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

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

相關文章

Ubuntu22.04安裝OBS Studio

OBS官網的最新的雖然支持Ubuntu系統&#xff0c;但是只支持最新的24.2版本的&#xff0c;而我的電腦上的Ubuntu的版本是22.04&#xff0c;所以在網上尋求解決辦法&#xff0c;看到了這一片博客&#xff0c;作為參考來實現ubuntu22.04安裝OBS&#xff0c;這里提示一下&#xff0…

Ansible 基本使用

Ansible 清單 靜態主機清單 主機清單支持多種格式&#xff0c;例如ini、yaml、腳本等。 本次課程使用 ini 格式。 #創建主機清單[lykcontroller ~ 13:36:01]# vim inventory#vim添加controllernode1node2node3node4?#測試連接單個服務器[lykcontroller ~ 14:08:18]$ ansibl…

網絡資源模板--基于Android Studio 實現的九寨溝App

目錄 一、測試環境說明 二、項目簡介 三、項目演示 四、部設計詳情&#xff08;部分) 首頁 購票頁面 五、項目源碼 一、測試環境說明 電腦環境 Windows 11 編寫語言 JAVA 開發軟件 Android Studio (2020) 開發軟件只要大于等于測試版本即可(近幾年官網直接下載也…

系統架構設計師備考之架構設計實踐知識

1.信息系統架構設計理論與實踐1.1.基本概念信息系統架構定義目前關于信息系統架構較為權威的定義有&#xff1a; &#xff08;1&#xff09;信息系統架構是系統的結構&#xff0c;由軟件元素、元素外部可見屬性和元素間關系組成。 &#xff08;2&#xff09;信息系統架構是軟件…

【IgH EtherCAT】如何利用 RTAI 提供的實時任務和調度機制來構建一個高精度、確定性的工業控制應用

SVG圖展示了系統的分層架構&#xff1a;RTAI實時層&#xff1a;包含RT_TASK、信號量和定時器EtherCAT Master層&#xff1a;主站、域、從站配置和PDO映射EtherCAT網絡層&#xff1a;與實際硬件設備&#xff08;EL3162模擬輸入、EL2004數字輸出&#xff09;通信關鍵特點&#xf…

7款熱門智能電視文件管理器橫向評測

7款智能電視文件管理器橫向評測 在智能電視和電視盒子日益普及的今天&#xff0c;一款好用的文件管理器能讓您的數字生活更加便捷。本文為您評測了7款廣受歡迎的TV版文件管理器&#xff0c;助您找到最適合自己的工具。 1. ES文件瀏覽器TV版 ES文件瀏覽器是一款廣受歡迎的多功能…

Python 類元編程(導入時和運行時比較)

導入時和運行時比較 為了正確地做元編程&#xff0c;你必須知道 Python 解釋器什么時候計算各個代碼 塊。Python 程序員會區分“導入時”和“運行時”&#xff0c;不過這兩個術語沒有嚴 格的定義&#xff0c;而且二者之間存在著灰色地帶。在導入時&#xff0c;解釋器會從上到 下…

[git diff] 對比檢查變更 | 提交前復審 | 版本回退

git diff git diff 是 Git 版本控制系統中用于比較文件差異的核心命令&#xff0c;可以顯示工作目錄、暫存區&#xff08;Index&#xff09;和倉庫歷史之間的變化。 通過對比不同版本或狀態的文件內容&#xff0c;幫助開發者理解代碼變更。 比較工作目錄與暫存區 運行以下命令查…

【數據可視化-85】海底撈門店數據分析與可視化:Python + pyecharts打造炫酷暗黑主題大屏

&#x1f9d1; 博主簡介&#xff1a;曾任某智慧城市類企業算法總監&#xff0c;目前在美國市場的物流公司從事高級算法工程師一職&#xff0c;深耕人工智能領域&#xff0c;精通python數據挖掘、可視化、機器學習等&#xff0c;發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN…

物聯網之小白調試網關設備

小伙伴們&#xff0c;你們好呀&#xff01;我是老寇&#xff01;跟我一起學習調試網關設備 相信搞過物聯網的朋友&#xff0c;對網關設備非常熟悉&#xff0c;本人以小白的視角&#xff0c;手把手教你調試網關設備&#xff01; 工作中使用的是Ubuntu操作系統&#xff0c;因此&a…

Node.js特訓專欄-實戰進階:22. Docker容器化部署

?? 歡迎來到 Node.js 實戰專欄!在這里,每一行代碼都是解鎖高性能應用的鑰匙,讓我們一起開啟 Node.js 的奇妙開發之旅! Node.js 特訓專欄主頁 專欄內容規劃詳情 我將從Docker容器化部署的基礎概念入手,介紹Node.js應用容器化的步驟,包括創建Dockerfile、構建鏡像、運行…

eclipse嵌入式編譯速度慢

eclipse 嵌入式 編譯 速度慢 同一個項目&#xff0c;eclipse編譯速度越來越慢&#xff0c;一開始幾秒鐘編譯完&#xff0c;后面要10分鐘。只需要將以下兩個程序卸載重新安裝即可。

編譯Android版本可用的高版本iproute2

背景&#xff1a; Android自帶的iproute2 太老&#xff0c;很多指令格式不支持 直接基于Android源碼&#xff0c;替換源碼下iproute2 代碼編譯新版&#xff0c;報錯太多&#xff0c;于是改用Android NDK工具編譯 環境&#xff1a; android-ndk-r25c-linux.zip 下載鏈接&am…

JavaScript的fetch函數的用法

基本語法fetch函數用于發起網絡請求&#xff0c;返回一個Promise對象。基本語法如下&#xff1a;fetch(url, options).then(response > response.json()).then(data > console.log(data)).catch(error > console.error(Error:, error));GET請求發起一個簡單的GET請求&…

Json和XML文件相互轉化

目錄 一.XML轉Json文件 示例&#xff1a;將XML轉換為JSON 依賴準備 Java代碼示例 代碼詳細講解 二.Json轉XML文件 示例&#xff1a;將JSON轉換為XML 依賴準備 Java代碼示例 代碼詳細講解 關鍵代碼解析 將JSON轉換為XML 寫入文件 示例輸入與輸出 三.具有相同功能的…

Python科學計算與可視化領域工具TVTK、Mayavi、Mlab、Traits(附視頻教程)

概述 TVTK、Mayavi、Mlab 和 Traits 都是 Python 科學計算與可視化領域中緊密相關的工具&#xff0c;它們常被結合使用來處理和展示三維數據。視頻教程&#xff1a;https://pan.quark.cn/s/f73e875225ca 1. TVTK TVTK&#xff08;Traits-based Visualization Toolkit&#xff0…

SQL INSERT INTO SELECT 詳解

SQL INSERT INTO SELECT 詳解 引言 SQL(Structured Query Language)是數據庫操作的基礎語言,廣泛用于各種關系型數據庫管理系統中。在SQL中,INSERT INTO SELECT 是一個強大的功能,它允許用戶從一個表中選取數據,并直接將這些數據插入到另一個表中。本文將詳細講解 SQL …

python速成學習路線

第一部分&#xff1a;核心基礎&#xff08;語法與工具&#xff09; 目標&#xff1a;掌握 Python 的基本語法規則、數據處理方式和開發工具 核心內容&#xff1a; 環境搭建 安裝Python 3.x版本&#xff08;推薦3.10&#xff09;配置開發工具&#xff08;如PyCharm、VS Code或…

自然語言處理的實際應用

在這個信息爆炸的時代&#xff0c;我們每天都在與文字、語音打交道 —— 發送消息、查詢信息、使用智能助手…… 這些看似平常的互動背后&#xff0c;都離不開一項關鍵技術的支撐&#xff1a;自然語言處理&#xff08;NLP&#xff09;。作為人工智能的重要分支&#xff0c;NLP …

Docker實戰:為項目打造即開即用的寶塔LNMP環境

Docker實戰&#xff1a;為項目打造即開即用的寶塔LNMP環境背景一、準備基礎鏡像二、啟動配置容器&#xff08;關鍵步驟&#xff09;三、容器內環境配置&#xff08;逐步執行&#xff09;1. 基礎環境搭建2. 安裝Systemd&#xff08;寶塔依賴&#xff09;3. 安裝寶塔面板&#xf…