十五、多态与虚函数

十五、多态与虚函数

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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;而你用的是…