多态(C++)
多态
- 一、初识多态
- 概念
- “登场”
- 1>. 多态的构成条件
- 2>. 虚函数
- 3>. 虚函数重写(覆盖)
- 4>. 虚函数重写的两个例外
- 1. 协变 一 基类和派生类虚函数返回值类型不同
- 2. 析构函数重写(基类和派生类析构函数名不同)
- 小结
- 二、延伸知识
- 1>. C++11 override和final
- 拓展一最终类
- 2>. 抽象类
- 概念
- 接口继承和实现继承
- 三、原理
- 1>. 虚函数表(也称虚表)
- 引入
- 分析虚表
- 2>. 多态的原理
- 3>. 拓展 一 静态绑定和动态绑定
- 四、单继承和多继承的虚函数表
- 1>. 单继承中的虚函数表
- 2>. 多继承中的虚函数表
- 1. 多继承
- 2. 菱形继承
- 3. 菱形虚拟继承
一、初识多态
概念
概念:去完成某个行为,当不同的对象去完成时会产生出不同的状态。
eg:
买车票:普通成年人买票时,是全价票;学生买票时,是半价票;
“登场”
1>. 多态的构成条件
- 多态是在不同继承关系的类对象,去调用同一函数,产生不同的行为。
- 在继承的继承上,需要:
- 必须通过基类的指针或者引用调用虚函数。
- 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 (覆盖)。
eg: “见见猪跑”

2>. 虚函数
虚函数:被virtual修饰的类成员函数

3>. 虚函数重写(覆盖)
虚函数重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同)。称子类的虚函数重写了父类的虚函数
注意: 重写基类虚函数时,派生类的虚函数可以不加virtual关键字。
eg:

4>. 虚函数重写的两个例外
虚函数重写的要求是派生类虚函数与基类虚函数的返回值类型,函数名,参数类型完全相同(三同)。例外的原因就是不满足三同
1. 协变 一 基类和派生类虚函数返回值类型不同
协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同。
满足协变的条件:返回值类型可以不同,但是返回值必须是父子关系的指针或引用。
test code:
class A
{};
class B : public A
{};class Person
{
public:virtual A* f(){return new A;}
};class Student : public Person
{
public://注意在重写的地方,返回值类型虽然可以不同,但是必须是父子关系,而且同指针或同引用。不能出现基类虚函数返回值类型是父类引用,而派生类重写的虚函数返回类型是派生类的指针virtual B* f(){return new B;}
};
2. 析构函数重写(基类和派生类析构函数名不同)
基类的析构函数为虚函数,则派生类析构函数只要定义,无论是否加virtual关键字,都构成重写。虽然表象函数名不同,但是编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor
test code:
class Person
{
public:virtual ~Person(){cout << "~Person()" << endl;}
};class Student : public Person
{
public:virtual ~Student(){cout << "~Student()" << endl;}
};//只有派生类Student的析构函数重写了Person的析构函数,
//这时delete调用析构时,才能构成多态,保证正确调用析构函数
int main()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;return 0;
}//output:
//~Person()
//~Student()
//~Person()
小结
重载、覆盖(重写)、隐藏(重定义)的对比:

