【c++随笔14】虚函数表

news2024/11/28 8:24:55

【c++随笔14】虚函数表

  • 一、虚函数表(Virtual Function Table)
    • 1、定义
    • 2、查看虚函数表
      • 2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?
      • 2.2、怎么发现虚函数表存在的?
      • 2.3、查看虚函数表里面都有什么?
    • 3、继承——虚函数的重写与覆盖
    • 4、虚函数为何可以实现多态?
    • 5、对象也能切片,为什么不能实现多态?(普通的继承为何不能实现多态?)
    • 6、打印虚函数表
      • 6.1同一个类型它们的虚表内存地址都是一样的,同一类型的对象共用一份虚表。
      • 6.2打印虚函数表
  • 二、多态原理
    • 1、动态绑定、动态类型、静态类型
    • 2、动态绑定和静态绑定对比
    • 3、虚函数表的存储位置
    • 4、汇编层面看多态实现原理
  • 三、多继承中的虚函数表
    • 1、多继承会有两张虚表(继承两个时),
    • 2、派生类定义的虚函数,存放在第一张虚函数表中
  • 四、经典问题

原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/131932164
qq技术交流群:921273910

一、虚函数表(Virtual Function Table)

1、定义

虚函数表是一个由虚函数组成的表格,用于实现动态绑定和多态性。每个包含虚函数的类都有自己的虚函数表,该表列出了该类及其所有基类的虚函数。当一个对象被创建时,它的类虚函数表也被创建,并且可以通过该对象的指针或引用来调用虚函数表中的函数。

虚函数表是一种实现动态多态性的机制。每个包含虚函数的类都有一个虚函数表,其中存储着该类的虚函数地址。当通过基类指针或引用调用虚函数时,程序会根据对象的实际类型查找对应类的虚函数表,并调用正确的虚函数。

2、查看虚函数表

2.1、 问题:三种类型,包含一个int类型的class、一个int类型的变量、int类型的指针:这三个大小分别是多少呢?


#include <iostream>
using namespace std;

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

int main(void)
{
	Base b;
	cout << sizeof(b) << endl;
	cout << sizeof(int) << endl;
	cout << sizeof(int *) << endl;

	return 0;
}

输出

在这里插入图片描述

  • 答案:(64位系统)

    • class 对象实例:占4个字节
    • int 变量:占4个字节
    • int 指针:占用8个字节
  • 其他结论:

    • 类class占用内存的大小,就是类calss成员变量占用内存的大小;
    • 类class占用内存的大小,和成员函数无关;
    • 类也可以作为一种数据类型来看待;
  • Base实例b里面有什么

只有成员变量_b
在这里插入图片描述

2.2、怎么发现虚函数表存在的?

  • 2.2.1加了virtual后,虚函数Base的大小
#include <iostream>
using namespace std;

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

int main(void)
{
	Base b;
	cout << sizeof(b) << endl;

	return 0;
}

输出

结论:
加了virtua后,虚函数Base大小为16;
在这里插入图片描述

  • 2.2.2、加了虚函数表后为何变成了16字节?
    调试查看Base类的实例b里面都有什么

调试截图如下,除了 _b 成员外,还有了一个 _vfptr 在 b1对象中在这里插入图片描述

结论:

  • 由于类里面,除了 _b 成员外,还增加了_vfptr,所以由4字节变成了16字节;
  • _vfptr就是虚函数表指针(virtual function pointer),指向虚函数表;

扩展:虚函数表指针占用8字节,int类型占用4字节;那8+4应该是12字节,为何总的内存变成了18字节呢,这里面有个内存对齐的问题。打个比方,你int类型占用4字节,double占用8字节,那么会总的便会占用16字节,int类型也会分配8字节的空间。

2.3、查看虚函数表里面都有什么?

在这里插入图片描述

结论:

  • 虚函数表里面是一个数组;
  • 数组里面存储的是每一个虚函数的地址;

扩展
其实,看了我之前写的文章就知道,其实类的实例的每一个成员函数,都有一个单独的地址,存储在代码段.text段。

3、继承——虚函数的重写与覆盖

代码:现在我们增加一个子类 Derive 去继承 Base:

#include <iostream>
using namespace std;

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

 // 子类 Derive
