【C++】多态(详解)

前言:今天学习的内容可能是近段时间最难的一个部分的内容了,C++的多态,这部分内容博主认为难度比较大,各位一起慢慢啃下来。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • 什么是多态
  • 什么是虚函数
  • 多态的定义及实现
    • 1.多态构成的条件
    • 2. 虚函数的重写
    • 3.协变
    • 4.析构函数的重写
    • 5. override 和 final
    • 6. 重载、覆盖(重写)、隐藏(重定义)的对比
  • 抽象类
  • 多态的原理
    • 虚函数表
    • 虚表指针的内容
    • 引用和指针如何实现多态
    • 动态绑定与静态绑定
    • 虚函数表存放位置
  • 单继承和多继承中的虚拟表


什么是多态

多态的概念:多态(polymorphism)是C++中面向对象编程的一个重要概念,它指的是同一种消息(方法调用)在不同的对象上产生不同的行为。这种特性使得程序设计更加灵活,提高了代码的可扩展性和可维护性。(通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态)。


什么是虚函数

在C++中,虚函数是一种特殊的成员函数,用于实现多态性。通过将基类的成员函数声明为虚函数,可以在派生类中对该函数进行重写。当通过基类指针或引用调用虚函数时,实际调用的是相应派生类中的函数。

虚函数的声明和定义如下:

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

在基类的函数声明前加上virtual关键字,就将该函数声明为虚函数。派生类可以选择重写基类的虚函数,使用相同的函数签名来定义派生类中的函数:

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

注意,在派生类中重写虚函数时,可以使用override关键字显式标注,以增强代码可读性。

使用虚函数时,需要通过基类指针或引用来调用虚函数。根据指针或引用所指向的具体对象类型,调用的将是相应对象的虚函数实现。

例如:

Base* base = new Derived();
base->foo(); // 调用的是Derived的foo()函数

在上述示例中,通过基类指针base指向Derived对象,并调用虚函数foo(),实际调用的是Derived类中的foo()函数。

总结来说,虚函数实现了在基类中声明一个函数,使其可以在派生类中被重写,并能通过基类指针或引用调用派生类对象的对应实现,从而实现多态性。


多态的定义及实现

1.多态构成的条件

C++中的多态性是指在相同的函数签名下,通过基类指针或引用调用不同的派生类对象时,能够实现不同的行为。在C++中,实现多态性需要满足以下三个条件:

  1. 存在继承关系:多态性需要至少有一个基类和一个或多个派生类。
  2. 基类函数为虚函数:基类中的函数必须声明为虚函数,以便在派生类中进行重写。子类父类都有这个虚函数 + 子类的虚函数与父类虚函数的函数名/参数/返回值 都相同 。
  3. 使用基类指针或引用:通过基类指针或引用来调用派生类对象的函数,实现函数的动态绑定。

下面是一个示例代码,演示了多态性的实现:

