C++程序詩篇的靈動賦形:多態

文章目錄

  • 1.什么是多態?
  • 2.多態的語法實現
    • 2.1 虛函數
    • 2.2 多態的構成
    • 2.3 虛函數的重寫
      • 2.3.1 協變
      • 2.3.2 析構函數的重寫
    • 2.4 override 和 final
  • 3.抽象類
  • 4.多態原理
    • 4.1 虛函數表
    • 4.2 多態原理實現
    • 4.3 動態綁定與靜態綁定
  • 5.繼承和多態常見的面試問題
  • 希望讀者們多多三連支持
  • 小編會繼續更新
  • 你們的鼓勵就是我前進的動力!

本篇將開啟 C++ 三大特性中的多態篇章,多態允許你以統一的方式處理不同類型的對象,通過相同的接口來調用不同的實現方法。這意味著你可以編寫通用的代碼,而這些代碼可以在運行時根據對象的實際類型來執行特定的操作

1.什么是多態?

通俗來說,就是多種形態,具體點就是去完成某個行為,當不同的對象去完成時會產生出不同的狀態

??舉個例子:

比如買高鐵票的時候,我們都屬于 Person 類,買的時候會顯示為全價,那么我們又屬于 Student 類,繼承于 Person 類,這時買的時候又會顯示為半價,假設兩個類都有 BuyTicket 函數,那么相同的函數在繼承的基礎上,能夠實現不同的功能,這就是多態

2.多態的語法實現

2.1 虛函數

class Person 
{
public:virtual void BuyTicket() { cout << "買票-全價" << endl;}
};

virtual 修飾的類成員函數稱為虛函數,注意這里和菱形虛擬繼承的 virtual 沒有關系,不過使用了同一個關鍵字而已

🔥值得注意的是:

  • 內聯函數一般不能是虛函數。內聯函數是在編譯時將函數體插入到調用處,而虛函數是在運行時進行動態綁定的,兩者特性沖突
  • 靜態成員不可以是虛函數,虛函數是通過對象的虛函數表指針來實現動態綁定的,也就是在運行時根據對象的實際類型來確定調用哪個虛函數。而靜態成員函數是屬于類的,不依賴于具體對象,沒有對象的概念,也沒有虛函數表指針,無法通過動態綁定來調用
  • 構造函數不可以是虛函數,對象中的虛函數表指針是在構造函數初始化列表階段才初始化的

2.2 多態的構成

虛函數是實現多態的重要組成部分,將上面舉的例子以代碼形式實現如下:

class Person 
{
public:virtual void BuyTicket(){cout << "買票-全價" << endl;}
};class Student : public Person 
{
public:virtual void BuyTicket() { cout << "買票-半價" << endl; }
};

多態是在不同繼承關系的類對象,去調用同一函數,產生了不同的行為,比如 Student 繼承了 PersonPerson 對象買票全價,Student 對象買票半價

那么在繼承中要構成多態還有兩個條件:

  1. 必須通過父類的指針或者引用調用虛函數

  2. 被調用的函數必須是虛函數,且派生類必須對基類的虛函數進行重寫

🔥值得注意的是: 多態構成條件缺一不可,如果多態產生問題,子類沒有對某個方法進行重寫,那么子類對象在調用該方法時,就會沿著繼承鏈向上查找,找到父類中對應的方法并調用

2.3 虛函數的重寫

class Person 
{
public:virtual void BuyTicket() { cout << "買票全價" << endl; }
};class Student : public Person 
{
public:virtual void BuyTicket() { cout << "買票半價" << endl; }
};void Func(Person& people)
{people.BuyTicket();
}int main()
{Person Mike;Func(Mike);Student Johnson;Func(Johnson);return 0;
}

Person 類的 BuyTicketStudent 類的 BuyTicket 構成重寫

虛函數的重寫: 又叫覆蓋,派生類中有一個跟基類完全相同的虛函數(即派生類虛函數與基類虛函數的返回值類型函數名字參數列表完全相同),稱子類的虛函數重寫了基類的虛函數

在這里插入圖片描述

🔥值得注意的是: 在重寫父類虛函數時,子類的虛函數在不加 virtual 關鍵字時,雖然也可以構成重寫(因為繼承后父類的虛函數被繼承下來了在子類依舊保持虛函數屬性),但是該種寫法不是很規范,不建議這樣使用

2.3.1 協變

class A {};
class B : public A {};class Person 
{
public:virtual A* f() { return new A; }
};class Student : public Person 
{
public:virtual B* f() { return new B; }
};

