【C++入门到精通】C++入门 —— 多态(抽象类和虚函数的魅力)

在这里插入图片描述

阅读导航

  • 前言
  • 一、多态的概念
    • 1. 概念
    • 2. 多态的特点
  • 二、多态的定义及实现
    • 1. 多态的构成条件
    • 2. 虚函数
    • 3. 虚函数的重写
      • ⭕虚函数重写的两个例外
        • 1.协变(基类与派生类虚函数返回值类型不同)
        • 2.析构函数的重写(基类与派生类析构函数的名字不同)
    • 4. override 和 final(C++11 )
      • override
      • final
    • 5. 重载、覆盖(重写)、隐藏(重定义)的对比
      • 1. 重载
      • 2. 覆盖(也称为重写)
      • 3. 隐藏(也称为重定义)
  • 三、抽象类
    • 1. 抽象类的概念
    • 2. 接口继承和实现继承
      • -- 接口继承
      • -- 实现继承
      • -- 适用类型
  • 四、多态的原理
    • 1. 虚函数表
    • 2. 动态绑定与静态绑定
      • 动态绑定
      • 静态绑定
    • 3. 多态的原理
  • 五、单继承和多继承关系中的虚函数表
    • 1. 单继承中的虚函数表
    • 2. 多继承中的虚函数表
  • 六、多态总结
  • 温馨提示

前言

前面我们讲了C语言的基础知识,也了解了一些数据结构,并且讲了有关C++的命名空间的一些知识点以及关于C++的缺省参数、函数重载,引用 和 内联函数也认识了什么是类和对象以及怎么去new一个 ‘对象’ ,也了解了C++中的模版,以及学习了几个STL的结构也相信大家都掌握的不错,接下来博主将会带领大家继续学习有关C++比较重要的知识点—— 多态(抽象类和虚函数的魅力)。下面话不多说坐稳扶好咱们要开车了😍

一、多态的概念

1. 概念

多态是面向对象编程中的一个重要概念,指的是同一个消息被不同类型的对象接收时产生不同的行为。通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

多态的核心思想是通过基类指针或引用来调用派生类的方法,在运行时确定实际调用的方法。这种动态绑定的特性使得代码更加灵活,能够适应不同的对象和场景。

2. 多态的特点

  1. 多态性通过继承和虚函数的机制来实现。
  2. 多态性在运行时动态地确定调用的函数,而不是在编译时静态地确定。
  3. 多态性可以让相同的代码适用于不同类型的对象,提高代码的可重用性和扩展性。
  4. 多态性能够进行方法重写,不同的派生类可以根据自身的需求提供不同的方法实现。

二、多态的定义及实现

1. 多态的构成条件

实现多态性的关键是满足以下两个条件:

  1. 必须通过基类的指针或者引用调用虚函数
  2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写

🚨虚函数:通过在基类中使用 virtual 关键字声明函数为虚函数,可以实现动态绑定,使得在运行时选择调用实际对象的方法。(下面会有概念及定义)

虚函数允许通过基类指针或引用调用派生类的方法,而在运行时动态地确定实际调用的函数。当通过基类指针或引用调用虚函数时,编译器会根据实际对象的类型来调用相应的派生类函数。

以下是实现多态性的典型代码结构:

class Base {
public:
    virtual void polymorphicFunction() {
        // 基类虚函数的默认实现
    }
};

class Derived : public Base {
public:
    virtual void polymorphicFunction() {
        // 派生类重写基类虚函数的实现
    }
};

基类 Base 声明了一个虚函数 polymorphicFunction(),派生类 Derived 重写了这个虚函数。通过基类指针或引用来调用该虚函数时,实际执行的是派生类中的函数实现。

总结起来,多态的构成条件是基于继承关系和虚函数的使用。满足这两个条件后,就可以通过基类的指针或引用调用派生类的函数,实现多态性的效果。

下面这个图片模拟了一个简单的多态的定义以及实现的过程,实现了“不同人群,买了不同的票”
在这里插入图片描述

2. 虚函数

虚函数:即被virtual修饰的类成员函数称为虚函数。例如:

class Base {
public:
    virtual void myFunction() {
        // 基类虚函数的默认实现
    }
};

3. 虚函数的重写

虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

class Derived : public Base {
public:
    virtual void myFunction() {
        // 派生类重写基类虚函数的实现
    }
};

⭕虚函数重写的两个例外

1.协变(基类与派生类虚函数返回值类型不同)

