[C++] 剖析多态的原理及实现

文章目录

  • 多态的概念及定义
    • 编译时多态(静态多态)
    • 运行时多态(动态多态)
      • 动态多态的原理
          • 示例:运行时多态
    • 两种多态的区别
  • 多态的实现
    • 基本条件
    • 虚函数
    • 虚函数的重写与覆盖
    • 虚函数重写的其他问题
      • 协变
      • 析构函数的重写
    • C++11 中的 `override` 和 `final` 关键字
    • 重载、重写和隐藏的对比
      • 重载(Overloading)
      • 重写(Overriding)
      • 隐藏(Hiding)
  • 纯虚函数和抽象类
    • 纯虚函数(Pure Virtual Function)
    • 抽象类(Abstract Class)
  • 多态的原理
    • 虚函数表指针(vptr)
    • 多态实现的原理
      • 如何实现多态?
      • 动态绑定和静态绑定
      • 虚函数表
  • 练习题
    • 多态场景的⼀个选择题
      • 执行流程:

多态的概念及定义

多态(Polymorphism)是面向对象编程中的一个重要概念,它使得同一个行为可以针对不同类型的对象表现出不同的形态。通俗来讲,多态就是“多种形态”的实现。

根据执行的时机,多态可以分为两种类型:

  1. 编译时多态(静态多态)
  2. 运行时多态(动态多态)

编译时多态(静态多态)

编译时多态,顾名思义,就是在编译期间决定函数调用的行为。它通过以下两种方式实现:

  • 函数重载:同名函数可以根据不同的参数类型或数量,做出不同的实现。
  • 模板:函数模板或类模板能够针对不同的类型参数生成不同的代码。

静态多态的特点是函数调用的解析过程在编译时就完成了。例如,函数重载通过传入不同的参数类型,编译器在编译时选择正确的函数版本。

函数重载示例:

void print(int i) {
    std::cout << "Integer: " << i << std::endl;
}

void print(double d) {
    std::cout << "Double: " << d << std::endl;
}

int main() {
    print(10);    // 输出: Integer: 10
    print(3.14);  // 输出: Double: 3.14
    return 0;
}

编译时多态通常称为静态绑定,因为在编译阶段就已经确定了实际调用的函数。这使得编译时多态非常高效,但不具备灵活的运行时决策能力。

运行时多态(动态多态)

运行时多态是在程序运行时,根据实际传入的对象类型来决定函数的具体实现。这种形式的多态依赖于继承虚函数

动态多态的原理

动态多态的核心思想是基类定义了接口(虚函数),而派生类根据自己的需求对这些接口进行不同的实现。在运行时,调用具体派生类的实现,而不是基类的实现。

实现动态多态有两个**必要条件****:**

  1. 基类的函数必须是虚函数,即使用<font style="background-color:#FBDE28;">virtual</font>关键字声明。
  2. 必须通过基类的指针或引用来调用虚函数。
示例:运行时多态

假设有一个“买票”行为,不同类型的对象执行该行为时有不同的表现:

class Person {
public:
    virtual void BuyTicket() {
        std::cout << "买票-全价" << std::endl;
    }
};

class Student : public Person {
public:
    void BuyTicket() override {
        std::cout << "买票-打折" << std::endl;
    }
};

class Soldier : public Person {
public:
    void BuyTicket() override {
        std::cout << "买票-优先" << std::endl;
    }
};

void BuyTicketForPerson(Person* person) {
    person->BuyTicket();  // 调用具体对象的BuyTicket函数
}

int main() {
    Person p;
    Student s;
    Soldier so;

    BuyTicketForPerson(&p);  // 输出: 买票-全价
    BuyTicketForPerson(&s);  // 输出: 买票-打折
    BuyTicketForPerson(&so); // 输出: 买票-优先
    return 0;
}

在这个例子中,BuyTicketForPerson 函数接收一个基类Person的指针,但是在实际调用时,动态多态会根据具体传入的对象类型,调用派生类的BuyTicket函数。对应不同人群的买票价格不一样。