協變是重寫的一種特殊情況,簡單來說協變就是派生類重寫基類虛函數時,與基類虛函數返回值類型不同,且要求父類虛函數類型和子類虛函數類型必須是父子關系的引用和指針

🔥值得注意的是: 必須都是引用或者都是指針,不能一個是引用一個是指針

2.3.2 析構函數的重寫

class Person 
{
public:virtual ~Person() { cout << "~Person()" << endl; }
};class Student : public Person 
{
public:virtual ~Student() { cout << "~Student()" << endl; delete[] ptr;}protected:int* ptr = new int[10];
};int main()
{Person* p = new Person;delete p;p = new Student;delete p;return 0;
}

這里單純講解很難理解,所以以一段代碼場景+一些提問來解析:

🚩析構函數+virtual,是不是虛函數重寫?

是,雖然函數名不相同,看起來違背了重寫的規則,其實不然,這里可以理解為編譯器對析構函數的名稱做了特殊處理,編譯后析構函數的名稱統一處理成 destructor

🚩為什么要處理成統一名字?

因為要讓兩個析構函數構成重寫

🚩為什么要讓他們構成重寫?

假設我們上面的這個代碼沒有加 virtual,運行代碼如下:

在這里插入圖片描述

觀察可以發現子類 Student 部分沒有得到釋放,那么 ptr 指向的空間就會造成內存泄漏

根據 C++ 內存管理學的知識可知

p -> destructor() + operator delete

這里只能調用 p 這個類型的析構函數,但是我們為了實現能夠調用指向空間的析構函數,期望是個多態調用,而不是普通調用,所以必須讓這兩個析構函數構成重寫

🔥值得注意的是:

  • 當使用父類指針指向子類對象,析構該指針時,如果父類的析構函數不是虛函數,那么將按指針本身的類型(即父類)來析構。這可能會導致子類部分的資源沒有被正確釋放,產生內存泄漏等問題

  • 如果父類的析構函數是虛函數,那么會按照指針實際指向的對象類型(即子類)來析構

2.4 override 和 final

🚩final:修飾虛函數,表示該虛函數不能再被重寫

class Car
{
public:virtual void Drive() final {}
};class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒適" << endl; }
};

🔥值得注意的是:

假設有個 A 類和 B 類,不想讓 B 類繼承 A 類,那么可以寫做:class A final,避免 A 類被繼承,這是 C++11 才支持的,在這之前使用的是將 A 的構造函數私有化的方法

🚩override:檢查派生類虛函數是否重寫了基類某個虛函數,如果沒有重寫編譯報錯

class Car 
{
public:virtual void Drive() {}
};class Benz :public Car 
{
public:virtual void Drive() override { cout << "Benz-舒適" << endl; }
};

3.抽象類

class Car
{
public:virtual void Drive() = 0;
};class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒適" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{Car* pBenz = new Benz;pBenz->Drive();//訪問Benz的虛函數Car* pBMW = new BMW;pBMW->Drive();//訪問BMW的虛函數
}

在虛函數的后面寫上 = 0 ,則這個函數為純虛函數,包含純虛函數的類叫做抽象類(也叫接口類

抽象類不能實例化出對象,即只要有純虛函數就不能實例化出對象,派生類繼承后也不能實例化出對象,只有重寫純虛函數,派生類才能實例化出對象。純虛函數規范了派生類必須重寫,另外純虛函數更體現出了接口繼承

🔥值得注意的是:

普通函數的繼承是一種實現繼承,派生類繼承了基類函數,可以使用函數,繼承的是函數的實現。虛函數的繼承是一種接口繼承,派生類繼承的是基類虛函數的接口,目的是為了重寫,達成多態,繼承的是接口。所以如果不實現多態,不要把函數定義成虛函數

4.多態原理

4.1 虛函數表

??以下我們通過多個例子進行詳細解析:

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};int main
{Base b;return 0;
}

sizeof(Base)是多少?

想必大部分人第一次做這道題都會覺得是 1,但運行后發現答案是 8

很奇怪,所以我們轉到調試查看

在這里插入圖片描述

發現除了 _b 以外,還多一個 _vfptr 放在對象的前面(注意有些平臺可能會放到對象的最后面,這個跟平臺有關),對象中的這個指針我們叫做虛函數表指針( v 代表 virtualf 代表 function)