🚩派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

协变的实现和使用需要满足以下条件:

  1. 继承关系
    协变是基于继承关系的特性,需要存在基类和派生类。派生类继承了基类的属性和方法。

  2. 虚函数
    必须在基类中将要协变的虚函数声明为虚函数,并在派生类中重新定义(重写)该虚函数。基类中的虚函数返回类型应该是指向某种类型的指针或引用,而派生类中的重写函数的返回类型可以是指向派生类类型的指针或引用。

下面是一个示例来说明协变的使用方法:

class Base {
public:
    virtual Base* createInstance() {
        return new Base();
    }
};

class Derived : public Base {
public:
    Derived* createInstance() override {
        return new Derived();
    }
};

在上述示例中,Base 类声明了一个虚函数 createInstance(),该函数返回类型为 Base*,即指向 Base 类对象的指针。在派生类 Derived 中重写了这个函数,将返回类型修改为 Derived*,即指向 Derived 类对象的指针。

通过协变,派生类 Derived 可以返回比基类 Base 更具体的类型,即 Derived*。这样,在使用基类指针调用派生类的虚函数时,实际会返回派生类的对象指针。

🚨注意

  • 协变只适用于指针或引用类型的返回值,而不适用于值类型
  • 派生类重写函数的返回类型必须在基类函数返回类型的派生类型层次结构中

2.析构函数的重写(基类与派生类析构函数的名字不同)

🍪如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor

以下是一个示例演示如何在派生类中重写基类的析构函数:

class Base {
public:
    virtual ~Base() {
        // 基类析构函数的实现
    }
};

class Derived : public Base {
public:
    ~Derived() override {
        // 派生类析构函数的实现
    }
};

在上述示例中,基类 Base 声明了一个虚析构函数 ~Base()。派生类 Derived 重写了基类的虚析构函数,使用 ~Derived() 来重新定义析构函数的实现。

🚨注意:

  • 虚析构函数只需要在基类中声明,不需要在派生类中显式声明为虚函数
  • 为了确保正确的析构顺序,派生类的析构函数可以在末尾隐式调用基类的析构函数,不需要显式调用

⭕重写基类的析构函数在处理继承关系和多态性时非常重要,它能够确保在销毁派生类对象时,正确地调用相应的析构函数,避免资源泄漏和未定义行为

4. override 和 final(C++11 )

overridefinal 都是 C++11 中引入的关键字,用于标识和修饰虚函数的行为。

override

override 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错

示例:

class Base {
public:
    virtual void myFunction() {
        // 基类虚函数的默认实现
    }
};

class Derived : public Base {
public:
    void myFunction() override {
        // 派生类重写基类虚函数的实现
    }
};

在上述示例中,派生类 Derived 使用 override 关键字表明它重写了基类 Base 的虚函数 myFunction()。如果在派生类中意外地使用了错误的函数签名(参数列表或返回类型不匹配),编译器会发出错误提示。

final

final 用于标识类、成员函数或虚函数,表示它们被声明为最终版本,禁止在派生类中进一步继承或重写。

示例:

class Base final {
public:
    virtual void myFunction() {
        // 基类虚函数的默认实现
    }
};

class Derived : public Base {
public:
    void myFunction() /* override 不可使用 */ {
        // 派生类重写基类虚函数的实现
    }
};

在上述示例中,基类 Base 使用 final 关键字标识它是最终类,不允许被继续派生。同时,派生类 Derived 中的 myFunction() 不能使用 override 关键字进行标识,因为基类已经被声明为最终类

5. 重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述

1. 重载

重载是指在同一个作用域内,根据函数的参数列表的不同,可以定义多个具有相同名称但参数不同的函数。重载函数可以有不同的返回类型,但不能仅仅通过返回类型的不同来进行重载。

示例:

void myFunction(int x) {
    // 函数实现
}

void myFunction(double x) {
    // 函数实现
}

在上述示例中,myFunction 函数被重载了,分别接受一个 int 类型和一个 double 类型的参数。

2. 覆盖(也称为重写)

覆盖是指派生类中重新定义(重写)基类的虚函数,以实现多态性。派生类中的虚函数必须与基类的虚函数具有相同的函数签名(包括参数列表和返回类型),并使用 override 关键字进行标识。

示例:

class Base {
public:
    virtual void myFunction() {
        // 基类虚函数的默认实现
    }
};

class Derived : public Base {
public:
    void myFunction() override {
        // 派生类重写基类虚函数的实现
    }
};