两种多态的区别

  • 编译时多态:通过函数重载和模板实现,函数调用在编译阶段确定,效率高,但灵活性较低。
  • 运行时多态:通过虚函数和继承实现,基类指针或引用根据实际对象类型调用对应的函数实现,具有更大的灵活性,但需要在运行时进行决策。

多态的实现

基本条件

  1. 通过基类的指针或引用调用虚函数:多态的前提是通过基类的指针或引用来访问派生类对象。只有基类的指针或引用才能够指向不同的派生类对象,并且根据派生类对象的实际类型,决定具体调用哪个函数。
  2. 函数必须是虚函数:要想在运行时根据对象的实际类型调用不同的函数实现,基类中的函数必须声明为虚函数(virtual)。虚函数机制使得调用操作在运行时决定,而不是在编译时。
  3. 派⽣类必须对基类的虚函数重写/覆盖,重写或者覆盖了,派⽣类才能有不同的函数,多态的不同形态效果才能达到

虚函数

虚函数是实现多态的核心。在C++中,类的成员函数前加上virtual关键字,就将其声明为虚函数。虚函数允许派生类重写该函数,并在运行时根据实际对象类型调用具体实现

class Person {
public:
    virtual void BuyTicket() {
        std::cout << "买票-全价" << std::endl;
    }
};

BuyTicket是一个虚函数,允许派生类重写它。

虚函数的重写与覆盖

重写(Override)是指派生类对基类的虚函数提供新的实现。派生类中的虚函数必须和基类虚函数的签名完全相同,即**返回类型、函数名、参数列表**必须一致。只有这样,才能保证派生类重写了基类的虚函数。

**注意:**尽管在派生类中可以省略virtual关键字,但不建议这样做,因为它可能会导致可读性和可维护性的下降。

现在可以分析开头引入多态概念的带代码:

  1. 虚函数的重写
class Person {
public:
    virtual void BuyTicket() {
        std::cout << "买票-全价" << std::endl;
    }
};

class Student : public Person {
public:
    void BuyTicket() override {
        std::cout << "买票-打折" << std::endl;
    }
};

Student类重写了Person类的BuyTicket函数。当通过基类指针或引用调用时,实际执行的是派生类的BuyTicket函数。

  1. 使用虚函数实现多态
void Func(Person* ptr) {
    // person* 的ptr会对传入的派生类对象进行切片操作
    // 尽管ptr是Person类型的指针,实际调用的函数由ptr指向的对象决定
    ptr->BuyTicket();
}

int main() {
    Person ps;
    Student st;
    
    Func(&ps);  // 输出: 买票-全价
    Func(&st);  // 输出: 买票-打折
    return 0;
}

通过Person类型的指针调用BuyTicket,具体执行的函数取决于指针实际指向的对象。对于Student对象,将调用其重写的BuyTicket函数。

虚函数重写的其他问题

协变

当派生类重写基类的虚函数时,如果基类虚函数返回基类类型的指针或引用,派生类虚函数可以返回派生类类型的指针或引用。这种情况称为协变

class A {};
class B : public A {};

class Person {
public:
    virtual A* BuyTicket() {
        std::cout << "买票-全价" << std::endl;
        return nullptr;
    }
};

class Student : public Person {
public:
    B* BuyTicket() override {
        std::cout << "买票-打折" << std::endl;
        return nullptr;
    }
};

析构函数的重写

在使用多态时,基类的析构函数应该声明为虚函数,否则会出现内存泄漏问题。如果基类析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,派生类的析构函数不会被调用,导致资源无法释放。

虽然基类与派⽣类析构函数名字不同看起来不符合重写的规则,实际上编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统⼀处理成<font style="color:rgb(31,35,41);">destructor</font>,所以基类的析构函数加了<font style="color:rgb(31,35,41);">vialtual</font>修饰,派⽣类的析构函数就构成重写

class A {
public:
    virtual ~A() {
        std::cout << "~A()" << std::endl;
    }
};

class B : public A {
public:
    ~B() {
        std::cout << "~B()" << std::endl;
    }
};

