十五、多態與虛函數

十五、多態與虛函數

15.1 引言

  • 面向對象編程的基本特征:數據抽象(封裝)、繼承、多態
  • 基于對象:我們創建類和對象,并向這些對象發送消息
  • 多態(Polymorphism):指的是相同的接口、不同的實現。簡單來說,多態允許不同的類對象同一個函數調用 做出 不同的響應
  • 動態多態性(Dynamical polymorphism) 是通過 虛函數 實現的。虛函數是邁向真正 面向對象編程(OOP) 的關鍵一步

多態的分類

  1. 編譯時多態(靜態多態/Static Polymorphism)

    • 特點:在編譯階段就能確定調用哪個函數

    • 實現方式:

      • 函數重載(Function Overloading)
      • 運算符重載(Operator Overloading)
    • 示例:

      #include <iostream>
      using namespace std;
      void print(int x){cout << x << endl;}
      void print(double x){cout << x << endl;}int main(){print(5);	//在編譯時就知道調用int版本print(9.8);//在編譯時就知道調用double版本
      }
      
  2. 運行時多態(動態多態/Dynamical polymorphism)

    • 特點:在程序運行時決定調用哪個函數,是程序運行起來后根據實際對象決定的。

    • 實現方式:虛函數(virtual function) + 基類指針或引用

    • 示例:

      #include <iostream>
      using namespace std;
      class Animal {
      public:virtual void speak() { cout << "Animal sound" << endl; }
      };
      class Dog :public Animal {
      public:void speak() override { cout << "Woof!" << endl; }
      };void makeSound(Animal* a) {a->speak();	//在運行時才知道是哪個版本
      }
      int main() {Animal a;Dog d;makeSound(&a);//輸出"Animal sound"makeSound(&d);//輸出 "Woof!",調用的是子類的函數
      }
      

15.2 向上轉型(Upcasting)

  • 當通過指針或引用(指向或引用基類)操作時,派生類的對象可以被當作其基類對象 來處理。
  • 向上轉型(Upcasting): 獲取一個對象的地址(無論是指針 還是 引用),并將其當作 基類類型 使用,就叫做向上轉型(Upcasting)
  • 也就是說,” 新類是現有類的一種類型 “。

示例

class Instrument {
public:void play() const {}
};
//Wind是Instrument的派生類
class Wind :public Instrument {};
void tune(Instrument& i) { i.play(); }
void main() {Wind flute;tune(flute);	//向上轉型(Upcasting)Instrument* p = &flute;//UpcastingInstrument& l = flute;//Upcasting
}

這里將一個 Wind 類型的引用或指針 轉換為 一個 Instument 類型的引用或指針的行為,就是向上轉型(Upcasting)

下面給出一個有疑問的示例

#include <iostream>
using namespace std;
class Instrument {
public:void play() { cout << "Instrument::play" << endl; }
};
class Wind:public Instrument {
public://重新定義接口函數void play() const { cout << "Wind::play" << endl; }
};void tune(Instrument& i) { i.play(); }void main() {Wind flute;tune(flute);//向上轉型
}

輸出

Instrument::play

問題

  • 此調用本應產生 Wind::play ,但實際調用了 Instrument::play
  • 為了解決這個問題,我們需要使用 虛函數(virtual function) 來解決這個問題。

15.3 虛函數(virtual functions)

什么是虛函數

  • 格式:

    virtual type function-name(arguments);

  • 如果一個函數在其基類中被聲明為virtual , 那么它在所有派生類中也是 virtual 的。

  • 在派生類中重新定義一個 virtual 函數,通常稱之為 重寫(Overriding)

  • 多態(Polymorphism):

    同名但不同實現的函數。虛函數(通過重寫)動態決定 調用哪一個函數的。

  • 函數重載(Function overloading):

    靜態決定調用哪個版本的函數。

示例 C15:Instrument2.cpp