在上述示例中,派生类 Derived 重写了基类 Base 的虚函数 myFunction(),并使用 override 关键字进行标识。

3. 隐藏(也称为重定义)

隐藏是指派生类中定义了与基类中同名的非虚函数或静态函数,从而隐藏了基类中的同名函数。隐藏函数不具有多态性,调用时会根据对象的静态类型来确定调用的函数。

示例:

class Base {
public:
    void myFunction() {
        // 基类函数的实现
    }
};

class Derived : public Base {
public:
    void myFunction() {
        // 派生类函数的实现
    }
};

在上述示例中,派生类 Derived 定义了与基类 Base 同名的函数 myFunction(),从而隐藏了基类的同名函数。

总结:

  • 重载是指在同一个作用域内定义多个具有相同名称但参数列表不同的函数。
  • 覆盖是指派生类中重新定义(重写)基类的虚函数,以实现多态性。
  • 隐藏是指派生类中定义了与基类中同名的非虚函数或静态函数,从而隐藏了基类中的同名函数。

三、抽象类

1. 抽象类的概念

抽象类是一种特殊的类,它不能实例化对象,只能作为其他类的基类。抽象类用于定义一组相关的类的共同接口,它包含纯虚函数和非纯虚函数

可以通过在类中声明纯虚函数来创建抽象类。纯虚函数是一个在基类中声明但没有具体实现的虚函数,它通过在函数声明后添加 “= 0” 来指示

抽象类的主要目的是定义一组规范和约束,要求派生类实现纯虚函数,以便在派生类中实现具体的功能。

以下是一个抽象类的示例:

class AbstractClass {
public:
    virtual void pureVirtualFunction() const = 0;
    virtual void virtualFunction() const {
        // 可选的虚函数实现
    }
    void regularFunction() const {
        // 非虚函数的实现
    }
};

在上述示例中,AbstractClass 是一个抽象类。它包含了纯虚函数 pureVirtualFunction(),该函数没有具体实现,需要在派生类中进行实现。同时,它还包含了一个可选的虚函数 virtualFunction() 和一个非虚函数 regularFunction()

抽象类具有以下特点:

  • 不能创建抽象类的对象,只能通过派生出的非抽象子类来创建对象。
  • 子类必须实现抽象类中的纯虚函数,否则子类也成为抽象类。
  • 抽象类可以包含非纯虚函数和数据成员。
  • 抽象类的指针可以用于引用或指向派生类的对象。

抽象类提供了一个接口的定义和封装,强制要求派生类实现接口中的方法。这种设计方式有助于实现多态性和代码的可扩展性,提高代码的模块化和重用性。

2. 接口继承和实现继承

– 接口继承

  • 接口继承关注的是类与类之间的协议或合约。一个类可以通过继承一个或多个接口来表明它具有实现这些接口所定义的方法和行为。
  • 接口(Interface)定义了一组纯虚函数(只有声明没有实现),表示了一种约定或契约,派生类必须实现这些纯虚函数。
  • 接口继承使得类之间的关系更加松耦合,可以实现更好的代码复用和扩展性。

– 实现继承

  • 实现继承关注的是类与类之间的具体的实现关系。一个类可以通过继承的方式获取另一个类的属性和方法的实现。
  • 在实现继承中,子类(派生类)继承父类(基类)的成员变量和成员方法,并可以通过继承的方式进行扩展或修改。
  • 实现继承可以扩展基类的功能,实现代码重用,但也可能导致类之间的紧密耦合。

– 适用类型

  • 当关注类之间的协议和约定,希望实现一种行为的一致性时,可以选择接口继承。
  • 当一个类希望获取另一个类的具体实现,并在此基础上进行扩展时,可以选择实现继承。

四、多态的原理

1. 虚函数表

我们首先来看一道题目:sizeof(Base)是多少?

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

在这里插入图片描述

通过观察测试我们发现Base对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtualf代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表

针对上面的代码我们做出以下改造

  1. 我们增加一个派生类Derive去继承Base
  2. Derive中重写Func1
  3. Base再增加一个虚函数Func2和一个普通函数Func3
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;
}

通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员

  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖重写是语法的叫法,覆盖是原理层的叫法。

  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表

  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr

  5. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

  6. 虚函数存在哪的?
    答:虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。(虚表存的是虚函数指针,不是虚函数,另外对象中存的不是虚表,存的是虚表指针)