class Derive : public Base {
public:
	virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

int main(void)
{
	Derive d;
	cout << sizeof(d) << endl;

	return 0;
}

输出

在这里插入图片描述

父类 b 对象和子类 b 对象虚表是不一样的,这里看我们发现 Func1 完成了重写,

所以 d 的虚表中存的是重写的 Derive::Func1,所以虚函数的重写也叫做覆盖。

就可以理解为:子类的虚表拷贝了父类的虚表,子类的 Func1 覆盖掉了父类上的 Func1。

(覆盖指的是虚表中虚函数的覆盖)

  • 虚函数重写:语法层的概念,子类对继承父类虚函数实现进行了重写。
  • 虚函数覆盖:原理层的概念,子类的虚表,拷贝父类虚表进行了修改,覆盖重写那个虚函数。

🔺 总结:虚函数的重写与覆盖,重写是语法层的叫法,覆盖是原理层的叫法。

4、虚函数为何可以实现多态?

多态调用实现是依靠运行时去指向对象的虚表中查,调用函数地址。

#include <iostream>
using namespace std;

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


 // 子类 Derive
class Derive : public Base {
public:
	virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

int main(void)
{
	Base b;
	Derive d;
	
	Base* ptr = &b;
	ptr->Func1();   // 调用的是父类的虚函数

	ptr = &d;
	ptr->Func1();   // 调用的是子类的虚函数


	return 0;
}

输出

在这里插入图片描述

5、对象也能切片,为什么不能实现多态?(普通的继承为何不能实现多态?)

既然指针和引用可以实现多态,那父类赋值给子类对象也可以切片,
根本原因是:对象切片时,子类对象只会拷贝成员给父类对象,并不会拷贝虚表指针。(没有虚函数表)

之前我们讨论过,为何没有静态多态的概念,除了当时说的部符合多态的定义外,本质的原因就在这里。

6、打印虚函数表

6.1同一个类型它们的虚表内存地址都是一样的,同一类型的对象共用一份虚表。

#include <iostream>
using namespace std;

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


 // 子类 Derive
class Derive : public Base {
public:
	virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}

	virtual void Func3() {
		cout << "Derive::Func3()" << endl;;
	}
public:
	int _d = 2;
};


using pf = void(*)();
//typedef void(*pf)(void); //和上面的写法相等,看不懂的可以看下我的另外一篇博客《函数指针》

int main(void)
{
	Base b;
	Derive d1;
	Derive d2;
	
	return 0;
}

查看局部变量的窗口

在这里插入图片描述

可以看到子类继承自父类的虚函数表中Func1函数地址是重写之后的函数地址,已经将父类的func函数地址覆盖掉。

  • 如果父类中的虚函数没有被子类重写,那么子类的虚函数表中的地址仍然是父类中虚函数的地址。
  • 只有虚函数才会进虚函数表,非虚函数是不进虚函数表的。
  • 如果派生类中存在新增加的虚函数,那么就会按照在派生类中的声明顺序依次添加到派生类的虚函数表的最后。
  • 虚函数表本质就是一个虚函数指针数组,而虚函数表指针本质就是这个数组的首元素地址。虚函数表的最后一个字段通常置为nullptr。

6.2打印虚函数表

我们例子中,每一个虚函数返回值类型void,参数无,所以,虚函数指针数组中元素的类型为void(*)(void);(不懂的可以查看我的另外一篇博客《【c++随笔09】函数指针》

  • 注意:派生类的虚函数,visual并没显示出来,但是我们打印出来了,可见:visual的可视化是有些问题的。
#include <iostream>
using namespace std;

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


 // 子类 Derive
class Derive : public Base {
public:
	virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}

	virtual void Func3() {
		cout << "Derive::Func3()" << endl;;
	}
public:
	int _d = 2;
};


using pf = void(*)();
//typedef void(*pf)(void); //和上面的写法相等,看不懂的可以看下我的另外一篇博客《函数指针》

int main(void)
{
	Base b;
	Derive d1;
	Derive d2;
	
	Base* ptr = &d1;
	//ptr->Func1();   // 调用的是父类的虚函数
	//Base* ptr2 = new Derive();

	pf* pfun = (pf*)*(long long*)ptr;
	//2.pp这个指针是函数指针数组的首元素的地址。
	while (*pfun)
	{
		cout << *pfun << endl;
		
		(*pfun)();
		cout << endl;
		pfun++;
	}
	cout << "----------------------------------------" << endl;

	ptr = &d2;
	pfun = (pf*)*(long long*)ptr;

	while (*pfun)
	{
		cout << *pfun << endl;

		(*pfun)();
		cout << endl;
		pfun++;
	}
	cout << "----------------------------------------" << endl;

	ptr = &b;
	pfun = (pf*)*(long long*)ptr;
	while (*pfun)
	{
		cout << *pfun << endl;

		(*pfun)();
		cout << endl;
		pfun++;
	}

	return 0;
}

输出

在这里插入图片描述

二、多态原理

1、动态绑定、动态类型、静态类型

我们依然查看《C++ Primer 第5版》第15章节末尾 术语表中的介绍(p575-576页)