//C15:Instrument2.cpp
#include <iostream>
using namespace std;class Instrument{
public:virtual void play() const{cout << "Instrument::play" << endl;}
};
class Wind:public Instrument{
public://重寫虛函數virtual void play() const	//省略"virtual"是可以的{cout << "Wind::play" << endl;}
};void tune(Instrument& i){	i.play();}void main(){Wind flute;tune(flute);//向上轉型
}

輸出

Wind::play

這樣就達到我們的期望了——Wind::play

可擴展性

  • 如果在基類中將 play() 定義為 虛函數(virtual) ,那么我們可以在不修改 tune() 函數的前提下,添加任意多的新類型。
  • 在一個設計良好的面向對象程序中,我們的大多數甚至全部函數,都會遵循 tune() 的模式,并只通過基類接口進行通信。這樣的程序是可擴展的,因為我們可以通過從共同的基類繼承新的數據類型 來添加新功能。

示例 Extensibility in OOP

//Extensibility in OOP
#include <iostream>
#include <string>
using namespace std;
class Instrument {
public:virtual void play() const { cout << "Instrument::play" << endl; }virtual string what() const { return "Instrument"; }//下面這個函數會修改對象virtual void adjust(int) {}
};class Wind :public Instrument {
public:void play() const { cout << "Wind::play" << endl; }string what() const { return "Wind"; }void adjust(int) {}
};class Stringed :public Instrument {
public:void play() const { cout << "Stringed::play" << endl; }string what() const { return "Stringed"; }
};class Brass :public Wind {
public:void play() const { cout << "Brass::play" << endl; }string what() const { return "Brass"; }
};void tune(Instrument& i) { i.play(); }void f(Instrument& i) { i.adjust(1); }int main() {Wind flute;Stringed violin;Brass horn;tune(flute);//Wind::play;tune(violin);//Stringed::play;tune(horn);//Brass::playf(horn);//Wind::adjustreturn 0;
}
  • 我們可以看到,virtual(虛函數) 機制 無論有多少層繼承都能正常運行。
  • adjust() 函數在 Brass 類中沒有別重寫。當這種情況發生時,繼承層次結構中 ”最近的“‘定義會被自動使用(即:Wind::adjust)。

注意

  1. 虛函數 是一個 非靜態成員函數

  2. 如果一個虛函數是在類體外定義的,那么關鍵字 virtual 只在聲明時需要寫明。

    class Instrument{
    public:virtual void play() const;
    };
    void Instrument::play() const {cout << "Instrument::play" endl;
    }
    
  3. 當使用 **作用域解析運算符:: ** 時,虛函數機制將不會被使用

    ………………
    void tune(Instrumnet& i){//……i.Instrument::play();	//顯示調用基類版本,禁用虛機制
    }void main(){Wind flute;tune(flute);
    }
    

    輸出

    Instrument::play
    
  4. 在派生類中,如果要重寫基類的虛函數,那么要重寫的函數的類型必須與基類中虛函數的類型完全相同,這樣才稱為 重寫(Overriding) 基類版本的虛函數。

  5. 如果類型不相同,那么這叫做重定義,是在派生類里面重定義了一個全新的函數,就不會重寫基類的虛函數,而是會名字隱藏基類的虛函數。

  6. 要實現多態行為:

    • 派生類必須是 公有繼承(public) 自基類
    • 被調用的成員函數必須是虛函數
    • 必須通過指針或引用來操作對象。(如果是直接操作對象而不是通過指針或引用,編譯器在編譯時就已知對象的確切類型,因此不需要運行時多態機制)

示例

#include <iostream>
using namespace std;
class A{
public:virtual void f1(){cout << "A::f1" << endl;}virtual void f2(){cout << "A::f2" << endl;}void f3() {cout << "A::f3" << endl;}void f4() {cout << "A::f4" << endl;}
};class B:public A
{
public:virtual void f1()	//虛函數的重寫{cout << "B::f1" << endl;}virtual void f2(int)	//新定義了一個虛函數{cout << "B::f2" << endl;}virtual void f3()	//在B中,f3是虛函數,但在A,f3并不是{cout << "B::f3" << endl;}void f4() 		//重定義{cout << "B::f4" << endl;}
};