2. 动态绑定与静态绑定

动态绑定

  • 动态绑定是在运行时确定函数、方法或操作要与调用者绑定的过程。
  • 动态绑定适用于虚函数和纯虚函数,通过基类的指针或引用来调用派生类的虚函数。
  • 在动态绑定中,根据对象的实际类型(而不是引用或指针的类型)来确定要调用的函数。这意味着在运行时才能确定函数的具体绑定。
  • 动态绑定通过虚函数表和虚函数指针来实现。编译器会查找虚函数表,并根据对象的实际类型找到对应的虚函数进行调用。

⭕动态绑定实现了多态性,使得通过基类的指针或引用调用派生类的虚函数时,能够在运行时根据对象的实际类型决定要调用的函数,从而实现了多态行为。

静态绑定

  • 静态绑定是在编译时确定函数、方法或操作要与调用者绑定的过程。
  • 在静态绑定中,根据引用或指针的声明类型来确定要调用的函数、方法或操作。
  • 静态绑定适用于非虚函数、静态函数和全局函数。编译器在编译阶段就能够确定调用哪个函数。
  • 静态绑定通常比动态绑定更快,因为在编译时已经决定了函数调用的路径。

🚨注意:静态绑定和动态绑定是相对的概念,多态性是建立在动态绑定的基础上的。在面向对象编程中,静态绑定主要用于非虚函数或静态函数,而动态绑定主要用于虚函数。

3. 多态的原理

🔴多态的原理主要基于两个关键的机制:虚函数和动态绑定

🚩多态的实现步骤

  1. 定义一个基类,并在其中声明一个或多个虚函数。
  2. 派生类继承基类,并可以选择重写基类中的虚函数。
  3. 创建基类的指针或引用,并指向派生类的对象。
  4. 通过基类的指针或引用调用虚函数。
  5. 运行时系统根据对象的实际类型确定要调用的虚函数,实现动态绑定,即根据对象的真正类型来调用正确的函数。

需要注意的是,多态性只适用于通过指针或引用调用虚函数的情况。当直接通过对象调用函数时,编译器在编译时就能够确定调用哪个函数,不会发生动态绑定。

多态通过虚函数和动态绑定实现,允许我们以统一的方式处理不同类型的对象,并在运行时根据对象的实际类型调用适当的虚函数,从而实现了面向对象编程中的多态性。

五、单继承和多继承关系中的虚函数表

1. 单继承中的虚函数表

✅**在单继承关系中,每个类只有一个直接基类,因此虚函数表的实现相对简单。**每个类的虚函数表存储了该类及其直接基类中的所有虚函数的地址。当通过对象的指针或引用调用虚函数时,编译器会使用对象的虚函数指针(vptr)来查找虚函数表,并根据对象的实际类型确定要调用的具体虚函数。

2. 多继承中的虚函数表

我们首先来看一段代码

class Base1 {
public:
	virtual void func1() {cout << "Base1::func1" << endl;}
	virtual void func2() {cout << "Base1::func2" << endl;}
private:
	int b1;
};

class Base2 {
public:
	virtual void func1() {cout << "Base2::func1" << endl;}
	virtual void func2() {cout << "Base2::func2" << endl;}
private:
	int b2;
};

class Derive : public Base1, public Base2 {
public:
	virtual void func1() {cout << "Derive::func1" << endl;}
	virtual void func3() {cout << "Derive::func3" << endl;}
private:
	int d1;
};

typedef void (*VFPTR)(); // 定义函数指针类型 VFPTR,用于表示虚函数的地址

void PrintVTable(VFPTR vTable[])
{
    cout << "虚表地址>" << vTable << endl; // 输出虚函数表的地址

    // 循环遍历虚函数表,直到遇到空指针为止
    for (int i = 0; vTable[i] != nullptr; ++i)
    {
        printf("第%d个虚函数地址: 0x%x, ->", i, vTable[i]); // 输出虚函数在虚函数表中的索引位置和地址(以十六进制格式表示)

        VFPTR f = vTable[i]; // 将当前虚函数的地址赋值给函数指针变量 f

        f(); // 通过调用函数指针 f 执行对应的虚函数
    }

    cout << endl; // 输出换行符表示当前虚函数的输出结束
}

int main()
{
	Derive d;
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(vTableb1);
	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));
	PrintVTable(vTableb2);
	return 0;
}

