牛商网做网站/平面设计培训班学费一般多少
文章目录
- 前言
- 一、多继承的虚函数表
- 二、菱形继承与菱形虚拟继承的虚函数表
- 1.菱形继承
- 2.菱形虚拟继承的虚函数表
- 三、抽象类
- 1.抽象类的概念
- 2.接口继承与实现继承
- 总结
前言
其实关于单继承的虚函数表我们在上一篇文章中已经说过了,就是派生类中的虚表相当于拷贝了一份父类的虚表,然后派生类中将重写的虚函数进行覆盖。如果派生类中也有自己的虚函数,但是并没有与父类构成重写,那么这个虚函数也是在虚表中的,不过不同的是vs2022的监视窗口是不会显示自己的虚函数的。但是我们可以在内存中观测到这个虚函数,并且我们进行验证,使用了一些非正常手段去调用这个函数从而证明这个地址确实是这个虚函数的地址。还有一点需要注意的是虚表是存储在代码段的,即常量区。需要注意我们验证的方法就是打印出每个区域的地址进行对比,从而推测出虚表所在的地方。
一、多继承的虚函数表
我们先看下面这段代码,猜一猜运行结果为多少?
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;
};
int main()
{cout << sizeof(Derive) << endl;return 0;
}
运行结果如下所示:结果是20
那么为什么是20呢?我们画出如下的对象模型
即,Derive这个对象由于继承了Base1和Base2,那么首先他的Base1由于先继承所以在前面,而Base1中有一个虚表指针,还有一个int类型的变量,所以占四个字节,然后内部对齐后,在与Base2和int进行对其,于是最终结果就是20了
这样的样子与我们之前的单继承是十分相似的,派生类是不会单独产生虚表的,派生类都是继承了父类,直接使用派生类中的父类的虚表即可。所以这里有两个虚表
所以我们就更能深刻里面多态的一个条件是父类的指针或者引用了。
如下面所示,是监视窗口中的场景。
可以看到两个基类的func1都被重写了。如下所示
不过这里的问题主要还是在于监视窗口并不可信,因为它这里并没有func3这个虚函数的地址。
根据单继承的思路,我们这里的func3这个虚函数是会存储在某一个虚表中的,那么是存储在Base1还是Base2还是两个都存储呢?
我们可以使用之前的方案,强行找出虚表的地址,进行调用函数来研究
typedef void(*FUNC_PTR)();
void Print_VFT(FUNC_PTR* table)
{for (int i = 0; table[i] != nullptr; i++){printf("[%d]:%p->", i, table[i]);table[i]();}cout << endl;
}
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;
};
int main()
{Derive d;int vft1 = *(int*)&d;//int vft2 = *(int*)((char*)&d + sizeof(Base1));Base2* ptr = &d;int vft2 = *(int*)ptr;Print_VFT((FUNC_PTR*)vft1);Print_VFT((FUNC_PTR*)vft2);return 0;
}
关于上面的代码,我们需要注意的是vft2的值的取法,即Base2中的虚表的地址如何取出来。我们可以自己去计算偏移量从而得出结果,当然我们也可以直接运用切片的特性,直接拿到了地址,然后在类型转换即可
运行结果如下:
可以看到func3的地址其实是存储在Base1的虚表中的。
所以**多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中 **
如上是Derive的虚表模型了,但是我们会发现一个很奇怪的问题。那就是明明两个基类的func1都被重写了,但是他们的地址为什么不一样呢?
所以这里出现了一个很奇怪的问题,虽然他们两个函数的地址不一样,但是最终却调用到同一个函数去了。
按照单继承的理论,这个func1只有一个地址才是比较合理的,毕竟这个func1只生成一份才是最好的。
为什么这里重写了func1,但是Base1和Base2的地址不一样呢?
面对这个问题,网上绝大多数是没有答案的。于是我们最好的办法就是看汇编
上面的问题换言之,就是下面这段代码,两个调用结果一样,但是过程出现了不一样的情况
我们直接进入汇编模式
如上是需要注意的事件,call之前都是在想办法取出虚表中的地址,我们可以直接观测eax的值,可以发现确实是Base1虚表中这个fun1函数的地址。
然后我们继续往深走,即进入call的内部,我们就会进入到一个jmp函数
也就是说,我们call的地址只是一个jmp的地址,一般jmp后面的才是真正的地址
如下所示,果然如此,jmp后面才会真正的调用func1函数
即如下所示的调用关系
我们再来观测后半部分代码(注意:这里我的代码前半部分的代码地址改变了,这里其实只是我的一个小失误操作,不过不影响后序现象)
这里与前面的现象是一样的,我们继续深入调查
在这里我们发现了一些需要注意的问题,这里的jmp的地址首先和前面jmp指令的地址是不一样的,其次jmp后面所跳转的地址也是不一样的。
现在我们只能继续深入调查了。
在这里我们发现,它没有直接像前面的一样直接跳转到函数,而是先对ecx减去8了。
要知道,ecx一般存储的是this指针的值。所以这里是对this指针减去8
减去以后ecx的值发生了变化
然后我们继续进入jmp内部
这个时候我们发现,jmp的地址着不就是Base1虚表中的func1的地址吗?
所以现在就来了一个大折返,直接与前面的一样了
也就是说,这里只是多走了几步
总之,无非就是后半部分多走了很多的代码。但是最终的结果是一样的。
那么编译器究竟为什么要这么做呢?我们知道中间多出来几步中,有一步是让this指针减去8。那么为什么要减去8呢?
我们也许会注意到我们的d的对象模型,ptr2和ptr1正好相差8
我们先想清楚func1是谁的成员函数,其实是Derive的成员函数。而我们想要调用Derive的成员函数,那么这个this指针应该指向的是d对象,ptr1和ptr2就是充当着this指针,而我们现在呢?ptr1指向的恰好就是起始地址,刚好巧了,所以它就不需要动。ptr2指向的是Base2的那一部分。它的功能就是找到对应的虚表,找到对应的函数地址以后,我们也不能直接对其进行call调用,因为实际调用成员函数的时候必须要传ecx的,那么我们就需要将他调整到d的起始位置。
而且我们也注意到了,这里的黄色部分刚好就是把ptr2的值交给了ecx,这里就说明了ecx存储的是this指针
相应的,ptr1的调用和ptr2是类似的,先要去把this指针给处理好
ptr1的好处在于它的this指针本身就是d的起始位置,本身就是正确的,所以这个this指针不需要进行调整,所以直接call函数即可
而ptr2可惜它指向的并非起始位置,所以得先绕个弯子,先把ecx给修正为正确的this指针,才能去调用函数。
这样的话,我们就已经彻底的辨析了下面的代码了,三个的本质调用的是一个函数,不过前两个是多态调用,后面是普通调用。ptr2的调用由于this指针的问题,所以需要进行修正。
int main()
{Derive d;Base1* ptr1 = &d;ptr1->func1();Base2* ptr2 = &d;ptr2->func1();Derive* ptr3 = &d;ptr3->func1();return 0;
}
也就是说,他们的调用可以分为两部分:一部分是传this指针,第二部分就是call地址。
当然我们这里的都是vs2022的行为,不同的编译器可能有不同的效果
那么比如说有没有可能我们可以让多继承中的两个地址是一样的呢?其实是可以的,我们只要调用之前去修正this指针就可以了。因为vs采用的是调用的过程中修正,所以他们的地址只能不一样了。
二、菱形继承与菱形虚拟继承的虚函数表
1.菱形继承
我们先简单的写一个菱形继承,如下所示,下面也刚好包括了虚函数的样例
class A
{
public:virtual void func1(){cout << "A::func1()" << endl;}int _a;
};
class B : public A
{
public:int _b;
};
class C : public A
{
public:int _c;
};
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使用A里面的虚表,C使用A里面的虚表,D使用B和C的虚表。对象模型里面存储的是虚表指针,所以对象模型并未发生改变,改变的只是虚表指针指向的内容,就是虚表。根据单继承和多继承的规则,B里面的虚函数并未构成重写,则直接衔接在A的虚函数表后面。C同理,D中的虚函数则放在B的虚表中,即B中的A虚表
我们可以验证一下
typedef void(*FUNC_PTR)();
void Print_VFT(FUNC_PTR* table)
{for (int i = 0; table[i] != nullptr; i++){printf("[%d]:%p->", i, table[i]);table[i]();}cout << endl;
}
class A
{
public:virtual void func1(){cout << "A::func1()" << endl;}int _a;
};
class B : public A
{
public:virtual void func2(){cout << "B::func2()" << endl;}int _b;
};
class C : public A
{
public:virtual void func3(){cout << "C::func3()" << endl;}int _c;
};
class D : public B, public C
{
public:virtual void func4(){cout << "D::func4()" << endl;}int _d;
};
int main()
{D d;d.B::_a = 1;d.C::_a = 2;d._b = 3;d._c = 4;d._d = 5;int vft1 = *(int*)&d;C* ptr = &d;int vft2 = *(int*)ptr;Print_VFT((FUNC_PTR*)vft1);Print_VFT((FUNC_PTR*)vft2);return 0;
}
可见,确实如此。
但是以上是没有发生重写/覆盖的情况下的。我们可以试一下发生了重写覆盖的情况
如下图所示,是B重写/覆盖了A的情况下,也是很好理解的,其实就是相当于B的虚函数地址覆盖了A的虚函数地址。D并不会在意B里面A的细节,它只关心虚表中的函数是否产生了重写/覆盖。如果是的话则,覆盖即可。没有就往后续
我们继续观察,当A与D产生了重写,但A没有与B产生重写的条件下,可见与我们前面所说的是一致的
当A、B、D都产生了重写的情况如下
当A、B、D和A、C产生了重写如下所示
2.菱形虚拟继承的虚函数表
我们使用与前文类似的代码,不过我们这次使用菱形虚拟继承
class A
{
public:virtual void func1(){cout << "A::func1()" << endl;}int _a;
};
class B : virtual public A
{
public:int _b;
};
class C : virtual public A
{
public:int _c;
};
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都放到了公共部分
此时的按照菱形虚拟继承的内存分配来看是没有什么大问题的,但是当我们B和C同时对A的虚函数进行了重写的时候,由于是菱形虚拟继承。所以都会让A给放到公共部分,实际上是B和C共享的A。两个都一起重写,导致编译器不知道什么该听哪一个的,所以就报错了
主要还是因为B和C都想要去重写这个A,才导致的问题。而如果只是一个菱形继承的话,就不会出现这个问题,因为各自重写各自的即可。
对于上面的情况,我们有两种方案去处理,第一种是只保留一种重写即可
如下所示就是,我们只保留了C的重写,不会发生冲突,所以就不会报错了
第二种方案就是让D在来一个重写,这样的话, 反正无论B和C是否重写,都要听D的重写函数了。
上面的这些原因其实都是因为只有A有一张虚表才导致的。最终上图中的虚表里面最后就只有D的虚函数了。
当然我们或许会以为B和C的重写就没有意义了。其实不是的,当我们想要一个B或者C类对象的时候,他们的重写就有意义了。
上面其实还是菱形虚拟继承中比较简单的情况,事实上菱形虚拟继承是更加复杂的,当我们在B和C里面又添加了一些虚函数,这些虚函数指针又该放哪里呢?都放A里面的虚表吗?
其实不是的,这里B和C又会各自生成一张虚表,它们自己的虚函数存储在他们自己的虚表里面。因为按照一开始的A的虚表是B和C共享的,如果两个都往A的虚表里面塞,其实不太合适的。
class A
{
public:virtual void func1(){cout << "A::func1()" << endl;}int _a;
};
class B : virtual public A
{
public:virtual void func1(){cout << "B::func1()" << endl;}virtual void func2(){cout << "B::func2()" << endl;}int _b;
};
class C : virtual public A
{
public:virtual void func1(){cout << "C::func1()" << endl;}virtual void func2(){cout << "C::func2()" << endl;}int _c;
};
class D : public B, public C
{
public:virtual void func1(){cout << "D::func1()" << endl;}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里面有两个指针,一个是虚基表指针,一个是虚表指针。那么究竟哪个是虚表指针,哪个是虚基表指针呢?我们可以测试一下
其实单纯,看里面的数据,我们大概可以猜测到,第一个是虚表指针,第二个是虚基表指针
对于虚基表里面的内容, 它里面存储的是偏移量,第一个是-4,第二个是18,可见,第一个指针是为了找到该部分的起始位置,第二个指针是为了找到A的部分
那么如果我们给D有自己单独的虚函数呢?D会额外创建虚表吗?其实不会的,因为D完全可以已经存在的虚表就够了。我们可能会以为放入共享的A的虚表,不过如果按照多继承的角度去理解,也有可能会放入B的虚表。
三、抽象类
1.抽象类的概念
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承 。
至于为什么起这个抽象的名字,我们可以理解为,这个类在现实世界中没有实体。所以不能实例化出对象。而且由于派生类继承了抽象类,它里面也包含了纯虚函数,那么它自然也不能实例化出对象了。派生类如果真的想要实例化出对象,我们可以使用重写的方式,这样的话,它里面的这个纯虚函数就被覆盖,就没有纯虚函数了,自然就可以实例化出对象了
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;}
};
void Test()
{Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}
int main()
{Test();return 0;
}
如下是由于包含纯虚函数导致不能实例化出对象的情形
对于抽象类的多态,我们可能更多是应用于如下场景
那么现在有一个问题,Car类有虚表吗?其实Car类甚至都没有实例化出对象,是根本不可能有虚表的。只有Benz和BMW类才有虚表。
其实纯虚函数的作用就是强制了派生类的重写,因为如果不重写的话,要虚函数其实也没有什么其他用处了。
它与override的区别就是,override则是检查派生类中的虚函数是否完成了重写
两者还是有一些差距的,一个是在基类的,一个是在派生类的。
2.接口继承与实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数 。
总结
本篇文章着重讲解了多继承与菱形继承的虚表,以及抽象类的使用等方法。也正如抽象类的名字一样,本节内容确实比较抽象。愿可以为读者带来帮助!
相关文章:

【C++从0到王者】第二十五站:多继承的虚表
文章目录 前言一、多继承的虚函数表二、菱形继承与菱形虚拟继承的虚函数表1.菱形继承2.菱形虚拟继承的虚函数表 三、抽象类1.抽象类的概念2.接口继承与实现继承 总结 前言 其实关于单继承的虚函数表我们在上一篇文章中已经说过了,就是派生类中的虚表相当于拷贝了一…...

老程序员教你如何笑对问题,轻松培养逻辑思考和解决问题的能力
原文链接 老程序员教你如何笑对问题,轻松培养逻辑思考和解决问题的能力 故事发生在一个阳光明媚的午后,我们的主人公,老李,一位拥有十年工作经验的 Python 老程序员,正悠哉地在喝着咖啡。 这时&#x…...

Omni Recover for Mac(专业的iPhone数据恢复软件)
Omni Recover for Mac是一款专业的Mac数据恢复软件,能够帮助用户快速找回被误删除、格式化、病毒攻击等原因造成的文件和数据,包括图片、视频、音频、文档、邮件、应用程序等。同时,Omni Recover for Mac还具有数据备份和清理功能,…...

视频垂直镜像播放,为您的影片带来新鲜感
大家好!在制作视频时,我们常常希望能够给观众带来一些新鲜感和独特的视觉效果。而垂直镜像播放是一个能够让您的影片与众不同的技巧。然而,传统的视频剪辑软件往往无法直接实现视频的垂直镜像播放,给我们带来了一些困扰。现在&…...