通常虛函數都被放在代碼段_vfptr 就是虛函數的地址,被存放在虛函數表,虛函數表放在只讀數據段,也就是常量區,所以虛函數表本質上是個函數指針數組,虛函數表是在編譯期間生成的

??那么多個虛函數是怎樣實現多態的,舉個例子:

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}

還是轉到監視窗口調試查看:

在這里插入圖片描述

實際上虛函數表是按照一定規則實現的:

  • 🚩復制基類虛表內容
    子類在生成虛表時,首先會把父類虛表中的內容完整地復制一份。這意味著子類虛表初始狀態下包含了基類所有虛函數的地址,保證了子類對象可以調用父類的虛函數,這是因為子類繼承了基類的接口,在某些情況下可能會使用到基類定義的虛函數實現

  • 🚩重寫虛函數的替換
    如果子類對父類中的某個虛函數進行了重寫,那么在子類虛表中,對應父類虛函數的地址會被替換為子類自己重寫后的虛函數地址。當通過父類指針或引用調用該虛函數時,程序會根據對象的實際類型(即子類類型),從子類虛表中找到并重寫后的虛函數來執行,從而實現多態性

  • 🚩新增虛函數的添加
    對于子類自己新定義的虛函數,會按照它們在子類中聲明的先后順序依次添加到子類虛表的末尾。這些新增的虛函數是子類特有的,父類中并不存在。因此,它們會被單獨添加到虛表中,以確保子類對象能夠調用這些專屬的虛函數

🔥值得注意的是:

  • 父類 b 對象和子類 d 對象虛表是不一樣的,這里我們發現 Func1 完成了重寫,所以 d 的虛表中存的是重寫的 Derive::Func1,所以虛函數的重寫也叫作覆蓋,覆蓋就是指虛表中虛函數的覆蓋。重寫是語法的叫法,覆蓋是原理層的叫法
  • Func2 繼承下來后是虛函數,所以放進了虛表,Func3 也繼承下來了,但是不是虛函數,所以不會放進虛表
  • 虛函數表本質是一個存虛函數指針的指針數組,一般情況這個數組最后面放了一個 nullptr
  • 一個類的不同對象共享同一個類的虛表

4.2 多態原理實現

那么回歸到多態的實現條件:

  1. 必須通過父類的指針或者引用調用虛函數

  2. 被調用的函數必須是虛函數,且子類必須對父類的虛函數進行重寫

我們可以提出兩個問題:

🚩為什么不是子類指針或者引用?

class Animal 
{
public:virtual void speak() {cout << "Animal makes a sound" << endl;}
};class Dog : public Animal 
{
public:void speak() override {cout << "Dog barks" << endl;}
};class Cat : public Animal 
{
public:void speak() override {cout << "Cat meows" << endl;}
};int main() {Dog dog;Animal* animalPtr = &dog;  // 父類指針指向子類對象animalPtr->speak();  // 運行時根據實際對象類型調用Dog的speak函數Cat cat;Animal& animalRef = cat;  // 父類引用綁定到子類對象animalRef.speak();  // 運行時根據實際對象類型調用Cat的speak函數return 0;
}

這里的子類 DogCat 都繼承于父類 Animal,就是因為是父類的指針或引用才能想調用哪個子類都行

如果是子類的指針或引用,比如有個 Dog 類的指針 Dog* dogPtr,它只能指向 Dog 類對象,沒辦法指向 Cat 類對象。如果想用它去調用 speak 函數,不管怎樣都是調用 Dog 類的 speak 函數,不能根據實際對象類型(Cat 或其他子類)來動態調用不同的 speak 函數,就實現不了多態了

🚩為什么不能是父類對象?

class Person
{
public:virtual void BuyTicket(){cout << "買票-全價" << endl;}
};class Student : public Person
{
public:virtual void BuyTicket(){cout << "買票-半價" << endl;}
};int main()
{Person ps;Student st;ps = st;return 0;
}

如果是使用對象,而不是指針或引用,子類中特有的成員變量和函數將被截斷,丟失子類的特性

而使用父類指針或引用指向子類對象時,不會發生切片,能夠完整保留子類對象的所有信息,從而可以訪問子類重寫的虛函數以實現多態

🔥值得注意的是: 子類對象賦值給父類對象的時候,不會拷貝虛函數表過去,如果拷貝了,那么父類虛函數表中的虛函數就變成子類虛函數了,就失去多態的意義了

在這里插入圖片描述

所以總結: 滿足多態以后的函數調用,不是在編譯時確定的,是運行起來以后到對象的中去找的。不滿足多態的函數調用時編譯時確認好的