  • 动态绑定(dynamic binding) 直到运行时才确定到底执行函数的哪个版本。在C++语言中,动态绑定的意思是在运行时根据引用或指针所绑定对象的实际类型来选择执行虚函数的某一个版本。

  • 动态类型(dynamic type) 对象在运行时的类型。引用所引对象或者指针所指对象的动态类型可能与该引用或指针的静态类型不同。基类的指针或引用可以指向一个派生类对象。在这样的情况中,静态类型是基类的引用(或指针),而动态类型是派生类的引用(或指针)。

  • 静态类型(static type) 对象被定义的类型或表达式产生的类型。静态类型在编译时是已知的。

《C++ Primer 第5版》第15.2章节(p529页)

  • 以动态绑定有时又被称为运行时绑定(run-time binding)。
  • 在C++语言中,当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定。

2、动态绑定和静态绑定对比

由于《C++ Primer 第5版》并没有给出静态绑定的概念,我们暂时把在程序编译期间确定了程序的行为,也称为静态绑定。比如函数重载。

  • 动态绑定
#include <iostream>
using namespace std;

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

int main(void)
{
	Base *ptr1 = new Base();
	ptr1->Func1();

	return 0;
}
  • 静态绑定
#include <iostream>
using namespace std;

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

int main(void)
{
	Base *ptr1 = new Base();
	ptr1->Func1();

	return 0;
}
  • 汇编层面分析静态绑定和动态绑定的区别
g++ main.cpp
objdump -h -d -x ./a.out

对比反汇编的代码,如下截图

在这里插入图片描述

3、虚函数表的存储位置

推断:虚表存储在只读数据段上。
在这里插入图片描述

4、汇编层面看多态实现原理