十一、MySQL(DQL)聚合函数
1、聚合函数 注意:在使用聚合函数时,所有的NULL是不参与运算的。 2、实际操作: (1)初始化表格 (2)统计该列数据的个数 基础语法: select count(字段名) from 表名; ;统…...

C语言:三子棋小游戏
简介: 目标很简单:实现一个 三子棋小游戏。三子棋大家都玩过,规则就不提及了。本博文中实现的三子棋在对局中,电脑落子是随机的,不具有智能性,玩家的落子位置使用键盘输入坐标。下面开始详细介绍如何实现一…...

JAVA - PO DTO 生成器
PO DTO 生成器 假设你是一个Java 高级程序员,我会提供一些信息,你需要帮我自动生成Java的PO、DTO 对象。 这些信息有着固定的形式,第一行是对象的类名,其后的每一行都是该对象的属性(简称“属性”)。 对于我属性,格式…...

tcpdump
TCPDump是一个用于抓取网络数据包的命令行工具。它可以帮助网络管理员和开发人员分析网络流量、故障排除以及安全问题。下面是一些TCPDump的详细用法: 基本用法: 监听指定网络接口:tcpdump -i eth0通过IP地址过滤:tcpdump host 19…...

数据通信——传输层TCP(可靠传输原理的ARQ)
引言 上一篇讲述了停止等待协议的工作流程,在最后提到了ARQ自动请求重传机制。接下来,我们就接着上一篇的篇幅,讲一下ARQ这个机制 还是这个图来镇楼 ARQ是什么? 发送端对出错的数据帧进行重传是自动进行的,因而这种…...