4.3 動態綁定與靜態綁定

靜態綁定: 又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態多態,比如:函數重載

動態綁定: 又稱后期綁定(晚綁定),是在程序運行期間,根據具體拿到的類型確定程序的具體行為,調用具體的函數,也稱為動態多態

5.繼承和多態常見的面試問題

  1. 下面哪種面向對象的方法可以讓你變得富有( )
    A: 繼承
    B: 封裝
    C: 多態
    D: 抽象
  2. ( )是面向對象程序設計語言中的一種機制。這種機制實現了方法的定義與具體的對象無關,
    而對方法的調用則可以關聯于具體的對象。
    A: 繼承
    B: 模板
    C: 對象的自身引用
    D: 動態綁定
  3. 面向對象設計中的繼承和組合,下面說法錯誤的是?()
    A:繼承允許我們覆蓋重寫父類的實現細節,父類的實現對于子類是可見的,是一種靜態復用,也稱為白盒復用
    B:組合的對象不需要關心各自的實現細節,之間的關系是在運行時候才確定的,是一種動態復用,也稱為黑盒復用
    C:優先使用繼承,而不是組合,是面向對象設計的第二原則
    D:繼承可以使子類能自動繼承父類的接口,但在設計模式中認為這是一種破壞了父類的封裝性的表現
  4. 以下關于純虛函數的說法,正確的是( )
    A:聲明純虛函數的類不能實例化對象
    B:聲明純虛函數的類是虛基類
    C:子類必須實現基類的純虛函數
    D:純虛函數必須是空函數
  5. 關于虛函數的描述正確的是( )
    A:派生類的虛函數與基類的虛函數具有不同的參數個數和類型
    B:內聯函數不能是虛函數
    C:派生類必須重新定義基類的虛函數
    D:虛函數可以是一個static型的函數
  6. 關于虛表說法正確的是( )
    A:一個類只能有一張虛表
    B:基類中有虛函數,如果子類中沒有重寫基類的虛函數,此時子類與基類共用同一張虛表
    C:虛表是在運行期間動態生成的
    D:一個類的不同對象共享該類的虛表
  7. 假設A類中有虛函數,B繼承自A,B重寫A中的虛函數,也沒有定義任何虛函數,則( )
    A:A類對象的前4個字節存儲虛表地址,B類對象前4個字節不是虛表地址
    B:A類對象和B類對象前4個字節存儲的都是虛基表的地址
    C:A類對象和B類對象前4個字節存儲的虛表地址相同
    D:A類和B類虛表中虛函數個數相同,但A類和B類使用的不是同一張虛表

參考答案:1. A 2. D 3. C 4. A 5. B 6. D 7. D

  1. 下面程序輸出結果是什么? ()
#include<iostream>
using namespace std;class A 
{
public:A(const char* s){ cout << s << endl; }~A() {}
};class B :virtual public A
{
public:B(const char* s1, const char* s2):A(s1) { cout << s2 << endl; }
};class C :virtual public A
{
public:C(const char* s1, const char* s2):A(s1) { cout << s2 << endl; }
};class D :public B, public C
{
public:D(const char* s1, const char* s2, const char* s3, const char* s4):B(s1, s2), C(s1, s3), A(s1){cout << s4 << endl;}
};int main() 
{D* p = new D("class A", "class B", "class C", "class D");delete p;return 0;
}

A:class A class B class C class D B:class D class B class C class A
C:class D class C class B class A D:class A class C class B class D

解析: 這是個菱形虛擬繼承,所以 A 只會被調用一次,D 類里的初始化列表是按聲明的順序來初始化的,所以按 ABCD 的順序,因此答案選 A

  1. 多繼承中指針偏移問題?下面說法正確的是( )