下面是針對上面示例的不同main() 函數測試

main1

void main(){B b;A* p = &b;p->f1();p->f2();p->f3();p->f4();
}

輸出

B::f1
A::f2
A::f3
A::f4

main2

void main(){B b;A& a1 = b;a1.f1();a1.f2();a1.f3();a1.f4();
}

輸出

B::f1
A::f2
A::f3
A::f4

main3

void main(){B b;A a = b;a.f1();a.f2();a.f3();a.f4();
}

輸出

A::f1
A::f2
A::f3
A::f4

15.4 C++ 如何實現晚綁定(late binding)

綁定: 在C++中,”綁定“”就是指函數調用與實際執行代碼之間建立聯系的過程。

綁定有兩種:

類型綁定時間說明
早綁定(Early Binding)編譯時決定編譯器在編譯時就知道要調用哪個函數。適用于普通函數、非虛函數等。效率高,但不靈活
晚綁定(Late Binding)運行時決定編譯時不確定,運行時根據對象的實際類型決定調用哪個函數。適用于虛函數。靈活,支持多態

C++晚綁定的機制

  • 編譯器為每個包含 虛函數 的類創建一個 虛函數表(VTABLE)
    • 編譯器會將類的所有虛函數地址存放到它對應的 虛函數表 中。
  • 在每個包含虛函數的類中,編譯器會 偷偷地添加一個 VPTR 指針 ,它指向該對象所屬類的 VTABLE。
  • 為每個類設置 VTABLE ,初始化 VPTR ,插入虛函數調用代碼——這些操作都會自動完成。
  • 當我們用基類指針(或引用)指向派生類對象時, 基類中的 虛指針(vptr)會被設置為指向派生類的虛函數表(vtable)
    以便實現動態多態(運行時綁定)
//C15:Early & Late Binding.cpp
#include <iostream>
#include <string>
using namespace std;
class Pet{
public:virtual string speak() const{return "Pet::speak";}
};class Dog:public Pet{
public:virtual string speak() const {return "Dog::speak";}
};void main(){Dog ralph;Pet* p1 = &ralph;	//有類型歧義Pet& p2 = ralph;	//有類型歧義Pet p3 = ralph;		//無類型歧義//晚綁定cout << p1->speak() << endl;cout << p2.speak() << endl;//早綁定cout << p3.speak() << endl;
}

輸出

Dog::speak
Dog::speak
Pet::speak

15.5 為什么使用虛函數(virtual functions)

  • 虛函數是一種選擇(并不是強制的)。
  • virtual 關鍵字的設計,是為了方便效率優化。當我們想提升代碼執行速度時,只需要查找哪些函數可以改為非虛函數即可。

15.6 抽象基類與純虛函數

  • 有時候我們希望基類僅僅作為接口供其派生類使用,而不希望任何人實際創建這個基類的對象

  • 一個 抽象類(abstract class) 至少包含一個 純虛函數(pure virtual function)。并且抽象類不可以拿來創建對象。

  • 純虛函數: 使用 virtual 關鍵字,并且以 = 0 結尾。

  • 純抽象類(pure abstract class): 是指其中只包含純虛函數,沒有其他函數實現。也不可以拿來創建對象。

  • 不能創建抽象類的對象。

  • 將一個類設計為抽象類,可以確保在向上轉型時只能通過指針或引用來使用這個類

  • 當一個抽象類別繼承時,所有純虛函數都必須在子列中實現(也就要定義),否則這個子類也會變成為一個抽象類。

#include <iostream>
using namespace std;
class Point	//抽象類(不是純抽象類)
{
public:Point(int i = 0,int j = 0) { x0 = i; y0 = j; }virtual void Set() = 0;virtual void Draw() = 0;
protected:int x0, y0;
};class Line :public Point
{
public:Line(int i = 0, int j = 0, int m = 0, int n = 0) :Point(i, j){x1 = m; y1 = n;}virtual void Set(){cout << "Line::Set() called." << endl;}virtual void Draw(){cout << "Line::Draw()." << endl;}
protected:int x1, y1;
};
//抽象類
class Ellipse :public Point
{
public:Ellipse(int i = 0, int j = 0, int p = 0, int q = 0) :Point(i, j){x2 = p; y2 = q;}
protected:int x2, y2;
};void main() {Line line(0, 1);//Elipse elipse(0,1,2,3);//錯誤,因為Wllipse是抽象類Point& p = line;p.Set();p.Draw();
}

