【C++从0到王者】第二十五站:多继承的虚表

news2024/9/20 8:10:46

文章目录

  • 前言
  • 一、多继承的虚函数表
  • 二、菱形继承与菱形虚拟继承的虚函数表
    • 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

image-20230828164831224

那么为什么是20呢?我们画出如下的对象模型

即,Derive这个对象由于继承了Base1和Base2,那么首先他的Base1由于先继承所以在前面,而Base1中有一个虚表指针,还有一个int类型的变量,所以占四个字节,然后内部对齐后,在与Base2和int进行对其,于是最终结果就是20了

image-20230828165053500

这样的样子与我们之前的单继承是十分相似的,派生类是不会单独产生虚表的,派生类都是继承了父类,直接使用派生类中的父类的虚表即可。所以这里有两个虚表

所以我们就更能深刻里面多态的一个条件是父类的指针或者引用了。

如下面所示,是监视窗口中的场景。

image-20230828165912026

可以看到两个基类的func1都被重写了。如下所示

image-20230828171257689

不过这里的问题主要还是在于监视窗口并不可信,因为它这里并没有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中的虚表的地址如何取出来。我们可以自己去计算偏移量从而得出结果,当然我们也可以直接运用切片的特性,直接拿到了地址,然后在类型转换即可

运行结果如下:

image-20230828173942134

可以看到func3的地址其实是存储在Base1的虚表中的。

所以**多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中 **

image-20230828174522144

如上是Derive的虚表模型了,但是我们会发现一个很奇怪的问题。那就是明明两个基类的func1都被重写了,但是他们的地址为什么不一样呢?

image-20230828174708860

所以这里出现了一个很奇怪的问题,虽然他们两个函数的地址不一样,但是最终却调用到同一个函数去了。

按照单继承的理论,这个func1只有一个地址才是比较合理的,毕竟这个func1只生成一份才是最好的。

为什么这里重写了func1,但是Base1和Base2的地址不一样呢?

面对这个问题,网上绝大多数是没有答案的。于是我们最好的办法就是看汇编

上面的问题换言之,就是下面这段代码,两个调用结果一样,但是过程出现了不一样的情况

image-20230828175549699

我们直接进入汇编模式

image-20230828182948017

如上是需要注意的事件,call之前都是在想办法取出虚表中的地址,我们可以直接观测eax的值,可以发现确实是Base1虚表中这个fun1函数的地址。

然后我们继续往深走,即进入call的内部,我们就会进入到一个jmp函数

image-20230828183020737

也就是说,我们call的地址只是一个jmp的地址,一般jmp后面的才是真正的地址

如下所示,果然如此,jmp后面才会真正的调用func1函数

image-20230828183050408

即如下所示的调用关系

image-20230828183140541

我们再来观测后半部分代码(注意:这里我的代码前半部分的代码地址改变了,这里其实只是我的一个小失误操作,不过不影响后序现象)

image-20230828183308062

这里与前面的现象是一样的,我们继续深入调查

image-20230828183328662

在这里我们发现了一些需要注意的问题,这里的jmp的地址首先和前面jmp指令的地址是不一样的,其次jmp后面所跳转的地址也是不一样的。

现在我们只能继续深入调查了。

image-20230828183653129

在这里我们发现,它没有直接像前面的一样直接跳转到函数,而是先对ecx减去8了。

要知道,ecx一般存储的是this指针的值。所以这里是对this指针减去8

减去以后ecx的值发生了变化

image-20230828183913396

然后我们继续进入jmp内部

image-20230828184000274

这个时候我们发现,jmp的地址着不就是Base1虚表中的func1的地址吗?

所以现在就来了一个大折返,直接与前面的一样了

image-20230828184407048

也就是说,这里只是多走了几步

总之,无非就是后半部分多走了很多的代码。但是最终的结果是一样的。

image-20230828184740930

那么编译器究竟为什么要这么做呢?我们知道中间多出来几步中,有一步是让this指针减去8。那么为什么要减去8呢?

我们也许会注意到我们的d的对象模型,ptr2和ptr1正好相差8

image-20230828185141277

我们先想清楚func1是谁的成员函数,其实是Derive的成员函数。而我们想要调用Derive的成员函数,那么这个this指针应该指向的是d对象,ptr1和ptr2就是充当着this指针,而我们现在呢?ptr1指向的恰好就是起始地址,刚好巧了,所以它就不需要动。ptr2指向的是Base2的那一部分。它的功能就是找到对应的虚表,找到对应的函数地址以后,我们也不能直接对其进行call调用,因为实际调用成员函数的时候必须要传ecx的,那么我们就需要将他调整到d的起始位置。