class Animal //基类
{
public:virtual void sound() {cout << "Animal is making a sound." << endl;}
};class Cat : public Animal//派生类 
{
public:virtual void sound() {cout << "Cat is meowing." << endl;}
};class Dog : public Animal//派生类  
{
public:virtual void sound() {cout << "Dog is barking." << endl;}
};void func(Animal& s)//接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数
{s.sound();
}void test1()
{Animal s1;Cat s2;Dog s3;func(s1);func(s2);func(s3);
}void test2()
{Animal* animal1 = new Animal();//当基类的指针指向派生类的时候,只能操作派生类中从基类中继承过来的数因据和基类自身的数据Animal* animal2 = new Cat();Animal* animal3 = new Dog();animal1->sound(); // Animal is making a sound.animal2->sound(); // Cat is meowing.animal3->sound(); // Dog is barking.delete animal1;delete animal2;delete animal3;}
int main() 
{test1();test2();return 0;
}

在上面的代码中,Animal类是基类,Cat和Dog类是派生类。基类Animal中的sound函数被声明为虚函数,而派生类Cat和Dog中重写了该函数。通过使用基类指针animal1、animal2和animal3,分别指向不同的派生类对象,实现了多态性。
注: 在test1中接受对象为父类的指针或者引用,你传递的是父类就调用父类的函数,传递的是子类就调用子类的函数。
在这里插入图片描述


2. 虚函数的重写

在C++中,虚函数的重写是指在派生类中对基类中已有的虚函数进行重新定义。通过重写虚函数,派生类可以改变基类中的函数实现,使其符合派生类的特定需求。

虚函数的重写要求派生类中的函数具有与基类中虚函数完全相同的函数签名(即函数名、参数列表和返回类型都一致)。可以使用override关键字显式标注派生类中的函数,以增强代码的可读性和可靠性。在刚刚的例子中我们通过派生类的Dog和Cat重写了基类中的虚函数sound


3.协变

在C++中,协变(covariant)指的是派生类中重写虚函数的返回类型与基类中的虚函数返回类型具有相关性。换句话说,派生类中重写的虚函数可以返回基类函数返回类型的派生类型。

在早期的C++标准中,派生类中重写虚函数的返回类型必须与基类函数的返回类型完全相同。但是在C++11标准引入了协变的概念,对于返回类型是指针或引用的虚函数,允许派生类中的返回类型是基类返回类型的派生类型。

:协变是子类虚函数与父类虚函数返回值类型不同,但子类和父类的返回值类型也必须是父子关系指针和引用。

以下是一个示例:

class Animal {
public:virtual Animal* clone() {cout << "Animal clone" << endl;return new Animal;}
};class Dog : public Animal 
{
public:virtual Dog* clone() override {cout << "Dog clone" << endl;return new Dog;}
};int main() {Dog s1;Animal* s2 = s1.clone();//基类接受派生类的虚函数的返回值构造对象return 0;
}

在这个例子中,基类Animal有一个虚函数clone,返回一个指向基类的指针。派生类Dog重写了clone函数,并返回一个指向派生类Dog的指针。

当通过基类指针调用clone函数时,根据派生类的类型,会返回相应的指针类型。也就是说,通过协变,Dog*指针会被正确返回。

总结起来,协变允许派生类中的虚函数返回类型与基类中的虚函数返回类型具有相关性,使得处理继承关系时更加灵活和准确。


4.析构函数的重写

在C++中,析构函数(Destructor)也可以被重写。重写析构函数是为了在派生类中定义自己的清理操作。

基类的析构函数通常应该声明为虚函数,以确保正确地调用派生类的析构函数。这是因为当使用基类指针或引用指向派生类对象时,通过基类指针或引用删除对象时,应该调用派生类的析构函数来释放派生类对象的资源。

以下是一个示例:

#include <iostream>
using namespace std;class Animal {
public:virtual ~Animal() {cout << "Animal destructor" << endl;}
};class Dog : public Animal {
public:virtual ~Dog() override {cout << "Dog destructor" << endl;}
};int main() {Animal* animal = new Dog();//先调用子类的析构,在调用父类的析构delete animal;return 0;
}

在这个例子中,基类Animal的析构函数被声明为虚函数。派生类Dog重写了析构函数。

当通过基类指针删除派生类对象时,会正确调用派生类的析构函数。也就是说,首先调用派生类的析构函数,然后调用基类的析构函数。

输出结果为:

Dog destructor
Animal destructor

这表明析构函数按照派生类到基类的顺序被调用。

总结起来,C++中的析构函数可以被重写,在派生类中定义自己的清理操作。为了确保正确地调用派生类的析构函数,基类的析构函数通常应该声明为虚函数。


5. override 和 final

在C++11标准中,overridefinal是两个特殊的关键字,用于修饰成员函数。

  1. override关键字用于表示覆盖基类的虚函数。在C++中,当一个派生类的成员函数与基类的虚函数具有相同的名称和参数列表时,可以使用override关键字显式地告诉编译器这是一个覆盖函数。这样做的好处是可以提醒开发者在派生类中是否正确地覆盖了基类的虚函数。如果函数签名不匹配,编译器会给出错误提示。例如:
class Base {
public:virtual void foo() const;
};class Derived : public Base {
public:void foo() const override;  // override关键字表示覆盖基类的虚函数
};
  1. final关键字用于表示禁止派生类进一步覆盖函数。在C++中,可以通过在基类的虚函数后面加上final关键字来禁止派生类进一步覆盖该函数。这样做的好处是可以防止派生类无意中修改这个函数的行为。例如:
class Base {
public:virtual void foo() const final;  // final关键字表示禁止派生类进一步覆盖该虚函数
};class Derived : public Base {
public:// 下面的代码会导致编译错误,因为foo()函数被标记为final,无法再被派生类覆盖// void foo() const;
};

总之,overridefinal关键字是在C++11中引入的,用于增强对虚函数覆盖的控制。override关键字表示派生类覆盖基类的虚函数,final关键字表示禁止派生类进一步覆盖函数。


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

在C++中,有三种不同的函数特性:重载、覆盖(重写)和隐藏(重定义),它们的区别如下:

  1. 重载(Overload):重载是指在同一个作用域内,根据函数的参数类型和/或数量的不同,可以定义多个同名函数。重载函数在调用时根据传入的参数类型和/或数量来决定使用哪个函数。重载函数可以在同一个类中或者不同的类中定义。例如:
void foo(int x);
void foo(float x);
  1. 覆盖(重写,Override):覆盖是指派生类中的函数覆盖了基类中的虚函数,实现了多态性。派生类中的函数必须具有和基类中虚函数相同的名称、参数列表和返回类型,而且在基类中该虚函数必须被声明为virtual。在运行时,根据对象的实际类型来确定使用哪个函数。例如:
class Base {
public:virtual void foo();
};class Derived : public Base {
public:void foo() override;
};
  1. 隐藏(重定义,Hide):隐藏是指派生类中的函数屏蔽了基类中的同名函数,不具有多态性。派生类中的函数必须具有和基类中被隐藏的函数相同的名称,但是参数列表和返回类型可以不同。在编译时,根据对象的静态类型来确定使用哪个函数。例如:
class Base {
public:void foo(int x);
};class Derived : public Base {
public:void foo(float x);
};

总结:

  • 重载是根据函数的参数类型和/或数量来决定使用哪个函数,主要是静态多态性。
  • 覆盖是指派生类中的函数覆盖了基类中的虚函数,实现了动态多态性。
  • 隐藏是指派生类中的函数屏蔽了基类中的同名函数,不具有多态性。
    在这里插入图片描述

抽象类

在C++中,抽象类是一个不能被直接实例化的类。它只能作为其他类的基类来派生出新的类。抽象类包含至少一个纯虚函数,也可以包含非纯虚函数。

纯虚函数是一个没有实现的虚函数,它通过在函数声明的末尾使用 “= 0” 来指定。纯虚函数的存在使得抽象类无法被实例化,因为任何一个派生类都必须实现所有纯虚函数,才能被实例化。

抽象类主要用于定义公共的接口,而具体的实现则由派生类来完成。它可以作为一种设计工具,用于实现多态性和封装性。在实际应用中,抽象类常常作为基类被其他具体类继承使用。

使用C++中的抽象类需要以下步骤:

  1. 创建一个抽象类:使用class关键字定义一个类,并在需要的成员函数前声明成纯虚函数。至少有一个成员函数是纯虚函数,用于使类成为抽象类。
class AbstractClass {
public:virtual void pureVirtualFunction() = 0; // 纯虚函数virtual void virtualFunction() { // 非纯虚函数// 具体实现}
};
  1. 派生一个具体类:从抽象类派生一个具体的子类,该子类必须实现抽象类中的所有纯虚函数。
class ConcreteClass : public AbstractClass {
public:void pureVirtualFunction() override {// 实现纯虚函数}
};
  1. 实例化具体类:通过具体类实例化对象,可以直接调用抽象类中定义的非纯虚函数,或者通过指针或引用调用纯虚函数。
ConcreteClass obj;
obj.virtualFunction();AbstractClass* ptr = new ConcreteClass();
ptr->pureVirtualFunction();
delete ptr;

通过使用抽象类,可以定义一个通用接口,而具体的实现则由派生类完成。这样可以提高代码的可维护性,支持多态性,并且遵循面向对象的封装性原则。


多态的原理

虚函数表

C++中的虚函数表(Virtual Function Table,简称vtable)是用于实现多态的一种机制。每个含有虚函数的类都会有一个对应的虚函数表,用于存储该类的虚函数的地址。

虚函数表是一个由函数指针组成的数组,每个函数指针指向相应的虚函数的地址。当一个对象被创建后,一个指向该对象对应的虚函数表的指针(通常称为虚表指针,vptr)会被添加到对象的内存布局中。

当通过基类指针或引用调用虚函数时,编译器会将其替换为通过虚函数表来调用相应的虚函数。具体过程如下:

  1. 编译器根据对象的类型找到它的虚函数表。
  2. 根据函数在虚函数表中的位置索引,调用相应的虚函数。

通过虚函数表,C++实现了运行时多态性,允许在运行时根据对象的实际类型来调用相应的虚函数,而不是根据指针或引用的静态类型来调用相应的函数。这为面向对象的程序设计提供了灵活性和可扩展性。

下面我看看一个面试题:
在这里插入图片描述
这里我们直接说结果:在这里插入图片描述

为什么是8呢?因为我们刚刚提到的虚函指针在这里出现了,我们去监视窗口看看,是不是真的有这个虚函数指针的存在。
在这里插入图片描述

结合刚刚的例子,这里就充分解释了,为什么这对象b中会是8个字节了,当一个对象被创建后,一个指向该对象对应的虚函数表的指针(通常称为虚表指针,vptr)会被添加到对象的内存布局中。


虚表指针的内容

话不多说先看一个例子:
在下图中我们会发现,你创建的对象中,第一个存的就是虚表指针的地址,且发现我们查看虚表指针的地址会发现,虚函数的地址依次存储在该虚表中。

在这里插入图片描述


既然这样,我们接着去派生类中查看他的虚表指针有什么不同,如下图所示:

  1. 基类Animal对象s1和派生类Cat对象s2虚表是不一样的,这里我们发现sound完成了重写,所以s2的虚表中存的是重写的Animal::sound,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法
  2. 另外fn继承下来后是虚函数,所以放进了虚表,f1也继承下来了,但是不是虚函数,所以不会放进虚表。
  3. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  4. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  5. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
    在这里插入图片描述

引用和指针如何实现多态

我们之前提到过多态,可以通过传过来的基类的对象或者派生类的对象,来调用其对应的虚函数,但是他是如何识别的呢?
在这里插入图片描述
:这里我们在强调一下,你无论对基类取地址,还是父类取地址,如果有虚函数,那么这个地址都是他虚函数表指针的地址。

这里我们就可以分析,当我们传递基类的对象地址时,就会去基类的虚表指针中去调用对应的虚函数。当我们传递派生类对象的地址时,会将派生类的内容切割掉(切片),然后去调用其虚表指针的地址,然后再去调用对应的虚函数。


动态绑定与静态绑定

在C++中,动态绑定(dynamic binding)和静态绑定(static binding)是两种不同的绑定方式,它们是实现多态性的关键。

静态绑定是在编译时确定函数调用的地址。当使用对象的指针或引用调用函数时,编译器会根据指针或引用的类型来确定调用的函数。这种绑定方式是静态的,因为调用的函数在编译时就已经确定了。静态绑定通常用于非虚函数。

动态绑定是在运行时确定函数调用的地址。当使用对象的指针或引用调用虚函数时,编译器会在运行时根据对象的实际类型来确定调用的函数。这种绑定方式是动态的,因为调用的函数直到运行时才能确定。动态绑定实现了多态性,因为可以通过基类的指针或引用调用派生类的成员函数。动态绑定通常用于虚函数。

动态绑定通过虚函数表(virtual function table)来实现。每个带有虚函数的对象都有一个虚函数表,其中存储了虚函数的地址。当调用虚函数时,通过对象的指针或引用访问虚函数表,并根据对象的实际类型调用正确的函数。

总结起来,静态绑定在编译时确定函数调用的地址,而动态绑定在运行时根据对象的实际类型确定函数调用的地址,实现了多态性。在使用函数时,如果希望实现多态性,需要使用虚函数和动态绑定。


虚函数表存放位置

在 C++ 中,虚函数表的存放位置通常是在 可执行目标文件 的 只读数据段 ( .rodata )1。具体来说,虚函数表指针( vptr )存储在对象实例的内存中,而虚函数表本身则存储在可执行文件的只读数据段中。这意味着直到程序启动并加载可执行文件时,虚函数表的地址才会确定。

举个例子验证一下:

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
private:int a;
};void func()
{}int main()
{Base b1;Base b2;static int a = 0;int b = 0;int* p1 = new int;const char* p2 = "hello world";printf("静态区:%p\n", &a);printf("栈:%p\n", &b);printf("堆:%p\n", p1);printf("代码段:%p\n", p2);printf("虚表:%p\n", *((int*)&b1));//虚表的地址是存放在类对象的头4个字节上因此我们对其进行强转,取地址就会得到虚表的位置了,printf("虚函数地址:%p\n", & Base::func1);printf("普通函数:%p\n", func);
}

如下图所示,我们会发现,虚表的地址和代码段的地址十分相近,因此我们可以得出虚表存放在代码段这个位置,和我们前面的结论相似。
在这里插入图片描述


单继承和多继承中的虚拟表

在C++中,每个类都有一个虚函数表(virtual function table),用于存储该类的虚函数的地址。当一个类中定义了虚函数时,编译器会为该类创建一个虚函数表,并将虚函数表的地址存储在对象的内存布局中。当通过指针或引用访问对象的虚函数时,编译器会根据对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

对于单继承关系,每个类只有一个虚函数表。当子类继承父类时,子类会继承父类的虚函数表,并在自己的虚函数表中添加自己的虚函数。当通过子类的指针或引用访问虚函数时,编译器会根据子类对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

对于多继承关系,每个类都有自己的虚函数表。当一个类通过多个父类进行多继承时,每个父类会有自己的虚函数表,并在子类的内存布局中存储这些虚函数表的地址。在访问虚函数时,编译器会根据对象的内存布局中存储的虚函数表的地址,找到对应的虚函数并调用。

需要注意的是,多继承中可能会出现虚函数表的冲突或者大小不一致的问题,编译器会根据不同的实现采取不同的解决方案来处理这些问题。

:每一个虚函数都会放到虚表里面,但是有的编译器并不会显示一些虚函数!!!


好啦,今天的内容就到这里啦,下期内容预告搜索树的学习与模拟实现.


结语:进阶的内容有点繁杂,大家一起加油呐!。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

操作系统真象还原:编写硬盘驱动程序

第13章-编写硬盘驱动程序 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 13.1 硬盘及分区表 13.1.1 创建从盘及获取安装的磁盘数 要实现文件系统&#xff0c;必须先有个磁盘介质&#xff0c;虽然咱们己经有个虚拟磁盘 hd60M.img&#xff0c;但它只…

力扣67 二进制求和

文章目录 1. 题目链接2. 题目代码3.感受 1. 题目链接 二进制求和 2. 题目代码 class Solution { public:string addBinary(string a, string b) {vector<int> stringA;vector<int> stringB;int lengthOfA a.length();int lengthOfB b.length();for(int subscrip…

OceanBase Meetup北京站|跨行业应用场景中的一体化分布式数据库:AI赋能下的探索与实践

随着业务规模的不断扩张和数据处理需求的日益复杂化&#xff0c;传统数据库架构逐渐暴露出业务稳定性波动、扩展性受限、处理效率降低以及运营成本高等一系列问题。众多行业及其业务场景纷纷踏上了数据库现代化升级之路。 为应对这些挑战&#xff0c;7月6日&#xff0c;OceanB…

专注于文件夹加密和保护的免费软件

一、简介 1、这是一款专注于文件夹加密和保护的免费软件。允许用户为重要的文件或文件夹设置密码&#xff0c;从而防止未经授权的访问。软件提供了隐藏、锁定、只读等多种保护模式&#xff0c;用户可以根据需要选择适合的模式来保护文件。除了基本的加密功能外&#xff0c;它还…

【java计算机毕设】陪诊师管理系统java MySQL springboot vue3 Maven源码 代码+文档PPT

目录 1项目功能 2项目介绍 3项目地址 1项目功能 【java计算机毕设】陪诊师管理系统java MySQL springboot vue3 Maven项目设计源码代码万字文档ppt 2项目介绍 系统功能&#xff1a; vue3陪诊师管理系统。 该平台采用了前后端分离技术&#xff0c;SpringBoot和VUE3框架&…

告别熬夜改稿:AI降重工具让论文降重变得轻松又有趣

已经天临五年了&#xff0c;大学生们还在为论文降重烦恼……手动降重确实是个难题&#xff0c;必须要先付点小经费去靠谱的网站查重&#xff0c;再对着红字标注去改&#xff0c;后面每一次的论文呢查重结果都像赌//博&#xff0c;谁也不知道明明是同一篇文章&#xff0c;第二次…

Halcon 曲线追踪

Halcon 曲线追踪&#xff08;边缘检测、xld分割、xld筛选、线段合并&#xff09; 图片数据与程序 链接&#xff1a;https://pan.baidu.com/s/1feGOa0A7dvCeBjQivr6TvA 提取码&#xff1a;f2ws 原图 起点终点方向 * 1.加载图片 ********************************************…

Python处理异常用操作介绍

Python中的异常处理主要用于捕获和处理程序运行过程中出现的错误。 在编写Python程序时&#xff0c;我们经常会遇到各种错误&#xff0c;如语法错误、运行时错误等。为了确保程序的稳定性和健壮性&#xff0c;我们需要对可能出现的错误进行捕获和处理。本文将介绍Python中常用的…

[笔记] 卷积 - 02 滤波器在时域的等效形式

1.讨论 这里主要对时域和频域的卷积运算的特征做了讨论&#xff0c;特别是狄拉克函数的物理意义。 关于狄拉克函数&#xff0c;参考这个帖子&#xff1a;https://zhuanlan.zhihu.com/p/345809392 1.狄拉克函数提到的好函数的基本特征是能够快速衰减&#xff0c;对吧&#xf…

软件功能测试基础知识大揭秘,功能测试报告就找专业软件测评机构

软件功能测试是以软件产品的需求规格为基础&#xff0c;通过对软件功能的逐个测试&#xff0c;验证软件是否符合需求规格&#xff0c;是否能够正常执行各项功能操作。对于软件产品而言&#xff0c;功能测试是一项至关重要的工作&#xff0c;它能够发现软件中存在的功能缺陷、错…

多微信运营管理方案

微信作为一款社交通讯软件&#xff0c;已经成为人们日常生活中不可缺少的工具。不仅个人&#xff0c;很多企业都用微信来联系客户、维护客户和营销&#xff0c;这自然而然就会有很多微信账号、手机也多&#xff0c;那管理起来就会带来很多的不便&#xff0c;而多微信私域管理系…

softmax从零开始实现

softmax从零开始实现 代码结果 代码 import numpy as np import torch import torchvision import torchvision.transforms as transforms from torch.utils import data# H,W,C -> C,H,W mnist_train torchvision.datasets.FashionMNIST(root"./data", trainTr…

java静态代理-被代理对象,代理对象的概念(图+代码解释)

案例是老师类&#xff0c;这个老师生病请假了&#xff0c;需要请另外一个老师临时帮忙&#xff0c;这个过来帮忙的老师就是代理对象&#xff0c;生病的老师就是被代理对象&#xff0c;其中我们需要代理对象和被代理对象都implement这个ITeacherDao接口&#xff0c;实现里面的te…

8款你不一定知道的良心软件!

AI视频生成&#xff1a;小说文案智能分镜智能识别角色和场景批量Ai绘图自动配音添加音乐一键合成视频https://aitools.jurilu.com/我们使用一些流行的软件的时候&#xff0c;往往会忽略一些功能非常强大的软件&#xff0c;因为这些软件的众 多&#xff0c;都因为看不见而丢失&a…

udp发送数据如果超过1个mtu时,抓包所遇到的问题记录说明

最近在测试Syslog udp发送相关功能&#xff0c;测试环境是centos udp头部的数据长度是2个字节&#xff0c;最大传输长度理论上是65535&#xff0c;除去头部这些字节&#xff0c;可以大概的说是64k。 写了一个超过64k的数据(随便用了一个7w字节的buffer)发送demo&#xff0c;打…

从百数教学看产品设计:掌握显隐规则,打造极致用户体验

字段显隐规则允许通过一个控件&#xff08;如复选框、单选按钮或下拉菜单&#xff09;来控制其他控件&#xff08;如文本框、日期选择器等&#xff09;和标签页&#xff08;如表单的不同部分&#xff09;的显示或隐藏。 这种规则通常基于用户的选择或满足特定条件来触发&#…

龙迅 国产原装 低成本高性能转换器 Type-C with 2lane@8.1Gbps/lane 4K60

2.一般说明 LT8711UXE1是一款高性能的Type-C/DP1.2至HDMI2.0转换器&#xff0c;设计用于将USBType-C源或DP1.2源连接至HDMI2.0收发器。该LT8711UXE1集成了一个DP1.2兼容接收器&#xff0c;和一个HDMI2.0兼容发射器。此外&#xff0c;还包括用于CC通信的两个CC控制器&#xff0c…

红酒与建筑:品味历史与艺术的交汇

在时间的长河中&#xff0c;红酒与建筑都是人类智慧的结晶&#xff0c;它们各自承载着历史的厚重与艺术的韵味。当这两者交汇时&#xff0c;仿佛是一场穿越时空的对话&#xff0c;将我们带入一个既古老又现代、既深沉又温柔的世界。今天&#xff0c;就让我们一起走进这个奇妙的…

PMP报考条件是什么?很多人都没读懂...

最近正值8月份考试报名期&#xff0c;想计划考8月份考试的宝子可以准备起来了&#xff0c;下面是报名时间和考试安排 8月考试时间安排&#xff1a; &#x1f449;报名时间在7.9日—12日 &#x1f449;考试时间在8.31日&#xff08;周六&#xff09; 一、PMP报名条件是什么&am…

炎黄数智人:万科集团——智能催收专员‘崔筱盼’,引领财务管理数字化转型

在数字化时代的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术的飞速发展正深刻改变着商业世界的面貌。万科集团&#xff0c;作为中国房地产行业的翘楚&#xff0c;一直致力于探索和实践最前沿的科技创新。此次&#xff0c;万科集团推出的数字员工“崔筱盼”&#…