輸出

Line::Set() called.
Line::Draw().

15.7 繼承與虛函數表(VTABLE)

虛函數表

  • 編譯器會為派生類自動創建一個新的 虛函數表(VTABLE) ,并將你新重寫的函數地址插入其中。對于那些沒有重寫的虛函數 ,則使用基類中的函數地址
//C15:AddingVirtuals.cpp
#include <iostream>
#include <string>
using namespace std;
class Pet {string pname;
public:Pet(const string& petName) :pname(petName) {}virtual string name() const { return pname; }virtual string speak() const { return ""; }
};class Dog :public Pet {string name;
public:Dog(const string& petName) :Pet(petName) {}virtual string sit() const { return Pet::name() + "sits"; }	//新的虛函數string speak () const { return Pet::name() + " says 'Bark!'"; }//重寫虛函數
};
int main() {Pet* p[] = { new Pet("generic"),new Dog("bob") };//創建一個Pet*數組cout << "p[0]->speak() = " << p[0]->speak() << endl;cout << "p[1]->speak() = " << p[1]->speak() << endl;//!cout << "p[1]->sit() = " << p[1]->sit() << endl;//非法,因為Pet型指針的Dog,會在Pet的虛函數表里面找sit(),但這是找不到的delete p[0];delete p[1];return 0;
}

輸出

p[0]->speak() =
p[1]->speak() = bob says 'Bark!'
Pet vtable
&Pet::name
&Pet::speak
Dog vtable
&Pet::name
&Dog::speak
&Dog::sit

對象切片(Object slicing)

  • 通過地址進行向上轉型是自動且安全的,但通過值進行向上轉型是不安全的。(向下轉型同樣不安全)
  • 對象切片: 對象切片會在復制對象到新對象時丟失原有對象的一部分信息(即派生類特有的部分會被“切掉”)。
  • 如果你將對象向上轉型為另一個對象 (而不是指針或引用),就會發生對象切片
//C15:ObjectSlicing.cpp
#include <iostream>
#include <string>
using namespace std;class Pet{string pname;
public:Pet(const string& name):pname(name){}virtual string name() const{return pname;}virtual string description() const{return "This is " + pname;}
};class Dog:public Pet{string favoriteActivity;
public:Dog(const string& name,const string& activity):Pet(name),favoriteActivity(activity){}virtual string description() const{return Pet::name() + "likes to " + favoriteActivity;}
};void describe(Pet a)	//對象切片
{cout << a.description() << endl;
}
void main(){Pet p("Zhang");Dog d("Li","sleep");describe(p);describe(d);//Pet::pname::pname,name(),description()
}

輸出

This is Zhang
This is Li

這里就要對This is Li 這段文字產生提出問題,我們并不希望這樣,因此需要將desctibe()函數進行修改

void describe(Pet& a)
{cout << a.description() << endl;
}

輸出

This is Zhang
Lilikes to sleep

15.8 重載和重寫(Overloading & Overriding)

  • 在派生類中,如果我們重寫或者重新定義了基類中某個重載的成員函數,那么基類里其他重載版本將會被隱藏。
  • 編譯器不允許我們通過更改基類虛函數的返回類型來**“重新定義”**該函數。