int main() {
    A* p = new B;
    delete p;  // 正确调用B的析构函数
    return 0;
}

C++11 中的 overridefinal 关键字

为了防止虚函数重写时出现意外情况,C++11引入了overridefinal关键字。

  • override:确保派生类的函数确实是重写了基类的虚函数。如果函数签名不匹配,编译器会报错。
  • final:用于禁止派生类进一步重写某个虚函数。
class Car {
public:
    virtual void Drive() final {
        std::cout << "Car driving" << std::endl;
    }
};

class Benz : public Car {
    // 编译错误,不能重写final函数
    // void Drive() override { std::cout << "Benz driving" << std::endl; }
};

重载、重写和隐藏的对比

重载(Overloading)

重载是指在同一个类中,存在多个同名函数,它们的参数列表不同(参数类型或数量)。重载函数在编译时通过传递给函数的参数类型或数量来确定调用哪个函数。

特点:

  • 发生在同一个作用域中(同一类或同一个函数)。
  • 函数名相同,但参数列表必须不同(类型或数量不同)。
  • 重载与返回值无关,返回值类型不能用于区分重载。
class Example {
public:
    void print(int i) {
        std::cout << "Integer: " << i << std::endl;
    }

    void print(double d) {
        std::cout << "Double: " << d << std::endl;
    }

    void print(std::string s) {
        std::cout << "String: " << s << std::endl;
    }
};

print函数被重载了三次,分别接受intdoublestd::string类型的参数。调用时,根据参数类型选择相应的print函数。

重写(Overriding)

重写是指在继承关系中,派生类对基类的虚函数重新实现。当基类中有虚函数时,派生类可以重写该虚函数,从而在运行时根据实际对象的类型调用对应的函数实现。

特点

  • 发生在继承层次结构中。
  • 基类中的函数必须是虚函数virtual),且派生类的函数与基类虚函数具有相同的签名(即返回值、参数列表必须一致)。
  • 运行时根据对象的实际类型调用对应的派生类或基类函数,实现动态多态
  • 派生类函数可以使用override关键字明确表示重写。
class Base {
public:
    virtual void show() {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void show() override {
        std::cout << "Derived class" << std::endl;
    }
};

Derived类重写了Base类的show函数。当通过基类指针调用show函数时,具体调用哪个函数取决于实际的对象类型。

隐藏(Hiding)

隐藏是指在派生类中定义了一个与基类同名但非虚的函数,此时基类的同名函数会被隐藏。隐藏的函数在派生类中无法通过对象或指针访问,除非显式地使用作用域解析符调用基类版本的函数。

特点

  • 发生在继承层次结构中。
  • 隐藏的函数与重写不同,隐藏的函数不是虚函数,因此不会参与动态多态机制。
  • 派生类函数的签名可以与基类相同,也可以不同,但一旦存在同名函数,基类函数就会被隐藏。
  • 可以通过基类的作用域解析符调用基类函数。
class Base {
public:
    void show() {
        std::cout << "Base class" << std::endl;
    }
};

class Derived : public Base {
public:
    void show(int i) {
        std::cout << "Derived class with int: " << i << std::endl;
    }
};

Derived类的show(int i)函数隐藏Base类的show()函数。通过Derived类的对象,无法调用Base类的show()函数。

如果需要访问基类函数,可以如下使用:

Derived d;
d.show(10);          // 调用 Derived 类的 show(int)
d.Base::show();      // 调用 Base 类的 show()

画板

纯虚函数和抽象类

纯虚函数(Pure Virtual Function)

在C++中,虚函数后加= 0,就将该函数声明为纯虚函数。纯虚函数没有具体实现,只提供接口,要求派生类必须实现该函数。通过纯虚函数,C++允许程序设计者定义一个抽象的接口,并要求任何继承该接口的类必须实现这些接口方法。