而且我们也注意到了,这里的黄色部分刚好就是把ptr2的值交给了ecx,这里就说明了ecx存储的是this指针

image-20230828191441105

相应的,ptr1的调用和ptr2是类似的,先要去把this指针给处理好

image-20230828231412471

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;
}

其实但对于菱形继承还是比较简单的。因为菱形继承我们大可以抽象为,两个类分别虚继承,然后一个类多继承即可

我们根据菱形继承的知识,不难画出如下对象模型

image-20230829002844902

调试窗口的运行结果如下

image-20230829003422554

所以菱形继承其实和多继承是一样的

即便是每个类里面都有一个虚函数也是一样的,因为继承以后,B使用A里面的虚表,C使用A里面的虚表,D使用B和C的虚表。对象模型里面存储的是虚表指针,所以对象模型并未发生改变,改变的只是虚表指针指向的内容,就是虚表。根据单继承和多继承的规则,B里面的虚函数并未构成重写,则直接衔接在A的虚函数表后面。C同理,D中的虚函数则放在B的虚表中,即B中的A虚表

image-20230829003938220

我们可以验证一下

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;
}

image-20230829004654186

可见,确实如此。

但是以上是没有发生重写/覆盖的情况下的。我们可以试一下发生了重写覆盖的情况

如下图所示,是B重写/覆盖了A的情况下,也是很好理解的,其实就是相当于B的虚函数地址覆盖了A的虚函数地址。D并不会在意B里面A的细节,它只关心虚表中的函数是否产生了重写/覆盖。如果是的话则,覆盖即可。没有就往后续

image-20230829005041545

我们继续观察,当A与D产生了重写,但A没有与B产生重写的条件下,可见与我们前面所说的是一致的

image-20230829005354773

当A、B、D都产生了重写的情况如下

image-20230829005440642

当A、B、D和A、C产生了重写如下所示

image-20230829005516633

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;
}

我们根据菱形虚拟继承的对象模型,不难得出以下的内存图

image-20230831165808761

我们可以使用内存图来进一步观察

image-20230831170138194

这里就是因为菱形虚拟继承会将B和C中的A都放到了公共部分

此时的按照菱形虚拟继承的内存分配来看是没有什么大问题的,但是当我们B和C同时对A的虚函数进行了重写的时候,由于是菱形虚拟继承。所以都会让A给放到公共部分,实际上是B和C共享的A。两个都一起重写,导致编译器不知道什么该听哪一个的,所以就报错了

image-20230831172324781

主要还是因为B和C都想要去重写这个A,才导致的问题。而如果只是一个菱形继承的话,就不会出现这个问题,因为各自重写各自的即可。

对于上面的情况,我们有两种方案去处理,第一种是只保留一种重写即可

如下所示就是,我们只保留了C的重写,不会发生冲突,所以就不会报错了

image-20230831172829914

第二种方案就是让D在来一个重写,这样的话, 反正无论B和C是否重写,都要听D的重写函数了。

image-20230831173331125

上面的这些原因其实都是因为只有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;
}

image-20230831182010505

如上图所示,是内存窗口的模样,那么我们可以看到B和C里面有两个指针,一个是虚基表指针,一个是虚表指针。那么究竟哪个是虚表指针,哪个是虚基表指针呢?我们可以测试一下

image-20230831183331160

image-20230831184007669

其实单纯,看里面的数据,我们大概可以猜测到,第一个是虚表指针,第二个是虚基表指针

对于虚基表里面的内容, 它里面存储的是偏移量,第一个是-4,第二个是18,可见,第一个指针是为了找到该部分的起始位置,第二个指针是为了找到A的部分

那么如果我们给D有自己单独的虚函数呢?D会额外创建虚表吗?其实不会的,因为D完全可以已经存在的虚表就够了。我们可能会以为放入共享的A的虚表,不过如果按照多继承的角度去理解,也有可能会放入B的虚表。

image-20230831184848308

三、抽象类

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;
}

image-20230901191413113

如下是由于包含纯虚函数导致不能实例化出对象的情形

image-20230901192114050

对于抽象类的多态,我们可能更多是应用于如下场景

image-20230901192312328

那么现在有一个问题,Car类有虚表吗?其实Car类甚至都没有实例化出对象,是根本不可能有虚表的。只有Benz和BMW类才有虚表。

其实纯虚函数的作用就是强制了派生类的重写,因为如果不重写的话,要虚函数其实也没有什么其他用处了。