//C15:NameHiding2.cpp
#include <iostream>
#include <string>
using namespace std;
class Base {
public:virtual int f() const { cout << "Base::f()" << endl; return 1; }virtual void f(string) const {}virtual void g() const {}
};class Derived1 :public Base
{
public:void g() const {}
};class Derived2 :public Base {
public://重寫int f() const { cout << "Derived2::f()" << endl; return 2; }
};class Derived3 :public Base {
public://不被允許,因為它在通過改變return類型來重新定義基類的虛函數f()//!void f() const { cout << "Derived3::f()" << endl; }
};class Derived4 :public Base {
public://重新定義,因為改變了參數列表int f(int) const { cout << "Derived4::f()" << endl; return 4; }
};int main() {string s("Hello");Derived1 d1;int x = d1.f();	//調用Base的int f()d1.f(s);	//調用Base的void f(string)Derived2 d2;x = d2.f();	//調用Derived2的int f()//!d2.f(s);//Derived2的int f()將其他版本隱藏了Derived3 d3;d3.f();//調用Base的int f()Derived4 d4;x = d4.f(1);//!x = d4.f();//Base的f()版本都被隱藏//!d4.f(s);//void f(string)被隱藏Base& br = d4;//向上轉型//!!br.f(1);//因為轉型到了Base,所以派生類的非虛函數不能在使用br.f();//可以使用Base版本br.f(s);
}

輸出

Base::f()
Derived2::f()
Base::f()
Derived4::f()
Base::f()

15.9 虛函數和構造函數

  • 當一個包含虛函數的對象被創建時,它的虛函數指針(VPTR)必須被初始化為指向正確的虛函數表(VTABLE)。

  • 構造函數負責初始化這個虛函數指針(VPTR)。

  • 構造函數不能是虛函數。

    原因:構造函數負責設置 VPTR,但虛函數調用又依賴 VPTR,所以構造函數不能是 虛函數,否則邏輯自相矛盾。如果構造函數是虛函數,那調用構造函數需要去使用 VPTR,那使用VPTR又需要去調用構造函數,就像“先有雞還是先有蛋”一樣,會是悖論。

15.10 虛函數和析構函數

  • 析構函數可以是虛函數,而且通常必須是虛函數如果要通過“基類或指針”來刪除派生類對象)。
  • 如果基類中的析構函數被聲明為 virtual ,那么即使派生類的析構函數沒有加 virtual 關鍵字,它們也依然是 virtual 的。
  • 這是為了確保析構函數能夠被準確地調用

下面給出原因

示例

#include <iostream>
using namespace std;class A {
public:A() { cout << "A::A()" << endl; }~A() { cout << "A::~A()" << endl; }
};class B :public A
{
public:B(int i) {buf = new char[i];cout << "B::B(int)" << endl;}~B() {delete[]buf;cout << "B::~B() called." << endl;}
private:char* buf;
};void main() {A* a = new B(15);delete a;
}

輸出

A::A()
B::B(int)
A::~A()

**問題:**我們通過基類指針刪除派生類對象,但 A::~A() 不是虛函數。

所以編譯器在執行 delete p; 時:

  • 只知道 a 是個 A* 類型
  • 查的是 A 的析構函數 --> ~A() ,不是虛函數
  • 所以它不會進行“虛調用”跳轉到 ~B()
  • 最終只調用 A::~A() ,而 B::~B() 根本沒被調用

后果: 導致buf的內存沒有被釋放——內存泄漏。

那么為什么呢?原因就主要在于它不會進行“虛調轉”跳轉到~B()

虛調用 的跳轉

  • 虛函數的跳轉:當通過基類指針或引用調用虛函數時,程序會根據對象的真實類型,通過虛函數表(vtable)**跳轉到**最派生類的函數版本。

  • 正向跳轉:調用一個虛函數時,跳轉到最派生類重寫的版本來執行。

    特點:只跳一次,執行最底層的版本,不會執行上層版本

    示例

    class A {
    public:virtual void f() { cout << "A::f" << endl; }
    };class B : public A {
    public:void f() override { cout << "B::f" << endl; }
    };class C : public B {
    public:void f() override { cout << "C::f" << endl; }
    };

    當我們寫:

    A* p = new C();
    p->f();  // 會執行誰?

    這里會執行的是 C::f() ,不是A 的也不是 B 的而是C的,這就是因為虛函數的跳轉。

  • 反向跳轉:析構對象時,虛函數表引導從最派生類開始,逐級調用所有析構函數(派生 → 基類)。

    特點:逐級調用每一層的析構函數,不能省略

    示例

    #include <iostream>
    using namespace std;
    class A {
    public:virtual void f() { cout << "A::f" << endl; }virtual ~A() { cout << "~A()" << endl; }
    };class B : public A {
    public:void f() override { cout << "B::f" << endl; }~B() { cout << "~B()" << endl; }
    };class C : public B {
    public:void f() override { cout << "C::f" << endl; }~C() { cout << "~C()" << endl; }
    };
    void main() {A* a = new C();delete a;
    }
    

    輸出

    ~C()
    ~B()
    ~A()