  • 定义:虚函数在声明时,末尾加= 0,表明它是一个纯虚函数,无法在基类中实现。
  • 特点:纯虚函数只需要声明,不需要定义。
class Car {
public:
    virtual void Drive() = 0;  // 纯虚函数
};

Car类不能直接实例化,因为它包含了纯虚函数,必须由派生类来实现。

抽象类(Abstract Class)

抽象类是指包含一个或多个纯虚函数的类。抽象类不能被实例化,必须通过派生类进行实例化。抽象类的作用是为派生类提供统一的接口,使得多个派生类可以通过相同的接口进行调用,从而实现多态。

  • 特点:抽象类不能被直接实例化,它只能作为基类存在。
  • 派生类要求:派生类必须实现抽象类中的所有纯虚函数,否则派生类也将成为抽象类,无法实例化。
class Car {
public:
    virtual void Drive() = 0;  // 纯虚函数
};

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

class BMW : public Car {
public:
    void Drive() override {
        std::cout << "BMW-操控" << std::endl;
    }
};

int main() {
    // Car car; // 错误,Car是抽象类,不能实例化

    Car* pBenz = new Benz;
    pBenz->Drive();  // 输出: Benz-舒适

    Car* pBMW = new BMW;
    pBMW->Drive();   // 输出: BMW-操控

    delete pBenz;
    delete pBMW;
    return 0;
}

Car是一个抽象类,因为它包含了纯虚函数DriveBenzBMW继承自Car,并且实现了Drive函数。因此,BenzBMW对象可以通过Car类型的指针实现多态调用。

多态的原理

虚函数表指针(vptr)

每个包含虚函数的对象都有一个隐藏的指针,称为虚函数表指针(vptr)。该指针指向该类的虚函数表,虚函数表中存储了类中虚函数的地址。

  • vptr的作用:vptr用于指向对象的虚函数表,帮助程序在运行时通过虚函数表找到具体的函数实现。
  • vptr的存储位置:vptr通常位于对象内存布局的开头,但这取决于编译器的实现。在某些平台上,vptr可能会位于对象的最后。

虚函数表指针用来指向当前对象对应的虚函数表(虚表)

多态实现的原理

如何实现多态?

class Person {
public:
    virtual void BuyTicket() {
        std::cout << "买票-全价" << std::endl;
    }
};

class Student : public Person {
public:
    void BuyTicket() override {
        std::cout << "买票-打折" << std::endl;
    }
};

class Soldier : public Person {
public:
    void BuyTicket() override {
        std::cout << "买票-优先" << std::endl;
    }
};

void Func(Person* ptr) {
    ptr->BuyTicket();  // 通过指针调用虚函数
}

int main() {
    Person ps;
    Student st;
    Soldier sr;

    Func(&ps);  // 输出: 买票-全价
    Func(&st);  // 输出: 买票-打折
    Func(&sr);  // 输出: 买票-优先
    return 0;
}

Person*类型的ptr作为Func的形参,用来指向接受的对象并进行切片。当运行的时候,多态展现出来,并不是使用PersonBuyTicket。通过上述图片发现当每次将不同类型传入Funcptr调用的都是接受的那个对象的类的BuyTicket。这样就是实现了指针或引用指向基类或者派生类直接调用指向类的虚函数。实现了多态。

动态绑定和静态绑定

  • 静态绑定:编译器在编译时已经确定了函数调用的地址,通常用于普通函数(不满足多态条件)。由于函数地址在编译时已经确定,静态绑定非常高效。
  • 动态绑定:程序在运行时根据对象的实际类型确定函数的调用地址,通常用于虚函数。这种方式提供了极大的灵活性,但运行时效率相对静态绑定较低。
// ptr是指针+BuyTicket是虚函数满⾜多态条件。

// 这⾥就是动态绑定,编译在运⾏时到ptr指向对象的虚函数表中确定调⽤函数地址
ptr->BuyTicket();

00EF2001 mov eax,dword ptr [ptr]
00EF2004 mov edx,dword ptr [eax]
00EF2006 mov esi,esp
00EF2008 mov ecx,dword ptr [ptr]
00EF200B mov eax,dword ptr [edx]
00EF200D call eax

// BuyTicket不是虚函数,不满⾜多态条件。
// 这⾥就是静态绑定,编译器直接确定调⽤函数地址
ptr->BuyTicket();

00EA2C91 mov ecx,dword ptr [ptr]
00EA2C94 call Student::Student (0EA153Ch)

虚函数表