观察下图可以看出:多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
在这里插入图片描述
这里有两篇文章推荐大家看一下
C++ 虚函数表解析
C++ 对象的内存布局

六、多态总结

  1. 多态性通过虚函数和动态绑定实现。虚函数是在基类中声明的带有关键字 virtual 的函数,它可以在派生类中重写(覆盖)。动态绑定是根据对象的实际类型来确定要调用的虚函数。

  2. 多态允许通过基类的指针或引用操作不同类型的对象,而无需关心对象的具体类型。这提高了代码的可扩展性和可维护性。

  3. 多态遵循面向对象编程的封装和抽象原则,能够隐藏对象的具体实现细节,将关注点放在接口和行为上。

  4. 在单继承关系中,每个类只有一个直接基类,虚函数表的实现相对简单,直接存储类及其直接基类的虚函数地址。通过对象的指针或引用调用虚函数时,根据对象的虚函数指针和虚函数表确定要调用的函数。

  5. 在多继承关系中,一个类可以有多个直接基类,可能涉及虚基类表和虚函数表指针数组的实现。虚基类表用于存储虚基类的相关信息,虚函数表指针数组在派生类的虚函数表中包含指向每个基类虚函数表的指针。

  6. 多态性只适用于通过指针或引用调用虚函数的情况。直接通过对象调用函数时,编译器在编译时就能够确定调用哪个函数,不会发生动态绑定。

温馨提示

感谢您对博主文章的关注与支持!另外,我计划在未来的更新中持续探讨与本文相关的内容,会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

1.4亿X区城市运行“一网统管”体系建设项目项目招标WORD

导读&#xff1a;原文《1.4亿X区城市运行“一网统管”体系建设项目项目招标WORD》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分内容&#xff1a; 各部分需求…

为Claude的分析内容做准备:提取PDF页面内容的简易应用程序

由于Claude虽然可以分析整个文件&#xff0c;但是对文件的大小以及字数是有限制的&#xff0c;为了将pdf文件分批传入Claude人工智能分析和总结文章内容&#xff0c;才有了这篇博客&#xff1a; 在本篇博客中&#xff0c;我们将介绍一个基于 wxPython 和 PyMuPDF 库编写的简易的…

FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS

FreeSWITCH 1.10.10 简单图形化界面5 - 使用百度TTS 0、 界面预览1、注册百度AI开放平台&#xff0c;开通语音识别服务2、获取AppID/API Key/Secret Key3、 安装百度语音合成sdk4、合成代码5、在PBX中使用百度TTS6、音乐文件-TTS7、拨号规则-tts_command 0、 界面预览 http://…

FxFactory 8 Pro Mac 苹果电脑版 fcpx/ae/motion视觉特效软件包

FxFactory pro for mac是应用在Mac上的fcpx/ae/pr视觉特效插件包&#xff0c;包含了成百上千的视觉效果&#xff0c;打包了很多插件&#xff0c;如调色插件&#xff0c;转场插件&#xff0c;视觉插件&#xff0c;特效插件&#xff0c;文字插件&#xff0c;音频插件&#xff0c;…

C语言基础之——指针(下)

前言&#xff1a;本篇文章将继续讲解有关指针的剩余基础知识。 学无止境&#xff0c;一起加油叭&#xff01;&#xff01; 目录 一.指针运算 1.指针 - 整数 2.指针的关系运算 3.指针 - 指针 二.指针与数组 三.二级指针 四.指针数组 总结 一.指针运算 指针运算包括以下三…

09-微信小程序 网络请求API(实现轮播广告和简易的聊天窗口)

09-微信小程序API网络请求(实现轮播广告和简易的聊天窗口) 文章目录 微信小程序API服务器域名配置注意网络相关APIrequestRequestTask 请求任务对象object.success 回调函数object.fail 回调函数案例代码&#xff08;实现轮播图&#xff09; WebSocket案例代码&#xff08;实现…

C++数据结构学习——栈

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、栈二、C语言实现1.声明代码2.实现增删查改代码3.测试代码 总结 前言 栈&#xff08;Stack&#xff09;是计算机科学中一种常见的数据结构&#xff0c;它是…

PHP敬老院管理系统Dreamweaver开发mysql数据库web结构php编程计算机网页

一、源码特点 PHP 敬老院管理系统&#xff08;养老&#xff09;是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 论文 https://download.csdn.net/download/qq_41221322/…

Wlan——STA上线流程与802.11MAC帧讲解以及报文转发路径