所以我們如果要解決上述的那個問題:就需要將 A 的析構函數改成虛函數

#include <iostream>
using namespace std;class A {
public:A() { cout << "A::A()" << endl; }virtual ~A() { cout << "A::~A()" << endl; }
};class B :public A
{
public:B(int i) {buf = new char[i];cout << "B::B(int)" << endl;}~B() {delete[]buf;cout << "B::~B() called." << endl;}
private:char* buf;
};void main() {A* a = new B(15);delete a;
}

輸出

A::A()
B::B(int)
B::~B() called.
A::~A()

注意:

  • 最好避免在構造函數和析構函數中調用虛函數。
  • 在構造函數或析構函數中調用虛函數時,不會啟用運行時多態性。
#include <iostream>
using namespace std;
class Base{
public:Base(){cout << "Bse constructor start" << endl;call();//調用虛函數cout << "Bse constructor end" << endl;}virtual void call(){cout << "Base::call()" << endl;}virtual ~Base(){cout << "Base destructor start" << endl;call();//析構再次調用虛函數cout << "Base destructor end" << endl;}
};class Derived : public Base {
public:Derived() {cout << "Derived constructor\n";}void call() override {cout << "Derived::call()\n";}~Derived() {cout << "Derived destructor\n";}
};
int main() {Derived d;return 0;
}

輸出

Base constructor start
Base::call()
Base constructor end
Derived constructor
Derived destructor
Base destructor start
Base::call()
Base destructor end

說明:

  • 即使 Derived 中重寫了 call() 函數,構造函數和析構函數中調用的都是Base::call()
  • 這是因為:
    • 在構造 Base 的時候, Derived 還沒構造完,因此不會使用它的虛函數表。
    • 在析構 Base 時,Derived 已經開始析構,也不會再用它的虛函數表。

15.11 運算符重載

  • 我們可以像其他成員函數一樣,把運算符函數聲明為虛函數。

15.12 向下轉型

  • 向下轉型是不安全的
  • dynamic_cast :會在運行時檢查類型安全(前提是基類中至少有一個虛函數),如果轉換失敗會返回 nullptr(指針)或拋出異常(引用),相對更安全。
  • static_cast:不做運行時檢查,速度快但風險大,只應在你確信類型匹配時使用。

15.13 總結