class Base1 
{ 
public:int _b1; 
};class Base2 
{ 
public:int _b2; 
};class Derive : public Base1, public Base2 
{ 
public: int _d; 
};int main() 
{Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3

解析: 畫圖理解即可,選 C
在這里插入圖片描述

  1. 以下程序輸出結果是什么()
class A
{
public:virtual void func(int val = 1) { cout << "A->" << val << endl; }virtual void test() { func(); }
};class B : public A
{
public:void func(int val = 0) { cout << "B->" << val << endl; }
};int main(int argc, char* argv[])
{B* p = new B;p->test();return 0;
}

A: A->0 B: B->1 C: A->1 D: B->0 E: 編譯出錯 F: 以上都不正確

解析: 這題絕大多數人肯定會選到 D,這題的知識點確實比較偏,首先我們要知道多態重寫的是實現,即只有 {} 內的內容是多態的,實際上子類的函數頭其實相當于是從父類拷貝過來的,因此函數頭的內容還是調用的父類的,所以答案選 B


希望讀者們多多三連支持

小編會繼續更新

你們的鼓勵就是我前進的動力!

請添加圖片描述

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

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

相關文章

算法訓練之動態規劃(三)

???~~~~~~歡迎光臨知星小度博客空間~~~~~~??? ???零星地變得優秀~也能拼湊出星河~??? ???我們一起努力成為更好的自己~??? ???如果這一篇博客對你有幫助~別忘了點贊分享哦~??? ???如果有什么問題可以評論區留言或者私信我哦~??? ?????? 個…

$_GET變量

$_GET 是一個超級全局變量&#xff0c;在 PHP 中用于收集通過 URL 查詢字符串傳遞的參數。它是一個關聯數組&#xff0c;包含了所有通過 HTTP GET 方法發送到當前腳本的變量。 預定義的 $_GET 變量用于收集來自 method"get" 的表單中的值。 從帶有 GET 方法的表單發…

jQuery多庫共存

在現代Web開發中&#xff0c;項目往往需要集成多種JavaScript庫或框架來滿足不同的功能需求。然而&#xff0c;當多個庫同時使用時&#xff0c;可能會出現命名沖突、功能覆蓋等問題。幸運的是&#xff0c;jQuery提供了一些機制來確保其可以與其他庫和諧共存。本文將探討如何實現…

MySQL 中的聚簇索引和非聚簇索引有什么區別?

MySQL 中的聚簇索引和非聚簇索引有什么區別&#xff1f; 1. 從不同存儲引擎去考慮 在MySIAM存儲引擎中&#xff0c;索引和數據是分開存儲的&#xff0c;包括主鍵索引在內的所有索引都是“非聚簇”的&#xff0c;每個索引的葉子節點存儲的是數據記錄的物理地址&#xff08;指針…

Java從入門到“放棄”(精通)之旅——啟航①

&#x1f31f;Java從入門到“放棄 ”精通之旅&#x1f680; 今天我將要帶大家一起探索神奇的Java世界&#xff01;希望能幫助到同樣初學Java的你~ (??????)?? &#x1f525; Java是什么&#xff1f;為什么這么火&#xff1f; Java不僅僅是一門編程語言&#xff0c;更…

三相電為什么沒零線也能通電

要理解三相電為什么沒零線也能通電&#xff0c;就要從發電的原理說起 1、弧形磁鐵中加入電樞&#xff0c;旋轉切割磁感線會產生電流 隨著電樞旋轉的角度變化&#xff0c;電樞垂直切割磁感線 電樞垂直切割磁感線&#xff0c;此時會產生最大電壓 當轉到與磁感線平行時&#xf…

文件上傳做題記錄

1&#xff0c;[SWPUCTF 2021 新生賽]easyupload2.0 直接上傳php 再試一下phtml 用蟻劍連發現連不上 那就只要命令執行了 2&#xff0c;[SWPUCTF 2021 新生賽]easyupload1.0 當然&#xff0c;直接上傳一個php是不行的 phtml也不行&#xff0c;看下是不是前端驗證&#xff0c;…

【Pandas】pandas DataFrame head

Pandas2.2 DataFrame Indexing, iteration 方法描述DataFrame.head([n])用于返回 DataFrame 的前幾行 pandas.DataFrame.head pandas.DataFrame.head 是一個方法&#xff0c;用于返回 DataFrame 的前幾行。這個方法非常有用&#xff0c;特別是在需要快速查看 DataFrame 的前…

日語學習-日語知識點小記-構建基礎-JLPT-N4階段(1):承上啟下,繼續上路

日語學習-日語知識點小記-構建基礎-JLPT-N4階段(1):承上啟下,繼續上路 1、前言(1)情況說明(2)工程師的信仰2、知識點(1)普通形(ふつうけい)と思います(2)辭書形ことができます(3)Vたことがあります。(4)Vた とき & Vる とき3、單詞(1)日語單詞(2…

碼率自適應(ABR)相關論文閱讀簡報

標題&#xff1a;Quality Enhanced Multimedia Content Delivery for Mobile Cloud with Deep Reinforcement Learning 作者&#xff1a;Muhammad Saleem , Yasir Saleem, H. M. Shahzad Asif, and M. Saleem Mian 單位: 巴基斯坦拉合爾54890工程技術大學計算機科學與工程系 …

匯編語言:指令詳解

零、前置知識 1、數據類型修飾符 名稱解釋byte一個字節&#xff0c;8bitword單字&#xff0c;占2個字節&#xff0c;16bitdword雙字&#xff0c;占4個字節&#xff0c;32bitqword四字&#xff0c;占8個字節&#xff0c;64bit 2、關鍵詞解釋 ptr&#xff1a;它代表 pointer&a…

藍橋杯c ++筆記(含算法 貪心+動態規劃+dp+進制轉化+便利等)

藍橋杯 #include <iostream> #include <vector> #include <algorithm> #include <string> using namespace std; //常使用的頭文件動態規劃 小藍在黑板上連續寫下從 11 到 20232023 之間所有的整數&#xff0c;得到了一個數字序列&#xff1a; S12345…

【C++算法】54.鏈表_合并 K 個升序鏈表

文章目錄 題目鏈接&#xff1a;題目描述&#xff1a;解法C 算法代碼&#xff1a; 題目鏈接&#xff1a; 23. 合并 K 個升序鏈表 題目描述&#xff1a; 解法 解法一&#xff1a;暴力解法 每個鏈表的平均長度為n&#xff0c;有k個鏈表&#xff0c;時間復雜度O(nk^2) 合并兩個有序…

Java中的注解技術講解

Java中的注解&#xff08;Annotation&#xff09;是一種在代碼中嵌入元數據的機制&#xff0c;不直接參與業務邏輯&#xff0c;而是為編譯器、開發工具以及運行時提供額外的信息和指導。下面我們將由淺入深地講解Java注解的概念、實現原理、各種應用場景&#xff0c;并通過代碼…

京東與喜茶關系破裂:切斷所有合作 禁止進入辦公場所

快科技4月10日消息&#xff0c;據報道&#xff0c;京東集團近日被曝出內部下發全員禁令&#xff0c;全面封殺喜茶產品進入辦公區域。 據知情人士透露&#xff0c;京東人力行政部門發布的通知明確規定&#xff1a;全國各職場禁止與喜茶品牌開展任何形式的合作&#xff1b;員工不…

+++++背到厭倦。持續更新

Spring IoC 的工作流程: 讀取 BeanDefinition: Spring 容器啟動時&#xff0c;會讀取 Bean 的配置信息 (例如 XML 配置文件、注解或 Java 代碼)&#xff0c;并將這些配置信息轉換為 BeanDefinition 對象。創建 Bean 實例: 根據 BeanDefinition 中的信息&#xff0c;Spring 容器…

如何在Git歷史中抹掉中文信息并翻譯成英文

如何在Git歷史中抹掉中文信息并翻譯成英文 在軟件開發和版本控制領域&#xff0c;維護一個清晰、一致的代碼歷史記錄是至關重要的。然而&#xff0c;有時我們可能會遇到需要修改歷史提交的情況&#xff0c;比如刪除敏感信息或修正錯誤。本文將詳細探討如何在Git歷史中抹掉中文…

21 天 Python 計劃:MySQL中DML與權限管理

文章目錄 前言一、介紹二、MySQL數據操作&#xff1a;DML2.1 插入數據&#xff08;INSERT&#xff09;2.1.1 插入完整數據&#xff08;順序插入&#xff09;2.1.2 指定字段插入數據2.1.3 插入多條記錄2.1.4 插入查詢結果 2.2 更新數據&#xff08;UPDATE&#xff09;2.3 刪除數…

微信小程序 -- 原生封裝table

文章目錄 table.wxmltable.wxss注意 table.js注意 結果數據結構 最近菜鳥做微信小程序的一個查詢功能&#xff0c;需要展示excel里面的數據&#xff0c;但是菜鳥找了一圈&#xff0c;也沒發現什么組件庫有table&#xff0c;畢竟手機端好像確實不太適合做table&#xff01; 菜鳥…

LangChain-輸出解析器 (Output Parsers)

輸出解析器是LangChain的重要組件&#xff0c;用于將語言模型的原始文本輸出轉換為結構化數據。本文檔詳細介紹了輸出解析器的類型、功能和最佳實踐。 概述 語言模型通常輸出自然語言文本&#xff0c;但在應用開發中&#xff0c;我們經常需要將這些文本轉換為結構化的數據格式…