二、延伸知识
1>. C++11 override和final
- override:检查派生类虚函数是否重写了基类某个虚函数,如果没有重写报错
test code:
class Car
{
public:virtual void Drive() {}
};class Benz : public Car
{
public:virtual void Drive() override //ok 完成了重写{cout << "Benz-舒适" << endl;}
};
- final:修饰虚函数,表示该虚函数不能再被重写
test code:
class Car
{
public:virtual void Drive() final{}
};class Benz : public Car
{
public:virtual void Drive() //error 原因:final禁止了重写{cout << "Benz-舒适" << endl;}
};
拓展一最终类
当我们想设计不想被继承的类时,有两种方法
方法1 一一 对应C++98
eg1: 隐藏构造函数,当想要创建A对象时,定义一个静态的成员函数
class A
{
public:static A CreateObj(){return A();}
private:A(){}
};class B : public A
{};int main()
{//B bb; //errA::CreateObj();return 0;
}
eg2:隐藏析构函数,当想要创建A对象new一个,释放时定义一个静态的destructor,即可
class A
{
public:
private:~A(){}
};
class B : public A
{};int main()
{//B bb; //errA* p = new A;return 0;
}
方法2 一一 对应C++11
eg:被final修饰的类,被称为最终类,不能被继承
class A final
{
public:
private:
};
class B : public A
{};
2>. 抽象类
概念
- 纯虚函数:虚函数的后面写上 = 0
- 抽象类(接口类):包含纯虚函数的类。
抽象类不能实例化出对象,派生类继承后也不能,只有重写纯虚函数,派生类才可以实例化对象。规范了派生类必须重写。
test code:
class Car
{
public:virtual void Drive() = 0;
};class Benz : public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};class BMW : public Car
{
public:virtual void Drive(){cout << "BMW-好操控" << endl;}
};int main()
{Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();return 0;
}
接口继承和实现继承
- 实现继承:普通函数继承。派生类继承基类函数,可以使用,继承的是函数实现。
- 接口继承:虚函数的继承。派生类继承的是虚函数的接口,目的是为了重写,达成多态,继承的是接口。
注意:不实现多态就不要把函数定义成虚函数
三、原理
基于vs2019进行模型分析
1>. 虚函数表(也称虚表)
引入
test code:
//计算Base对象的大小
class Base
{
public:virtual void Func(){cout << "Func()" << endl;}private:int _b = 1;
};int main()
{Base b;cout << sizeof(b) << endl;return 0;
}
//output: 8
代码分析:

分析虚表
test code:
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;
}
通过上面的测试代码,发现一下六点:

2>. 多态的原理
上面分析了很久的虚表,以对虚表的介绍为基础,来分析多态的原理。
test code:
class Person
{
public:virtual void BuyTicket(){cout << "买票-全价" << endl;}
};
class Student : public Person
{
public:virtual void BuyTicket(){cout << "买票-半价" << endl;}
};void Func(Person* p)
{p->BuyTicket();
}int main()
{Person ps;Func(&ps);ps.BuyTicket();Student st;Func(&st);return 0;
}
达到多态,有两个条件:一是虚函数覆盖,一个是对象的指针或引用调用虚函数。
通过下面汇编代码的分析,看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象中去找的。不满足多态的函数调用是编译时确认好的。


3>. 拓展 一 静态绑定和动态绑定
- 静态绑定(前期绑定):在程序编译期间确定了程序的行为,也称静态多态。 eg:函数重载
- 动态绑定(后期绑定):是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称动态多态
上一个汇编代码的例子,就很好的解释了静态绑定和动态绑定
四、单继承和多继承的虚函数表
1>. 单继承中的虚函数表
test code:
class Base
{
public:virtual void func1(){cout << "Base::func1()" << endl;}virtual void func2(){cout << "Base::func2()" << endl;}
private:int _b;
};class Derive : public Base
{
public:virtual void func1(){cout << "Derive::func1()" << endl;}virtual void func3(){cout << "Derive::func3()" << endl;}virtual void func4(){cout << "Derive::func4()" << endl;}
private:int _d;
};int main()
{Base b;Derive d;return 0;
}
观察下图中监视窗口,发现派生类的虚函数func3和func4看不见。 原因:编译器的监视窗口隐藏了这个两个函数。

在分析虚表这一小节内容时,在第四小点说到,虚表本质是一个存虚函数指针的指针数组,一般情况这个数组最后放一个nullptr,通过监视窗口查看不了派生类对象d的虚表,下面我们借用nullptr的帮助,使用代码打印出虚表中的函数
PrintVTable_code:打印虚表的代码
注意:这个打印虚表的代码经常崩溃,编译器对虚表的处理不干净,虚表最后没有放nullptr的指针,导致越界。我们只需要清理解决方案,重新编译即可。
typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr VTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << VTable << endl;for (size_t i = 0; VTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, VTable[i]);VFPtr f = VTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}
注意:传参调用PrintVTable的思路
int main()
{Base b;Derive d;//思路:取b、d对象的头4个字节,就是虚表的指针。//以b对象讲解//1.先取b的地址,强转成int*的指针//2.再解引用取值,就取到b对象头4个字节的值,也就是指向虚表的指针//3.再强转成VFPtr*,因为虚表就是一个存VFPtr类型(虚函数指针类型)的数组//4.虚表指针传递给PrintVTable进行打印虚表VFPtr* vTable_b = (VFPtr*)(*(int*)&b);PrintVTable(vTable_b);VFPtr* vTable_d = (VFPtr*)(*(int*)&d);PrintVTable(vTable_d);return 0;
}
上面代码打印出虚表中虚函数的结果分析:

2>. 多继承中的虚函数表
1. 多继承
test code:
class Base1
{
public:virtual void func1(){cout << "Base1::func1()" << endl;}virtual void func2(){cout << "Base1::func2()" << endl;}
private:int _b1 = 1;
};class Base2
{
public:virtual void func1(){cout << "Base2::func1()" << endl;}virtual void func2(){cout << "Base2::func2()" << endl;}
private:int _b2 = 1;
};class Derive : public Base1, public Base2
{
public:virtual void func1(){cout << "Derive::func1()" << endl;}virtual void func3(){cout << "Derive::func3()" << endl;}
private:int _d = 2;
};typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);VFPtr f = vTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}int main()
{Derive d;VFPtr* vTable_b1 = (VFPtr*)(*(int*)&d);PrintVTable(vTable_b1);//(char*)&d 这里一定要注意强转,否则+1,就是加一个Derive的大小VFPtr* vTable_b2 = (VFPtr*)(*(int*)((char*)&d + sizeof(Base1)));PrintVTable(vTable_b2);return 0;
}
多继承测试代码展开分析:

2. 菱形继承
test code:
#include<iostream>
using namespace std;
class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}int _a = 0;
};class B : public A
{
public:virtual void fun1(){cout << "B::fun1()" << endl;}virtual void fun2(){cout << "B::fun2()" << endl;}int _b = 0;
};class C : public A
{
public:virtual void fun1(){cout << "C::fun1()" << endl;}virtual void fun2(){cout << "C::fun2()" << endl;}int _c = 0;
};
class D : public B, public C
{
public:virtual void fun2(){cout << "D::fun2()" << endl;}virtual void fun3(){cout << "D::fun3()" << endl;}
};typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);VFPtr f = vTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}int main()
{D d;VFPtr* vTable_d = (VFPtr*)(*(int*)&d);PrintVTable(vTable_d);C* ptr1 = &d;VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1);PrintVTable(vTable_c);return 0;
}
菱形继承测试代码展开分析:(菱形继承和多继承没有什么大的区别)

3. 菱形虚拟继承
- 只有A类有虚函数
test code:
class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}
public:int _a = 0;
};class B : virtual public A
{
public:int _b = 0;
};class C : virtual public A
{
public:int _c = 0;
};
class D : public B, public C
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
代码内存分析:

- B和C完成对A的虚函数重写
test code:
class A
{
public:virtual void fun1(){cout << "A::fun1()" << endl;}
public:int _a = 0;
};class B : virtual public A
{
public:virtual void fun1(){cout << "B::fun1()" << endl;}
public:int _b = 0;
};class C : virtual public A
{
public:virtual void fun1(){cout << "C::fun1()" << endl;}
public:int _c = 0;
};
class D : public B, public C
{
public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
运行结果:
编译报错
原因:因为整个对象只有A一张虚表是共享的,B要重写,C也要重写。不明确到底重写谁的。
解决办法:让D重写即可。当然B和C的重写并不是没有意义,如果定义B和C类型的对象,单独使用还是有意义的。
- 只有A类有虚函数 + B和C有单独的虚函数
test code:
class A
{
public:virtual void func1(){cout << "A::func1" << endl;}
public:int _a;
};class B : virtual public A
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func2(){cout << "B::func2" << endl;}
public:int _b;
};class C : virtual public A
{
public:virtual void func1(){cout << "C::func1" << endl;}virtual void func2(){cout << "C::func2" << endl;}
public:int _c;
};class D : public B, public C
{
public:virtual void func1(){cout << "D::func1" << endl;}public:int _d;
};int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;return 0;
}
代码内存分析:

- 如果在第三点的基础上,D类也有自己的虚函数,那么将放哪里?
test code:
class A
{
public:virtual void func1(){cout << "A::func1" << endl;}
public:int _a;
};class B : virtual public A
{
public:virtual void func1(){cout << "B::func1" << endl;}virtual void func2(){cout << "B::func2" << endl;}
public:int _b;
};class C : virtual public A
{
public:virtual void func1(){cout << "C::func1" << endl;}virtual void func2(){cout << "C::func2" << endl;}
public:int _c;
};class D : public B, public C
{
public:virtual void func1(){cout << "D::func1" << endl;}virtual void func3(){cout << "D::func3" << endl;}public:int _d;
};typedef void(*VFPtr) (); //函数指针
void PrintVTable(VFPtr vTable[])
{//依次取虚表中的虚函数指针打印并调用。cout << "虚表地址>" << vTable << endl;for (size_t i = 0; vTable[i] != nullptr; i++){printf("第%d个虚函数地址:0X%p,->", i, vTable[i]);VFPtr f = vTable[i];f(); //调用方便看存的是那个函数}cout << endl;
}int main()
{D d;VFPtr* vTable_d = (VFPtr*)(*(int*)&d); //B的虚表PrintVTable(vTable_d);C* ptr1 = &d;VFPtr* vTable_c = (VFPtr*)(*(int*)ptr1); //C的虚表PrintVTable(vTable_c);A* ptr2 = &d;VFPtr* vTable_a = (VFPtr*)(*(int*)ptr2); //A的虚表PrintVTable(vTable_a);return 0;
}
运行结果:由结果得到D类自己的虚函数放在第一张虚表中