  • 虛數調用的綁定方式:早期綁定、晚期綁定
  • 虛函數、多態
  • 向上轉型(Upcasting)
  • 重寫(Overriding)、重載(Overloading)
  • 純虛函數、抽象類、純抽象類

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

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

相關文章

點云特征提取的兩大經典范式:Voxel-based 與 Pillar-based

點云特征提取的兩大經典范式&#xff1a;Voxel-based 與 Pillar-based 在點云處理領域&#xff0c;尤其是針對 3D 目標檢測任務&#xff0c;特征提取是核心環節之一。目前&#xff0c;Voxel-based&#xff08;體素化&#xff09;和 Pillar-based&#xff08;柱狀化&#xff09…

前蘋果首席設計官回顧了其在蘋果的設計生涯、公司文化、標志性產品的背后故事

每周跟蹤AI熱點新聞動向和震撼發展 想要探索生成式人工智能的前沿進展嗎&#xff1f;訂閱我們的簡報&#xff0c;深入解析最新的技術突破、實際應用案例和未來的趨勢。與全球數同行一同&#xff0c;從行業內部的深度分析和實用指南中受益。不要錯過這個機會&#xff0c;成為AI領…

web 自動化之 selenium 元素四大操作三大切換等待

文章目錄 一、元素的四大操作二、三大切換&等待1、切換窗口:當定位的元素不在當前窗口&#xff0c;則需要切換窗口2、切換iframe&#xff1a;當定位的元素在frame/iframe&#xff0c;則需要切換3、切換彈出窗口 一、元素的四大操作 1、輸入 2、點擊 3、獲取文本 4、獲取屬…

window server 2012安裝sql server2008 r2

執行sql server2008 r2安裝目錄下的setup 選擇運行程序而不獲取幫助 然后就是讓人絕望的 只能先搞這個了&#xff0c;F*微軟&#xff0c;自家軟件不讓正常安裝 打開服務器管理器->添加角色和功能->選擇Web 服務&#xff08;IIS&#xff09;->添加.NET Framework3.5 然…

【K8S學習之生命周期鉤子】詳細了解 postStart 和 preStop 生命周期鉤子

0. 參考 Kubernetes容器生命周期 —— 鉤子函數詳解&#xff08;postStart、preStop&#xff09; - 人艱不拆_zmc - 博客園詳解Kubernetes Pod優雅退出 - 人艱不拆_zmc - 博客園 1. Kubernetes 生命周期鉤子概述 在 Kubernetes 中&#xff0c;生命周期鉤子&#xff08;Lifec…

測試文章標題01

模型上下文協議&#xff08;Model Context Protocol, MCP&#xff09;深度解析 一、MCP的核心概念 模型上下文協議&#xff08;Model Context Protocol, MCP&#xff09;是一種用于規范機器學習模型與外部環境交互的標準化框架。其核心目標是通過定義統一的接口和數據格式&am…

kubuntu系統詳解

Kubuntu 系統深度解析&#xff08;從系統架構到用戶體驗&#xff09; 一、定位與核心特性 Kubuntu 是 Ubuntu 的官方 KDE 衍生版&#xff0c;基于 Ubuntu 的穩定底層&#xff08;Debian 技術棧&#xff09;&#xff0c;搭載 KDE Plasma 桌面環境&#xff0c;主打 “功能豐富、…

cURL:通過URL傳輸數據的命令行工具庫介紹

文章目錄 1. 什么是 curl&#xff1f;2. 下載與安裝 curl3. curl 的常見用法3.1 獲取網頁內容3.2 下載文件3.3 發送 POST 請求&#xff08;帶表單數據&#xff09;3.4 發送帶 JSON 的 POST 請求 1. 什么是 curl&#xff1f; cURL&#xff08;CommandLine URL&#xff09;是非常…

從零搭建AI工作站:Gemma3大模型本地部署+WebUI配置全套方案

文章目錄 前言1. 安裝Ollama2.Gemma3模型安裝與運行3. 安裝Open WebUI圖形化界面3.1 Open WebUI安裝運行3.2 添加模型3.3 多模態測試 4. 安裝內網穿透工具5. 配置固定公網地址總結 前言 如今各家的AI大模型廝殺得如火如荼&#xff0c;每天都有新的突破。今天我要給大家安利一款…

Element Plus對話框(ElDialog)全面指南:打造靈活彈窗交互

&#x1f4cc; 開篇導語 對話框是Web應用中實現用戶交互的核心組件之一&#xff0c;常用于信息確認、表單提交或詳情展示。Element Plus的ElDialog組件以高擴展性和優雅動效著稱&#xff0c;支持高度定制化開發。本文將從基礎配置到進階技巧&#xff0c;手把手教你掌握對話框組…

解決WSL、Ubuntu的.ico圖標不正確顯示縮略圖

解決WSL、Ubuntu的.ico圖標不正確顯示縮略圖 問題描述 Win10系統中由于更新了某些軟件&#xff0c;篡改了默認的圖像顯示軟件&#xff0c;導致WSL等軟件未能成功顯示圖標&#xff0c;表現如下&#xff1a; 解決方法 將ico文件的默認打開方式更改為“畫圖”&#xff0c;如下…

[數據結構高階]并查集初識、手撕、可以解決哪類問題?

標題&#xff1a;[數據結構高階]并查集初識、手撕、可以解決哪類問題&#xff1f; 水墨不寫bug 文章目錄 一、認識并查集二、模擬實現并查集三、用并查集解決問題1、[省份的數量](https://leetcode.cn/problems/number-of-provinces/)2、[等式方程的可滿足性](https://leetcode…

如何快速入門大模型?

學習大模型的流程是什么 &#xff1f; 提示詞工程&#xff1a;只需掌握提問技巧即可使用大模型&#xff0c;通過優化提問方式獲得更精準的模型輸出套殼應用開發&#xff1a;在大模型生態上開發業務層產品&#xff08;如AI主播、AI小助手等&#xff09;&#xff0c;只需調用API…

《AI大模型應知應會100篇》第59篇:Flowise:無代碼搭建大模型應用

第59篇&#xff1a;Flowise&#xff1a;無代碼搭建大模型應用 摘要&#xff1a;本文將詳細探討 Flowise 無代碼平臺的核心特性、使用方法和最佳實踐&#xff0c;提供從安裝到部署的全流程指南&#xff0c;幫助開發者和非技術用戶快速構建復雜的大模型應用。文章結合實戰案例與配…

python打卡day23@浙大疏錦行

知識回顧: 1. 轉化器和估計器的概念 2. 管道工程 3. ColumnTransformer和Pipeline類 作業&#xff1a; 整理下全部邏輯的先后順序&#xff0c;看看能不能制作出適合所有機器學習的通用pipeline 一、導入數據庫 import pandas as pd import numpy as np import matplo…

Vue.js框架的優缺點

別再讓才華被埋沒&#xff0c;別再讓github 項目蒙塵&#xff01;github star 請點擊 GitHub 在線專業服務直通車GitHub賦能精靈 - 艾米莉&#xff0c;立即加入這場席卷全球開發者的星光革命&#xff01;若你有快速提升github Star github 加星數的需求&#xff0c;訪問taimili…

交易流水表的分庫分表設計

交易流水表的分庫分表設計需要結合業務特點、數據增長趨勢和查詢模式&#xff0c;以下是常見的分庫分表策略及實施建議&#xff1a; 一、分庫分表核心目標 解決性能瓶頸&#xff1a;應對高并發寫入和查詢壓力。數據均衡分布&#xff1a;避免單庫/單表數據傾斜。簡化運維&#…

操作系統學習筆記第3章 (竟成)

第 3 章 內存管理 【考綱內容】 1.內存管理基礎&#xff1a; 1.內存管理的基本概念&#xff1a;邏輯地址空間與物理地址空間&#xff1b;地址變換&#xff1b;內存共享&#xff1b;內存保護&#xff1b;內存分配與回收&#xff1b; 2.連續分配管理方式&#xff1b; 3.頁式管理&…

中科院無人機導航物流配送的智能變革!LogisticsVLN:基于無人機視覺語言導航的低空終端配送系統

作者&#xff1a;Xinyuan Zhang, Yonglin Tian, Fei Lin, Yue Liu, Jing Ma, Kornlia Sra Szatmry, Fei-Yue Wang 單位&#xff1a;中國科學院大學人工智能學院&#xff0c;中科院自動化研究所多模態人工智能系統國家重點實驗室&#xff0c;澳門科技大學創新工程學院工程科學系…

1.10-數據傳輸格式

1.10-數據傳輸格式 在對網站進行滲透測試時&#xff0c;使用目標服務器規定的數據傳輸格式來進行 payload 測試非常關鍵 如果不按規定格式發送數據&#xff0c;服務器可能直接拒絕請求或返回錯誤響應&#xff0c;比如&#xff1a; 接口要求 JSON 格式&#xff0c;而你用的是…