目录 802.11MAC帧基本概念 802.11帧结构 802.11MAC帧的分类 管理帧 控制帧 数据帧 STA接入无线网络流程 信号扫描—管理帧 链路认证—管理帧 用户关联—管理帧 用户上线 不同802.11帧的转发路径 802.11MAC帧基本概念 802.11协议在802家族中的角色位置 其中802.3标…

数据结构——栈和队列OJ题

栈和队列小提升&#xff01; 前言一、用队列实现栈队列接口实现&#xff08;1&#xff09;栈的接口定义&#xff08;2&#xff09;栈的初始化&#xff08;3&#xff09;入栈函数的定义&#xff08;4&#xff09;出栈函数的定义&#xff08;5&#xff09;查找栈顶元素&#xff0…

Python“牵手”当当网商品详情API接口运用场景及功能介绍,当当网API接口申请指南

当当网是全球知名的综合性网上购物商城&#xff0c;由国内著名出版机构科文公司、美国老虎基金、美国IDG集团、卢森堡剑桥集团、亚洲创业投资基金&#xff08;原名软银中国创业基金&#xff09;共同投资成立。当当网是北京当当网信息技术有限公司营运的一家中文购物网站&#x…

QT版权查询

文章目录 QT工具版权QT模块版权查询 根据条件自动筛选&#xff1a; Qt Features, Framework Essentials, Modules, Tools & Add-Ons QT工具版权 Licensing QT模块版权查询 在 All Modules 中点击进入每个模块&#xff0c;在详细内容中一般有Lisence相关内容。 Licens…

uniapp - 实现卡片式胶囊单选后右上角出现 “√“ 对勾对号选中效果功能,适用于小程序h5网页app全平台通用(一键复制组件源码,开箱即用!)

效果图 uniapp全平台兼容(小程序/h5网页/app)实现点击选择后,右上角出现 √ 对号效果(角标形式展现),功能组件, 改个样式,直接复制使用该组件。 组件源码 在 components 组件文件夹下,随便建立一个 .vue 文件,一键复制下方源码。

DockerFile解析

1. 是什么 Dockerfile是田来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本 1.1 概述 1.2 官网 Dockerfile reference | Docker Documentation 1.3 构建三步骤 1. 编写dockerfile文件 2. docker build命令构建镜像 3. docker run依镜像运…

文本分类任务

文章目录 引言1. 文本分类-使用场景2. 自定义类别任务3. 贝叶斯算法3.1 预备知识3.2 贝叶斯公式3.3 贝叶斯公式的应用3.4 贝叶斯公式在NLP中的应用3.5 贝叶斯公式-文本分类3.6 代码实现3.7 贝叶斯算法的优缺点 4. 支持向量机4.1 支持向量机-核函数4.2 支持向量机-解决多分类4.3…

go学习之流程控制语句

文章目录 流程控制语句1.顺序控制2.分支控制2.1单分支2.2双分支单分支和双分支的四个题目switch分支结构 3.循环控制for循环控制while 和do...while的实现 4.跳转控制语句breakcontinuegotoreturngotoreturn 流程控制语句 介绍&#xff1a;在程序中&#xff0c;程序运行的流程…

【LeetCode75】第三十七题 二叉树中的最长交错路径

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 给我们一棵二叉树&#xff0c;问我们在这棵树里能找到的最长交错路径。最长交错路径就是在二叉树里一左一右一左一右这样走&#xff0c;最…

Windows下搭建Tomcat HTTP服务,发布外网远程访问

文章目录 前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册 2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置 3.公网访问测试4.结语 前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff0…

港联证券|燃气板块午后走高,美能能源涨停,水发燃气大幅拉升

燃气板块21日午后快速拉升&#xff0c;到发稿&#xff0c;美能动力涨停&#xff0c;水发燃气涨超7%&#xff0c;蓝天燃气涨超5%&#xff0c;贵州燃气涨逾4%。 消息面上&#xff0c;受澳大利亚LNG工厂罢工忧虑影响&#xff0c;欧洲基准天然气价格一度大涨18%。 有报导称&#x…

Zenity 简介

什么使 Zenity Zenity 是一个开源的命令行工具&#xff0c;它提供了一种简单的方式来创建图形化的用户界面&#xff08;GUI&#xff09;对话框&#xff0c;以与用户进行交互。它基于 GTK 库&#xff0c;可以在 Linux 和其他 UNIX-like 系统上使用。 Zenity 可以通过命令行或脚…