它与override的区别就是,override则是检查派生类中的虚函数是否完成了重写

两者还是有一些差距的,一个是在基类的,一个是在派生类的。

2.接口继承与实现继承

普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数 。


总结

本篇文章着重讲解了多继承与菱形继承的虚表,以及抽象类的使用等方法。也正如抽象类的名字一样,本节内容确实比较抽象。愿可以为读者带来帮助!

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

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

相关文章

注意!CSPM换证工作于9月6日起转为线上开展!

之前胖圆给大家介绍了CSPM的相关信息&#xff0c;CSPM换证也将于9月6日起转成线上申请&#xff01;不用再快递资料了&#xff01;更加方便快捷&#xff01; CSPM-3考试也将于10月28日进行&#xff01;已经通过PMP的小伙伴可以考CSPM-3&#xff0c;CSPM-4了&#xff01;具体考试…

虚拟机安装aix 7.2

虚拟机安装aix 7.2 环境安装参考 环境 kali 2023 aix7.2镜像 https://archive.org/details/aix_7200-04-02-2027_072020安装 apt install qemu-system qemu-img create -f qcow2 aix-hdd.qcow2 20G qemu-system-ppc64 -cpu POWER8 -machine pseries -m 4G -serial mon:stdio…

什么是认证标志证书(VMC证书)?

认证标志证书&#xff08;VMC证书&#xff09;是电子邮件营销和安全的重要组成部分&#xff0c;Gmail、Yahoo Mail 、Fastmail 以及Apple Mail等知名邮箱供应商均支持认证标志证书&#xff08;以下简称VMC证书&#xff09;。那么什么是VMC证书呢&#xff1f;VMC证书有什么作用呢…

Navicat15天试用期过期解决办法

如果你是windows电脑&#xff0c;发现过期了先把Nvaicat关掉&#xff0c;按照以下步骤可以恢复到15天试用。 1.注册表输入regedit winR打开注册表 2.搜索输入HKEY_CURRENT_USER\Software\PremiumSoft\Navicat 删除Registration15XCS和Update这两个文件夹。 3.搜索HKEY_CURRE…

数据并行 - DP/DDP/ZeRO

数据并行DP 数据并行的核心思想是&#xff1a;在各个GPU上都拷贝一份完整模型&#xff0c;各自吃一份数据&#xff0c;算一份梯度&#xff0c;最后对梯度进行累加来更新整体模型。理念不复杂&#xff0c;但到了大模型场景&#xff0c;巨大的存储和GPU间的通讯量&#xff0c;就…

【OpenCV实战】4.OpenCV 五种滤波使用实战(均值、盒状、中值、高斯、双边)