  • 基类的虚函数表:基类的虚表中存放该类所有虚函数的地址。当基类中的虚函数未被派生类重写时,派生类的虚表会继承这些地址。
  • 派生类的虚函数表:当派生类重写了基类的虚函数,派生类的虚表中的相应条目会替换为派生类的虚函数地址。派生类的虚表包含三类地址:
  1. 基类的虚函数地址:未被派生类重写的基类虚函数。
  2. 重写的虚函数地址:派生类对基类虚函数的重写。
  3. 派生类特有的虚函数地址:派生类定义的独有虚函数。
    1. 派生类独有的虚函数地址被存放在虚函数表的最后
    2. 如果一个派生类继承了多个有虚函数的类,一个类对应一个虚函数表,派生类独有的虚函数地址存放在第一个虚函数表的后面。
  • 派生类的虚函数表与基类相独立

  • 派⽣类中重写的基类的虚函数,派⽣类的虚函数表中对应的虚函数就会被覆盖成派⽣类重写的虚函
    数地址。

  • 虚函数表的底层工作原理

    • 当通过基类指针调用虚函数时,程序会:
  1. 通过对象的vptr访问该对象的虚函数表。
  2. 在虚表中找到对应函数的地址。
  3. 通过动态绑定机制)调用函数,函数的具体实现取决于虚表中存储的地址。

虚函数表是一个由虚函数指针构成的数组,虚表的最后可能会存储一个标记(如0x00000000),用来表示数组的结束(不同的编译器可能会有不同的实现)。

  • 虚函数存储在哪?

虚函数和普通函数一样,编译后会变成一段机器指令,并被存储在代码段(Code Segment)中。虚函数表本质上是指向这些指令的指针数组。

+-------------------------+
|       代码段            |   --> 存储所有的函数(包括虚函数)的指令代码
+-------------------------+
|    只读数据段/常量区    |   --> 存储常量字符串、只读数据(如虚表)
+-------------------------+
|       全局数据区        |   --> 存储全局和静态变量
+-------------------------+
|       堆(Heap)        |   --> 动态分配的对象(如new分配的对象)
+-------------------------+
|       栈(Stack)       |   --> 存储局部变量和函数调用的上下文
+-------------------------+

练习题

多态场景的⼀个选择题

以下程序输出结果是什么( )
A:A->0B:B->1C:A->1D:B->0E:编译出错F:以上都不正确

class A
{
public :
    virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
    virtual void test() { func(); }
};

class B : public A
{
public :
    void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};

int main(int argc, char* argv[])
{
    B* p = new B;
    p->test();

    return 0;
}
  1. 虚函数与默认参数
    • 在C++中,默认参数的绑定是在编译时完成的,而虚函数调用的解析是在运行时完成的。尽管函数调用的解析在运行时根据对象的类型调用了B类的func,但是默认参数的值是在编译时绑定的,它依然使用了基类**A**的默认参数值
  2. 详细过程
    • 当调用p->test()时,程序首先执行test函数,这个函数是A类中的虚函数。
    • test()函数内部调用了func(),由于func是虚函数,调用的是B类重写的func
    • 虽然B类的func函数被调用了,但是默认参数val是在编译时绑定的,所以val的值仍然是基类**A**的默认值**1**,而不是B类中的0
    • 因此,func()的输出是B->1

执行流程:

  1. 调用p->test()
    • 调用的是A类中的test函数,执行test()中的func()调用。
  2. func()是虚函数,实际调用的是B类的func
  3. 虽然调用了B类的func,但是由于默认参数val在编译时已绑定为1(基类A的默认参数),所以输出B->1

或者说:重写的是函数体部分

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/876140.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

引领智能家居新风尚,WTN6040F门铃解决方案——让家的呼唤更动听