Compose - 交互组合项
按钮 Button OutLinedButton带外边框、TextButton只是文字、IconButton只是图标形状。 Button(onClick { }, //点击回调modifier Modifier,enabled true, //启用或禁用interactionSource MutableInteractionSource(),elevation ButtonDefaults.elevatedButtonElevation( /…...

【发版公告】Virbox Protector 3.1.3.19051 发版- elf 文件支持导入表保护
深盾安全-软件保护工具 Virbox Protector 3 ( 3.1.3.19051)迎来了版本升级.本次升级支持了 elf 文件导入表保护。 以下是本次 Virbox Protector 发版的主要功能: 新功能 1. ELF格式的程序支持导入表保护(Beta);; 2…...

点云数据做简单的平面的分割 三维场景中有平面,杯子,和其他物体 实现欧式聚类提取 对三维点云组成的场景进行分割
点云分割是根据空间,几何和纹理等特征对点云进行划分,使得同一划分内的点云拥有相似的特征,点云的有效分割往往是许多应用的前提,例如逆向工作,CAD领域对零件的不同扫描表面进行分割,然后才能更好的进行空洞修复曲面重建,特征描述和提取,进而进行基于3D内容的检索,组合…...

C++之std::search应用实例(一百八十九)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...

一文详解 requests 库中 json 参数和 data 参数的用法
在requests库当中,requests请求方法,当发送post/put/delete等带有请求体的请求时,有json和data2个参数可选。 众所周知,http请求的请求体格式主要有以下4种: application/json applicaiton/x-www-from-urlencoded mu…...

Django学习笔记-AcApp端授权AcWing一键登录
笔记内容转载自 AcWing 的 Django 框架课讲义,课程链接:AcWing Django 框架课。 AcApp 端使用 AcWing 一键授权登录的流程与之前网页端的流程一样,只有申请授权码这一步有一点细微的差别: 我们在打开 AcApp 应用之后会自动向 AcW…...

如何在小红书进行学习直播
诸神缄默不语-个人CSDN博文目录 因为我是从B站开始的,所以一些直播常识型的东西请见我之前写的如何在B站进行学习直播这一篇。 本篇主要介绍一些小红书之与B站不同之处。 小红书在手机端是可以直接点击“”选择直播的。 文章目录 1. 电脑直播-小红书直播软件2. 电…...

F5服务器负载均衡能力如何?一文了解
但凡知道服务器负载均衡这个名词的,基本都知道 F5,因为负载均衡是 F5 的代表作,换句话来说,负载均衡就是由 F5 发明的。提到F5服务器负载均衡能力如何?不得不关注F5提出的关于安全、网络全面优化的解决方案,…...

Ubuntu18.04安装docker-io
1. 安装docker 1.1 网上一搜,全是更新仓库、下载依赖、添加docker的gpg密钥、添加docker仓库、安装docker-ce的步骤,但是在安装docker-ce时却提示“package "docker-ce" has no installation candidate”,就很迷。 1.2 安装docke…...

代码随想录笔记--栈与队列篇
目录 1--用栈实现队列 2--用队列实现栈 3--有效的括号 4--删除字符串中的所有相邻重复项 5--逆波兰表达式求值 6--滑动窗口的最大值 7--前k个高频元素 1--用栈实现队列 利用两个栈,一个是输入栈,另一个是输出栈; #include <iostrea…...

【力扣】55. 跳跃游戏 <贪心>
【力扣】55. 跳跃游戏 给一个非负整数数组 nums ,最初位于数组的第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false 。 示例 1…...

在iPhone 15发布之前,iPhone在智能手机出货量上占据主导地位,这对安卓来说是个坏消息
可以说这是一记重拳,但似乎没有一个有价值的竞争者能与苹果今年迄今为止的智能手机出货量相媲美。 事实上,根据Omdia智能手机型号市场跟踪机构收集的数据,苹果的iPhone占据了前四名。位居榜首的是iPhone 14 Pro Max,2023年上半年…...

题目:2620.计数器
题目来源: leetcode题目,网址:2620. 计数器 - 力扣(LeetCode) 解题思路: 定义两个全局变量,一个判断 n 是否改变,另一个记录上一次出现的数。 解题代码: /*** par…...

【MySQL】MySQL系统变量(system variables)列表(SHOW VARIABLES 的结果例)
文章目录 【MySQL】MySQL系统变量(system variables)列表(SHOW VARIABLES 的结果例)SHOW VARIABLES 的结果例参考 【免责声明】文章仅供学习交流,观点代表个人,与任何公司无关。 编辑|SQL和数据库技术(ID:S…...

【多AZ】浅述云计算多az
多AZ(Availability Zone)是云计算中一种重要的容灾和冗余策略,它通过在不同的地理位置或不同的设备上存储数据副本以及网络切换策略,以保证在单个设备或地理位置发生故障时,云加计算集群仍然能够提供服务。 多AZ的特点…...

Element浅尝辄止13:Collapse 折叠面板
通过折叠面板收纳内容区域 1.如何使用? 可同时展开多个面板,面板之间不影响 <el-collapse v-model"activeNames" change"handleChange"><el-collapse-item title"一致性 Consistency" name"1">&l…...

51 单片机包含头文件 BIN51.H 直接写二进制数字
51 单片机包含头文件 BIN51.H 直接写二进制 最近学习 51 单片机,写代码的时候感觉用二进制的形式更直观。就是每次都需要宏定义,太麻烦。干脆把所有的8位二进制数字全部用宏定义写出来,放进头文件,下次使用直接包含头文件就行。 …...

php环境搭建步骤(与资源配套使用版)
1.将phpEnv.zip下载到D盘下 2.解压到当前文件夹 3.找到Apache24下的bin目录,执行cmd操作,回车。 4.在cmd中执行代码 Httpd -k install -n “Apache24” 4.使用winR键打开运行,输入services.msc ,回车,进入服务 …...

java 集合处理:
// 1 数组转map public static void main(String[] args) {String backendIdStr"[\"backend-mvj05upv7yc\",\"backend-mvj055qvric\",\"backend-mvj04hlutx4\"]";String[] backendIdList JsonUtil.asObject(backendIdStr, String[].c…...

算法训练第五十二天
718. 最长重复子数组 - 力扣(LeetCode) class Solution { public:int findLength(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>> dp(nums1.size() 1,vector<int>(nums2.size() 1,0));int res…...

LeetCode——回溯篇(三)
刷题顺序及思路来源于代码随想录,网站地址:https://programmercarl.com 目录 46. 全排列 47. 全排列 II 332. 重新安排行程 51. N 皇后 37. 解数独 46. 全排列 给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任…...