OpenCV 五种滤波使用实战(均值、盒状、中值、高斯、双边&#xff09; 〇、Coding实战内容一、滤波、核和卷积1.1 滤波1.2 核 & 滤波器1.3 公式1.4 例子 二、图片边界填充实战2.1 解决问题2.2 相关OpenCV函数2.3 Code 三. 均值滤波实战3.1 理论3.2 Blur3.3 Code 四. 盒状滤波…

集成跨境电商ERP(积加、易仓、马帮等)连接多个应用

场景描述&#xff1a; 基于跨境电商开放平台&#xff08;积加、易仓、马帮等&#xff09;能力&#xff0c;无代码集成跨境电商ERP与多个应用互通互连。通过Aboter可搭建业务自动化流程&#xff0c;实现多个应用之间的数据连接。 连接器&#xff1a; 积加ERP马帮ERP易仓ERP……

Visual Studio Code 终端配置使用 MySQL

Visual Studio Code 终端配置使用 MySQL 找到 MySQL 的 bin 目录 在导航栏中搜索–》服务 找到MySQL–>双击 在终端切换上面找到的bin目录下输入指令 终端为Git Bash 输入命令 ./mysql -u root -p 接着输入密码&#xff0c;成功在终端使用 MySQL 数据库。

【LLM】快速开始 LangChain

theme: orange LangChain是一个软件开发工具包&#xff0c;它通过将组件链接在一起并公开简单统一的API&#xff0c;简化了大型语言模型和应用程序的集成。本篇文章将会简要介绍&#xff0c;让各位开发者对其有一个整体的认识。 前言 如果你是一名软件开发人员&#xff0c;努力…

chatGPT讲师AIGC讲师叶梓:大模型这么火,我们在使用时应该关注些什么?-5

以下为叶老师讲义分享&#xff1a; P20-P24 顺便看看某大模型觉得“两头蛇”长啥样&#xff1f; “羊驼-2”的神逻辑 欣赏一下GPT-4给出的满分答案 提示工程的模式 1、说明模式下&#xff0c;您为 ChatGPT 输入内容来解释或阐明一个概念或理论。 它的主要功能是定义各种概念。…

设计封面有诀窍,这5个实用软件让你快人一步

每位作者和出版商都梦想着为他们的作品设计一个引人注目的封面。这样一来&#xff0c;潜在的读者才会被吸引&#xff0c;愿意拿起这本书来阅读&#xff0c;从而提高书籍的销售量。这正是封面设计软件发挥作用的地方。专业的封面设计软件能够添加前沿的效果&#xff0c;以呈现书…

使用代理服务器和pip安装软件包

在开着代理服务器的情况下&#xff0c;直接pip install 软件包名会出现如下错误&#xff0c; WARNING: Retrying (Retry(total4, connectNone, readNone, redirectNone, statusNone)) after connection broken by SSLError(SSLZeroReturnError(6, TLS/SSL connection has been…

【Cadence】Calculator计算sp的3dB带宽

【Cadence】Calculator计算sp的3dB带宽 1.计算最大增益2.cross函数3. 3dB带宽 下面演示如何在Cadence计算s参数&#xff08;如增益&#xff09;的3dB带宽 1.计算最大增益 ymax函数 2.cross函数 cross函数可以计算经过y轴给定值对应的x坐标 edge number选择1是经过的第一个点…

B081-Lucene+ElasticSearch

目录 认识全文检索概念lucene原理全文检索的特点常见的全文检索方案 Lucene创建索引导包分析图代码 搜索索引分析图代码 ElasticSearch认识ElasticSearchES与Kibana的安装及使用说明ES相关概念理解和简单增删改查ES查询DSL查询DSL过滤 分词器IK分词器安装测试分词器 文档映射(字…

火热报名中 | 网安朝阳·西门子白帽黑客大赛燃爆来袭

2022年 首届西门子白帽黑客大赛 集结全国网安精英 以热爱之名 引爆整个夏天 2023年 网安朝阳西门子白帽黑客大赛—— 国际精英挑战赛 再度重磅归来 网安骑士的荣耀角斗场 等你来战 赛宁网安持续为第二届赛事 提供全程服务支持 热血战役 即将打响 报名通道现已开启…

风险评估

风险评估概念 风险评估是一种系统性的方法&#xff0c;用于识别、评估和量化潜在的风险和威胁&#xff0c;以便组织或个人能够采取适当的措施来管理和减轻这些风险。 风险评估的目的 风险评估要素关系 技术评估和管理评估 风险评估分析原理 风险评估服务 风险评估实施流程

SQLAlchemy 封装的工具类,数据库pgsql(数据库连接池)

1.SQLAlchemy是什么&#xff1f; SQLAlchemy 是 Python 著名的 ORM 工具包。通过 ORM&#xff0c;开发者可以用面向对象的方式来操作数据库&#xff0c;不再需要编写 SQL 语句。 SQLAlchemy 支持多种数据库&#xff0c;除 sqlite 外&#xff0c;其它数据库需要安装第三方驱动。…

专访远航汽车远勤山:踏踏实实做好产品 直面挑战乘风远航

8月25日&#xff0c;第二十六届成都国际汽车展览会在中国西部国际博览城隆重开幕。车展举办期间&#xff0c;远航汽车董事长远勤山先生、产品研发总监王震先生向媒体分享了远航汽车品牌发展、产品研发、技术创新以及市场布局等内容。 “通过我们的付出和努力&#xff0c;让我们…

景芯SoC 芯片全流程培训

【全网唯一】景芯SoC是一款用于芯片全流程培训的低功耗ISP图像处理SoC&#xff0c;采用低功耗RISC-V处理器&#xff0c;内置ITCM SRAM、DTCM SRAM&#xff0c;集成包括MIPI、ISP、CNN、QSPI、UART、I2C、GPIO、百兆以太网等IP&#xff0c;采用SMIC40工艺设计流片。 培训数据包括…

云计算在智能制造中的应用与前景

文章目录 云计算的基本概念智能制造的基本概念云计算在智能制造中的应用1. 数据存储和管理2. 大数据分析3. 机器学习和预测维护4. 跨地理分布的协作5. 资源弹性和成本优化 未来前景1. 智能工厂2. 预测性维护3. 定制化生产4. 绿色生产5. 全球制造协作 结论 &#x1f389;欢迎来到…