在追求高效与便捷的智能家居时代&#xff0c;每一个细节都承载着我们对美好生活的向往。WTN6040F&#xff0c;作为一款专为现代家庭设计的低成本、高性能门铃解决方案&#xff0c;正以其独特的魅力&#xff0c;悄然改变着我们的居家生活体验。 芯片功能特点&#xff1a; 1.2.4…

直流电源纹波怎么测量?示波器的探头和带宽如何选择?

对于电源工程师来说&#xff0c;精确测量电源纹波是一项基本技能。本文将详细介绍直流电源纹波测试时的注意事项&#xff0c;包括示波器探头的选择、带宽设置、时基选择&#xff0c;确保精准测量直流电源纹波。 一、选择合适的示波器带宽 为了避免电路的高频噪声影响电源纹波的…

基于树莓派ubuntu20.04的ros-noetic小车

目录 一、小车的架构 1.1 总体的概述 1.2 驱动系统 1.3 控制系统 二、驱动系统开发 2.1 PC端Ubuntu20.04安装 2.2 树莓派Ubuntu20.04安装 2.3 PC端虚拟机设置静态IP 2.4 树莓派设置静态IP 2.5 树莓派启动ssh进行远程开发 2.5 arduino ide 开发环境搭建 2.5.1 PC…

C++: 二叉树进阶面试题

做每件事之前都心存诚意, 就会事半功倍. 目录 前言1. 根据二叉树创建字符串2. 二叉树的层序遍历Ⅰ3. 二叉树的层序遍历Ⅱ4. 二叉树的最近公共祖先5. 二叉搜索树与双向链表6. 根据一棵树的前序遍历与中序遍历构造二叉树7. 根据一棵树的中序遍历与后序遍历构造二叉树8. 二叉树的…

【数据结构】8——图3,十字链表,邻接多重表

数据结构8——图3&#xff0c;十字链表&#xff0c;邻接多重表 文章目录 数据结构8——图3&#xff0c;十字链表&#xff0c;邻接多重表前言一、十字链表结构例子 复杂例子 二、邻接多重表&#xff08;Adjacency Multilist&#xff09;例子 前言 除了之前的邻接矩阵和邻接表 …

在k8s中,客户端访问服务的链路流程,ingress--->service--->deployment--->pod--->container

图片来源&#xff1a;自己画的 ingress是一个API资源。 客户端访问ingress的不同url ingress给客户端返回不同的服务。 就和nginx反向代理服务器一样。 根据不同的url&#xff0c;给客户端返回不同的服务。 -----------------------------------------------------------…

MySql基础-单表操作

1. MYSQL概述 1.1 数据模型 关系型数据库 关系型数据库(RDBMS)&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 特点&#xff1a; 使用表存储数据&#xff0c;格式统一&#xff0c;便于维护 使用SQL语言操作&#xff0c;标准统一&…

班迪录屏和这三款录屏工具,一键操作,太方便了!

嘿&#xff0c;小伙伴们&#xff01;今天我要跟大家分享几款超棒的录屏工具&#xff0c;它们绝对是我们在工作和学习中不可或缺的好帮&#xff1b;这些工具功能强大且操作简单&#xff0c;下面就让我来详细介绍一下它们的使用体验和好用之处吧&#xff01; 班迪录屏工具使用体…

医学数据分析实训 项目二 数据预处理作业

文章目录 项目二 数据预处理一、实践目的二、实践平台三、实践内容任务一&#xff1a;合并数据集任务二&#xff1a;独热编码任务三&#xff1a;数据预处理任务四&#xff1a;针对“项目一 医学数据采集”中“3. 通过 UCI 机器学习库下载数据集”任务所下载的数据集进行预处理。…

新能源汽车BMS 学习笔记篇—AFE 菊花链通信中电容隔离 电感隔离的使用

在汽车高压BMS系统中&#xff0c;通常采用 CAN 总线或菊花链&#xff08;&#xff08;Daisy Chain&#xff09;架构。菊花链架构通过串行连接每个节点&#xff0c;通常只需要两条信号线穿过所有节点。相比之下&#xff0c;CAN总线通常需要多个并行连接到总线上&#xff0c;布线…