相关文章:
多态(C++)
多态 一、初识多态概念“登场”1>. 多态的构成条件2>. 虚函数3>. 虚函数重写(覆盖)4>. 虚函数重写的两个例外1. 协变 一 基类和派生类虚函数返回值类型不同2. 析构函数重写(基类和派生类析构函数名不同) 小结 二、延伸…...
算法leetcode|73. 矩阵置零(rust重拳出击)
文章目录 73. 矩阵置零:样例 1:样例 2:提示:进阶: 分析:题解:rust:go:c:python:java: 73. 矩阵置零: 给定一个 m x n 的矩…...
axios 二次封装
axios 二次封装 基本上每一个项目开发,都必须要二次封装 axios。主要是为了减少重复性工作,不可能每一次发起新请求时,都要重新配置请求域名、请求头 Content-Type、Token 等信息。所以需要把公用的部分都封装成一个函数,每次调用…...
Rust安全之数值
文章目录 数值溢出 数值溢出 编译通过,运行失败 cargo run 1 fn main() {let mut arg std::env::args().skip(1).map(|x| x.parse::<i32>().unwrap()).next().unwrap();let m_i i32::MAX - 1;let a m_i arg;println!("{:?}", a); }thread main panicked…...
4种方法实现html 页面内锚点定位及跳转
使用scrollIntoView进行锚点定位效果 不知道你有没有遇到这样的需求:锚点定位?进入页面某个元素需要出现在可视区?…这一类的需求归根结底就是处理元素与可视区域的关系。我接触了很多前端小伙伴,实现的方式有各种各样的ÿ…...
gitlab配置备忘
版本 gitlab 14.6.2 gitlab备份上传到阿里云oss ### Backup Settings ###! Docs: https://docs.gitlab.com/omnibus/settings/backups.html# gitlab_rails[manage_backup_path] true # gitlab_rails[backup_path] "/var/opt/gitlab/backups"###! Docs: https://…...
基于Centos搭建k8s仓库
系统环境: Red Hat Enterprise Linux 9.1 (Plow) Kernel: Linux 5.14.0-162.6.1.el9_1.x86_64 主机名地址master192.168.19.128node01192.168.19.129node02192.168.19.130 目录 1、关闭防火墙,关闭SElinxu ,开启时间同步服务 2、关…...
浅谈泛在电力物联网发展形态与技术挑战
安科瑞 华楠 摘 要:泛在电力物联网是当前智能电网发展的一个方向。首先,总结了泛在电力物联网的主要作用和价值体现;其次,从智能电网各个环节概述了物联网技术在电力领域的已有研究和应用基础;进而,构思并…...
git reset --soft 用法
git reset --soft 是 Git 命令中的一个选项,它用于取消之前的提交,并将取消的更改保留在暂存区。这允许您重新组织提交历史或将更改合并到一个新的提交中,而不影响暂存区和工作目录中的更改。 这个命令的语法是: git reset --so…...
哪些测试仪器可以用于检测静电中和设备的性能
静电设备性能测试通常需要使用一些专门的仪器来进行。以下是一些常见的静电设备性能测试仪器: 1. 静电电压测试仪:用于测量物体表面的静电电压。它通常可以测量正负电压,并具有高精度和快速响应的特点。 2. 静电电荷仪:用于测量物…...
浅析 GlusterFS 与 JuiceFS 的架构异同
在进行分布式文件存储解决方案的选型时,GlusterFS 无疑是一个不可忽视的考虑对象。作为一款开源的软件定义分布式存储解决方案,GlusterFS 能够在单个集群中支持高达 PiB 级别的数据存储。自从首次发布以来,已经有超过十年的发展历程。目前&am…...
ARM开发,stm32mp157a-A7核PWM实验(驱动蜂鸣器,风扇,马达工作)
1.分析框图; 2.比较捕获寄存器(产生PWM方波); 工作原理: 1、系统提供一个时钟源209MHZ,需要通过分频器进行分频,设置分频器值为209分频; 2、当定时器启动之后,自动重载…...
群狼调研(长沙眼镜店神秘顾客)|消费者需求研究方案
本文由群狼调研(长沙品牌调研)出品,欢迎转载,请注明出处。消费者需求研究方案是在开展研究之前制定的计划,用于指导研究的设计、实施和分析。以下是一个可能的消费者需求研究方案的大致框架: 1. 研究目标和问题: • …...
电脑入门:宽带路由器常见故障排除技巧
宽带路由器在企业网络中的应用是相当广泛的,在运行的过程中出现故障是在所难免的,虽然故障现象多种多样,引起故障发生的原因也不尽相同,但从大体上可以把这些故障分为硬件故障和软件故障,具体来说就是一些网络连接性问题、配置文件选项问题以及网络协议问题等。 由于路由器…...
基于云原生网关的流量防护实践
作者:涂鸦 背景 在分布式系统架构中,每个请求都会经过很多层处理,比如从入口网关再到 Web Server 再到服务之间的调用,再到服务访问缓存或 DB 等存储。在下图流量防护体系中,我们通常遵循流量漏斗原则进行流量防护。…...
开源与云计算:新的合作模式
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
前端需要理解的跨平台知识
混合开发是指使用多种开发模开发App的一种开发模式,涉及到两大类技术:原生 Native、Web H5。原生 Native 主要指 iOS(Objective C)、Android(Java),原生开发效率较低,开发完成需要重…...
《基于 Vue 组件库 的 Webpack5 配置》3.将 CSS 提取到单独的文件
使用 webpack 插件 mini-css-extract-plugin 需要额外安装 npm i mini-css-extract-pluginlatest -D; 同时打包 js 和 css 文件时,可参考 entry 高级用法; package.json 的配置如下 const { VueLoaderPlugin } require(vue-loader); // 可…...
2023CCF图形学启明星计划夏令营感想记录
这篇就是纯日记了,想记录一下参加这个夏令营的感想,中间的一些过程,毕竟这对我来说算是一段难忘的经历。 一、了解到的渠道 我个人是比较喜欢图形渲染的,之前也学过GAMES的课程,然后偶然的一天,GAMES101里…...
如何解决“缺失msvcp110.dll”错误,msvcp110.dll丢失要怎样才能修复
今天,我将为大家分享关于电脑提示msvcp110.dll丢失的3种修复方法。希望这些方法能帮助到正在遇到这个问题的朋友们。 首先,我们来了解一下msvcp110.dll文件的作用。msvcp110.dll是Microsoft Visual C 2010 Redistributable Package的一部分,…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
LeetCode - 394. 字符串解码
题目 394. 字符串解码 - 力扣(LeetCode) 思路 使用两个栈:一个存储重复次数,一个存储字符串 遍历输入字符串: 数字处理:遇到数字时,累积计算重复次数左括号处理:保存当前状态&a…...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
Java多线程实现之Thread类深度解析
Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...
深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...
10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