  • 多态
#include <iostream>
using namespace std;

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


// 子类 Derive
class Derive : public Base {
public:
	virtual void Func1() {
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

void pf(Base *b)
{
	b->Func1();
}

int main(void)
{
	Base *ptr1 = new Base();
	pf(ptr1);

	Base *ptr2 = new Derive();
	pf(ptr2);

	return 0;
}


  • 非多态
#include <iostream>
using namespace std;

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


// 子类 Derive
class Derive : public Base {
public:
	void Func1() {
		cout << "Derive::Func1()" << endl;
	}
private:
	int _d = 2;
};

void pf(Base *b)
{
	b->Func1();
}

int main(void)
{
	Base *ptr1 = new Base();
	pf(ptr1);

	Base *ptr2 = new Derive();
	pf(ptr2);

	return 0;
}
g++ main.cpp
objdump -h -d -x ./a.out

对比反汇编的代码,如下截图

在这里插入图片描述

三、多继承中的虚函数表

  • 1、多继承会有两张虚表(继承两个时),

// 基类A
class A {
public:
    virtual void func1() {
        std::cout << "A::func1()" << std::endl;
    }

    virtual void func2() {
        std::cout << "A::func2()" << std::endl;
    }

};

// 基类B
class B {
public:
    virtual void func1() {
        std::cout << "B::func1()" << std::endl;
    }

    /*virtual void func2() {
        std::cout << "B::func2()" << std::endl;
    }*/

};

// 派生类C,多继承自A和B
class C : public A, public B {
public:
    virtual void func1() override {
        std::cout << "C::func1()" << std::endl;
    }

    /*virtual void func2() override {
        std::cout << "C::func2()" << std::endl;
    }*/

    virtual void func3() {
        std::cout << "C::func3()" << std::endl;
    }
};

int main() {
    C c;
}

我们先透过监视简单看一下:

在这里插入图片描述

2、派生类定义的虚函数,存放在第一张虚函数表中

#include <iostream>

// 基类A
class A {
public:
    virtual void func1() {
        std::cout << "A::func1()" << std::endl;
    }

    virtual void func2() {
        std::cout << "A::func2()" << std::endl;
    }

};

// 基类B
class B {
public:
    virtual void func1() {
        std::cout << "B::func1()" << std::endl;
    }

    /*virtual void func2() {
        std::cout << "B::func2()" << std::endl;
    }*/

};

// 派生类C,多继承自A和B
class C : public A, public B {
public:
    virtual void func1() override {
        std::cout << "C::func1()" << std::endl;
    }

    /*virtual void func2() override {
        std::cout << "C::func2()" << std::endl;
    }*/

    virtual void func3() {
        std::cout << "C::func3()" << std::endl;
    }
};

int main() {
    C c;

    // 打印A的虚函数表
    std::cout << "A's vtable: " << std::endl;
    void** aVTable = *(void***)(&c);
    for (int i = 0; aVTable[i] != nullptr; i++) {
        std::cout << "    [" << i << "]" << aVTable[i]<<" -> "<<" 函数执行";
        void(*func)() = (void(*)())(aVTable[i]);
       func();
    }

    // 打印B的虚函数表
    std::cout << "B's vtable: " << std::endl;
    void** bVTable = *(void***)(((char*)&c) + sizeof(A));
    for (int i = 0; bVTable[i] != nullptr; i++) {
        std::cout << "    [" << i << "]" << aVTable[i]<<" -> " << " 函数执行";
        void(*func)() = (void(*)())(bVTable[i]);
        func();
    }

     根据虚函数表地址运行虚函数
    //std::cout << "Running virtual function using A's vtable: " << std::endl;
    //void(*aFunc)() = (void(*)())(aVTable[0]);
    //aFunc();

    //std::cout << "Running virtual function using B's vtable: " << std::endl;
    //void(*bFunc)() = (void(*)())(bVTable[0]);
    //bFunc();

    return 0;
}

输出

在这里插入图片描述

四、经典问题

    1. inline函数可以是虚函数嘛?

inline函数可以是虚函数,但是其内联的特性也就没有了,因为inline只是对编译器的建议。内联函数是在调用的地方展开,没有函数地址,而虚函数的地址是要写入虚函数表的,所以内联函数和虚函数只能为其中的一个,不可兼得。

    1. 静态成员函数可以是虚函数嘛?

不能,因为静态成员函数没有this指针,使用类名::成员函数的调用方式 无法访问虚函数表,所以静态成员函数无法放进虚函数表。

    1. 构造函数可以是虚函数嘛?

不可以,因为虚函数表指针是在构造函数的初始化列表初始化的,但是虚函数又要借助虚函数表指针来调用虚函数,两者矛盾,所以不可以为虚函数。

    1. 析构函数可以是虚函数嘛?

可以,并且建议将析构函数定义为虚函数,因为这样可以避免内存泄漏的问题。如果子类对象是动态开辟的,使用父类指针指向子类对象,在delete时如果构成多态那么就会调用子类析构函数,而调用子类析构函数前系统会默认先调用父类析构函数,这样可以避免内存泄漏。

    1. 对象访问普通函数快还是访问虚函数快?

如果是通过实例化的对象访问那么是一样快的,如果是指针或引用对象访问的话是访问普通函数快的,因为指针或引用去访问虚函数时走的是多态调用是一个晚绑定,需要在运行时去需表中找函数的地址。

    1. 虚函数表是在什么时候形成的?存在哪?

和虚函数相关的字符,字符在只读数据区(.rodata),但是虚函数的实现代码应该是和其他的函数一样,存储在代码段(.text)

    1. 什么是抽象类?

函数纯虚函数的类叫做抽象类,此类不能实例化出对象,这也强制了其派生类如果想要实例化出对象那么就必须重写纯虚函数。

    1. C++菱形继承解决方案和多态原理?

菱形继承具有数据冗余和二义性的问题,解决的方法是通过虚继承的方式,虚继承的派生类中会产生一个虚基表指针,该指针指向虚基表,表中的内容是一个到冗余数据的偏移量,而原本冗余的数据会被放到派生类对象的最后。
多态的原理是通过重写虚函数,达到在派生类的虚函数表中重写的虚函数地址覆盖掉原本的地址,然后通过基类的指针或者引用指向派生类对象时,调用虚函数调用的时子类重写后的虚函数,而执行基类对象时调用的就是基类的虚函数达到多态的行为。
不要将虚基表和虚函数表搞混。

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

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

相关文章

JVM 内存管理深度剖析

1、JVM 基础知识 1.1 JVM 与操作系统的关系 JVM 能识别 class 后缀的文件&#xff0c;并且能够解析它的指令&#xff0c;最终调用操作系统上的函数&#xff0c;完成指定操作。操作系统并不认识这些 class 文件&#xff0c;是 JVM 将它们翻译成操作系统可识别的机器码&#xf…

【古诗生成AI实战】之二——项目架构设计

[1] 项目架构 在我们深入古诗生成AI项目的具体实践之前&#xff0c;让我们首先理解整个项目的架构。本项目的代码流程主要分为三个关键阶段&#xff1a; 1、数据处理阶段&#xff1b;   2、模型训练阶段&#xff1b;   3、文本生成阶段。 第一步&#xff1a;在数据处理阶段…

WordPress无需插件禁用WP生成1536×1536和2048×2048尺寸图片

我们在使用WordPress上传图片媒体文件的时候&#xff0c;是不是看到媒体库中有15361536和20482048的图片文件&#xff0c;当然这么大的文件会占用我们的服务器空间&#xff0c;如何禁止掉呢&#xff1f; function remove_default_image_sizes( $sizes) {unset( $sizes[1536x15…

Gee教程1.HTTP基础

标准库启动web服务 Go语言内置了 net/http库&#xff0c;封装了HTTP网络编程的基础的接口。这个Web 框架便是基于net/http的。我们先回顾下这个库的使用。 package mainimport ("fmt""log""net/http" )func main() {//可以写成匿名函数(lambda…

Java 注解在 Android 中的使用场景

Java 元注解有 5 种&#xff0c;常用的是 Target 和 Retention 两个。 其中 Retention 表示保留级别&#xff0c;有三种&#xff1a; RetentionPolicy.SOURCE - 标记的注解仅保留在源码级别中&#xff0c;并被编译器忽略RetentionPolicy.CLASS - 标记的注解在编译时由编译器保…

树状数组 / pbds解法 E2. Array Optimization by Deque

Problem - 1579E2 - Codeforces Array Optimization by Deque - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 树状数组解法 将 a i a_i ai​插入到队头&#xff0c;贡献为&#xff1a;原队列中所有比 a i a_i ai​小的数的数量将 a i a_i ai​插入到队尾&#xff0c;贡献为&a…

深信服超融合一体机提示:内存ECC

PS&#xff1a;此事件分享主要来源于季度巡检时发现的超融合一体机红灯闪烁异常&#xff0c;接入IPMI端口查看日志发现持续提示内存ECC&#xff1b; 因为是只有3.05这一天发现了有这个告警的提示&#xff0c;所以当时清除了日志以后重启了BMC服务就解决了&#xff1b;但是如果清…

常见树种(贵州省):021冬青、连香树、白辛树、香合欢、云贵鹅耳枥、肥牛树、杜英、格木、黄连木、圆果化香树、南天竹

摘要&#xff1a;本专栏树种介绍图片来源于PPBC中国植物图像库&#xff08;下附网址&#xff09;&#xff0c;本文整理仅做交流学习使用&#xff0c;同时便于查找&#xff0c;如有侵权请联系删除。 图片网址&#xff1a;PPBC中国植物图像库——最大的植物分类图片库 一、冬青 …

MyBatis插入操作返回主键报错问题记录

一开始用直接传参数的方法写的插入操作 StudentMapper.java接口 Integer insertStudent(Param("sname") String name,Param("sage") int age); 然后在网上搜了返回主键的方法 StudentMapper.xml: <insert id"insertStudent" useGenerat…

简易版王者荣耀

所有包和类 GameFrame类 package newKingOfHonor;import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; import java.util.ArrayList;im…

【操作系统】Linux操作系统中命令行参数与环境变量

本篇要分享的内容关于Linux新操作系统中命令行参数。 命令行参数本质上还是作为以后的环境变量的基础来学习的&#xff0c;所以在接触更高难度的内容之前先学习基础。 以下为本篇目录 目录 1.main函数的参数&#xff1f; 2.命令行解释器意义 3.环境变量 3.1.PATH环境变量…

手把手教会你--渗透实战--Hack The Box-Starting Point-Meow--持续更新

有什么问题&#xff0c;请尽情问博主&#xff0c;QQ群796141573 前言 前言 请务必跟着博主复现一遍 参考&#xff1a; Hack The Box-Starting Point-Meow

工业级 S25HS01GTDPBHV030 NOR闪存,L9305EP汽车级驱动器IC,LMK03318RHSR时钟发生器,PLL(中文资料)

一、工业级 S25HS01GTDPBHV030 Semper™ NOR闪存 S25HS01GT SEMPER™ NOR Flash闪存系列是英飞凌高性能、安全而可靠的 NOR Flash解决方案。 它集成了适用于汽车、工业、通信等广泛应用的关键安全功能。 凭借 SEMPER™ NOR Flash闪存&#xff0c;英飞凌推出了业界首款符合 ASI…

设计模式—接口隔离原则(ISP)

1.背景 2002 年罗伯特C.马丁给“接口隔离原则”的定义是&#xff1a;客户端不应该被迫依赖于它不使用的方法&#xff08;Clients should not be forced to depend on methods they do not use&#xff09;。该原则还有另外一个定义&#xff1a;一个类对另一个类的依赖应该建立…

C#学习-9课时

P11 IF判断(上) P11 IF判断(中 ) bool→true or false&#xff1b; 为&#xff1a;变量赋值 为&#xff1a;等于(判断) !为&#xff1a;≠ 优先级&#xff1a;大于 using System; using System.Collections.Generic; using System.Linq; using System.Text; usin…

USB总线-Linux内核USB3.0 Hub驱动分析(十四)

1.概述 USB Hub提供了连接USB主机和USB设备的电气接口。USB Hub拥有一个上行口&#xff0c;至少一个下行口&#xff0c;上行口连接上一级的Hub的下行口或者USB主机&#xff0c;连接主机的为Root Hub&#xff0c;下行口连接下一级Hub的上行口或者USB设备。经过Hub的扩展&#x…

Linux7安装mysql数据库以及navicat远程连接mysql

1.下载地址&#xff1a;MySQL :: Download MySQL Community Server 2.创建mysql目录将压缩包上传到该目录 mkdir /opt/mysql cd /opt/mysql3.解压压缩包 gzip mysql-8.1.0-1.el7.x86_64.rpm-bundle.tar tar -zxvf mysql-8.1.0-1.el7.x86_64.rpm-bundle.tar.gz 4.前置检查 ch…

【算法】装备合成(二分)

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 牛牛有x件材料a和y件材料b&#xff0c;用2件材料a和3件材料b可以合成一件装备&#xff0c;用4件材料a和1件材料b也可以合成一件装备。牛牛想要最大化合成的装备的数量&#xff0c;于是…

opencv-利用DeepLabV3+模型进行图像分割去除输入图像的背景

分离图像中的人物和背景通常需要一些先进的图像分割技术。GrabCut是一种常见的方法&#xff0c;但是对于更复杂的场景&#xff0c;可能需要使用深度学习模型。以下是使用深度学习模型&#xff08;如人像分割模型&#xff09;的示例代码&#xff1a; #导入相关的库 import cv2 …

什么是网络安全 ?

网络安全已成为我们生活的数字时代最重要的话题之一。随着连接设备数量的增加、互联网的普及和在线数据的指数级增长&#xff0c;网络攻击的风险呈指数级增长。 但网络安全是什么意思&#xff1f; 简而言之&#xff0c;网络安全是一组旨在保护网络、设备和数据免受网络攻击、…