一些写leetcode的笔记

标准库中的string类没有实现像C#和Java中string类的split函数&#xff0c;所以想要分割字符串的时候需要我们自己手动实现。但是有了stringstream类就可以很容易的实现&#xff0c;stringstream默认遇到空格、tab、回车换行会停止字节流输出。 #include <sstream> #incl…

沉浸式体验Stability AI最新超强AI图片生成模型Ultra

2024年9月4日&#xff0c;亚马逊云科技在Amazon Bedrock上新了Stability AI最新的的三款文本图像生成模型&#xff1a;他们分别是Stable Image Ultra、Stable Diffusion 3 Large 和 Stable Image Core。全新的模型在处理多主题提示词、图像质量和图片排版上较上一代模型有显著提…

美团图床设置教程

大厂图床&#xff0c;CDN加速 项目地址&#xff1a;https://github.com/woniu336/mt-img 使用方法 在mt.php填上你的token即可&#xff0c;然后打开index.html上传图片 获取token方法 注册https://czz.meituan.com/发布视频&#xff0c;上传封面&#xff0c;注意在上传封面后…

jenkins流水线+k8s部署springcloud微服务架构项目

文章目录 1.k8s安装2.jenkins安装3.k8s重要知识1.简介2.核心概念3.重要命令1.查看集群消息2.命名空间3.资源创建/更新4.资源查看5.描述某个资源的详细信息6.资源编辑7.资源删除8.资源重启9.查看资源日志10.资源标签 4.k8s控制台1.登录2.界面基本操作1.选择命名空间2.查看命名空…

CCS6 软件及仿真器驱动安装

1 CCS6 软件获取 TI 的官网上下载: http://www.ti.com/tools-software/ccs.html 注意 首先 win32 是 CCS 安装包支持 64 位系统,我们电脑也是 64 位系统也是安装的 win32 的安装包,另外 TI 只提供 win32 的安装包,无 win64 的安装包。 2 CCS6 软件安装 CCS如果获取提供的…

第十二周:机器学习笔记

第十二周周报 摘要Abstract机器学习1. Recurrent Neural Network&#xff08;下&#xff09;1.1 RNN的Loss Function怎么求&#xff1f;1.2 RNN奇怪的特性1.3 如何解决 RNN 梯度消失或者爆炸1.4 RNN 其他应用 Pytorch学习1. 现有的网络模型使用以及其修改1.1 在VGG16模型添加Mo…

docker部署bind9

一、部署 ## docker 部署bind9# docker run -d --name bind9 --restartalways --publish 53:53/tcp --publish 53:53/udp --publish 10000:10000/tcp --volume /data/docker/dns-server:/data --env ROOT_PASSWORDroot dhub.kubesre.xyz/sameersbn/bind:9.16.1-20200524# 建数…

小程序——生命周期

文章目录 运行机制更新机制生命周期介绍应用级别生命周期页面级别生命周期组件生命周期生命周期两个细节补充说明总结 运行机制 用一张图简要概述一下小程序的运行机制 冷启动与热启动&#xff1a; 小程序启动可以分为两种情况&#xff0c;一种是冷启动&#xff0c;一种是热…

53.9k star 提升命令行效率的模糊搜索神器--fzf

fzf简介 作为Linux/Unix命令行的重度用户,你是否还在使用繁琐的管道命令与复杂选项组合来过滤文件和数据?其实我们有一个更简单高效的选择 - fzf。 fzf是一个开源的通用模糊搜索工具,可以大幅度提升命令行的使用体验。它的查询运行速度极快,支持预览选中的文件内容,还能与各…

Tableau学习日记

Day1&#xff1a;Tableau简介、条形图与直方图 1.1 Tableau绘制条形图 1.1.1 条形图1&#xff1a;各地区酒店数量 1.1.2 条形图2&#xff1a;各地区酒店均价 1.1.3 堆积图&#xff1a;价格等级堆积图 1.2 Tableau绘制直方图 1.2.1创建评分直方图 Day2&#xff1a;数据处理、…