C++ —— 多态

news2024/11/26 8:56:05

目录

1.多态的概念

2.多态的定义及实现

2.1构成多态的两个硬性条件

2.2虚函数的重写

2.3override和final

3.抽象类

3.1接口继承和实现继承

 4.多态原理

4.1虚函数表

4.2原理

4.3静态绑定和动态绑定

5.单继承和多继承体系的虚函数表

5.1单继承体系的虚函数表

5.2多继承体系的虚函数表

6.继承和多态常见面试问题

6.1概念考察

6.2问答题

1.多态的概念

多态的前提的是继承。当不同的对象去完成同一种行为时会产生不同的结果就是多态的通俗意义。

例如学生、成人两个对象去完成买票这个行为,那么学生的结果是获得半价,而成人获得的结果的是全价。

2.多态的定义及实现

2.1构成多态的两个硬性条件

调用的函数必须是虚函数,且派生类必须对虚函数重写;必须是基类的引用或指针调用虚函数。

虚函数就是在成员函数之前加一virtual关键字。虚函数的目的就是为了实现多态

下面的程序是一个多态的案例:

class Person
{
public:
	virtual void print()
	{
		cout << "Person::全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void print()
	{
		cout << "Student::半价" << endl;
	}
};

void test(Person* ptr)	//传入的对象不同调用不同的虚函数
{
	ptr->print();	//必须是基类的指针或引用调用虚函数
}
int main()
{
	Student s;
	Person p;

	test(&p);
	test(&s);
	
	return 0;
}

2.2虚函数的重写

派生类中有一个与基类完全相同的虚函数(返回值、函数名、参数),但是定义是派生类想要的,这样的,称派生类完成了对基类虚函数的重写(覆盖)。

派生类的重写虚函数不一定非要加virtual关键字,因为基类被继承之后其虚函数依旧保持其原有的虚函数属性,但是这样的代码风格是不规范的。

class Student : public Person
{
public:
	void print()	//不加virtual关键字也可以
	{
		cout << "Student::半价" << endl;
	}
};

如果派生类没有对虚函数进行重写,那么派生类依然有基类原有的虚函数。

class Student : public Person
{
	//派生类没有重写基类的虚函数
	//那么派生类依然持有基类原有的虚函数
};

虚函数重写的两个例外:

        1.协变(基类虚函数和派生类虚函数的返回值不同)

基类虚函数可以返回任意具有继承关系的基类的指针或引用;派生类虚函数可以返回任意具有继承关系的派生类的指针或引用;前两者的返回值必须是同一个继承系统。

class A
{};
class B : public A
{};

// 基类虚函数可以返回任意具有继承关系的基类的指针或引用
class Person
{
public:
	virtual A* print()
	{
		cout << "Person::全价" << endl;
		return nullptr;
	}
};

// 派生类虚函数可以返回任意具有继承关系的派生类的指针或引用
class Student : public Person
{
public:
	virtual B* print()
	{
		cout << "Student::半价" << endl;
		return nullptr;
	}
};

        2.派生类的析构函数与基类的析构函数构成重写关系

如果基类的析构函数是虚函数,那么派生类的析构函数无论是否带有virtual关键字都与基类的析构函数构成重写。其原因在于:在多态系统中,编译器会自动将基类和派生类的析构函数解析为同名的destructor()函数。必须让派生类的析构函数与基类的析构函数构成重写关系的原因如下段代码所示:

class A
{
public:
	virtual ~A()
	{
		cout << "A::~A()" << endl;
	}
};

class B : public A
{
public:
	~B()
	{
		cout << "B:~B()" << endl;
	}
};

int main()
{
	// 如果基类和派生类的析构函数不构成多态系统
	// 那么在清理资源时就会产生重复释放
	A* pa = new A;
	A* pb = new B;
	
	delete pa;
	delete pb;
	return 0;
}

2.3override和final

C++对重写的要求比较严格,我们可以借助override和final这两个关键字检测派生类是否完成对基类虚函数的重写。

        1.final放在类名后面,表示此类不能被继承:

class A final
{};

class B : public A	//错误,A类不能被继承
{};

        2.final修饰虚函数,表明该虚函数不能被重写:

class Car {
public:
	virtual void Drive () final
	{}
};

class Benz :public Car {
public:
	virtual void Drive() //错误,此虚函数不能被重写
	{ 
		cout << "Benz-舒适" << endl; 
	}
};

        3.override可以检查派生类虚函数是否完成对基类虚函数的重写:

class Car {
public:
	virtual void Drive () const
	{}
};

class Benz :public Car {
public:
	// 报错,多余Drive函数,派生类没有正确重写
	virtual void Drive() override
	{}
};

3.抽象类

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口
类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生
类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

class Car
{
public:
	virtual void Drive() = 0;
};

class Benz :public Car
{
public:
};

int main()
{
	Benz b;	//错误,Benz类没有完成基类纯虚函数的重写
	return 0;
}

3.1接口继承和实现继承

普通成员函数的继承是一种实现继承,派生类继承了基类普通成员函数的所有东西(返回值、函数名、参数列表、函数定义)。虚函数的继承是一种接口继承,派生类只继承了基类虚函数的接口(返回值、函数名、参数列表),其目的就是为了派生类能够对虚函数进行重写(即使没有对基类虚函数进行重写,派生类依然保持基类原有虚函数,这是特性)。

下面是一道练习题:

// 下面程序输出结果是什么?
class A
{
public:
	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
	virtual void test() { func(); }
};
class B : public A
{
public:
	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
};
int main(int argc, char* argv[])
{
	B* p = new B;
	p->test();
	return 0;
}
// A: A->0 B : B->1 C : A->1 D : B->0 E : 编译出错 F : 以上都不正确

解析:使用派生类指针p指向派生类对象,那么这是一次普通函数调用。调用的函数是基类继承到派生类的虚函数,但是派生类没有对此函数完成重写,所以调用func函数时的this指针是基类指针,是一次多态调用。这个基类指针指向的是派生类对象(外部new了一个派生类对象),所以调用的是派生类重写之后的func函数,又因为接口继承,所以派生类的func函数的参数的缺省值为1,所以最终结果为B->1。

 4.多态原理

4.1虚函数表

我们使用一段代码来引用出虚函数表:

// 32位平台下,sizeof(Base)是多少?
class Base
{
public:
	virtual void Func1()
	{
		cout << "Func1()" << endl;
	}
private:
	int _b = 1;
};

int main()
{
	cout << sizeof(Base) << endl;
	return 0;
}

解析:如果没有Func1这个虚函数,那么答案绝对是4。但是这里的输出为8。其原因在于,类中的虚函数需要放到虚函数表中,一个含有虚函数的类至少有一个虚函数表的指针,虚函数表也称虚表。所以一个含有虚函数的类,不仅仅存放了成员变量,还存放了一个虚表指针。

我们使用更为复杂的代码往下分析: 

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

观察上图的监视列表可以得出以下结论:

        1.派生类对象由两部分构成,一部分是自己的成员,另一部分是基类成员;派生类对象也有也有一个虚表指针,这个指针存放在基类部分当中。

        2.基类对象和派生类对象虚表是不一样的,这里我们发现派生类完成了Func1的重写,所以派生类对象的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖。覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。

        3.非虚函数的函数地址不会放入虚表中。

        4.虚函数表本质是一个存放虚函数指针的指针数组(函数指针数组),一般情况这个数组以nullptr结尾(VS环境下,Linux可能不一样)。

        5.派生类的虚函数表是怎样生成的?先将基类虚表内容拷贝一份至派生类的虚表当中(因为这两个虚表的地址不一样,所以可以断定派生类的虚表是自己生成的)。如果派生类完成了对某个虚函数的重写,就会将重写的虚函数的地址放入虚表的对应位置。派生类自己新增的虚函数会依照声明顺序增加到派生类虚表的后面。

4.2原理

class Person
{
public:
	virtual void print()
	{
		cout << "Person::全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void print()
	{
		cout << "Student::半价" << endl;
	}
};

void test(Person& ptr)	//传入的对象不同调用不同的虚函数
{
	ptr.print();	//必须是基类的指针或引用调用虚函数
}
int main()
{
	Student s;
	Person p;

	test(p);
	test(s);
	
	return 0;
}

为什么根据传入对象的不同就能调用不同的虚函数?无论基类的指针或引用绑定的对象是基类对象还是派生类对象,他们都是没有区别的(绑定到派生类对象时会发生切片动作),所以在编译的时候无法确定基类的指针或引用到底绑定的是基类对象还是派生类对象中的基类部分,但是这是正确的,所以编译会通过。当需要调用虚函数时,根本无法确定调用的是哪个虚函数,所以只能在程序运行时去虚函数表当中找被调用虚函数的地址。此时无论基类指针或引用绑定的对象是谁,只需要根据虚表内容不同,调用不同的虚函数即可。

4.3静态绑定和动态绑定

        1.静态绑定:静态绑定在程序编译阶段就确定了行为,也称静态多态,例如函数重载。

        2.动态绑定:动态绑定在编译阶段无法确定行为,只能在程序运行期间根据具体类型调用具体的函数,也称动态多态。

5.单继承和多继承体系的虚函数表

5.1单继承体系的虚函数表

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int b;
};

int main()
{
	Derive d;
	return 0;
}

观察监视窗口发现:派生类的虚函数表中并没有func3函数的地址。是不是前面的理论是错误的?

其实不然,我们可以将虚表内容打印出来:

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a = 1;
};

class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int b = 2;
};

typedef void (*VFPTR)();

void print_VFtable(VFPTR vf[])
{
	cout << "虚表地址:0x" << vf << endl;
	for (int i = 0; vf[i] != nullptr; i++)
	{
		printf("[%d]:0x%p->", i, vf[i]);
		vf[i]();
	}
	cout << endl;
}
int main()
{
	Base b;
	Derive d;
	
	// 对象的前4/8个字节存放的是虚表指针
	VFPTR* vfb = (VFPTR*)(*(int*)&b);
	print_VFtable(vfb);

	VFPTR* vfd = (VFPTR*)(*(int*)&d);
	print_VFtable(vfd);

	return 0;
}

 结论:派生类新增的虚函数的地址会放入派生类的虚表中,但是监视窗口不能体现出来

5.2多继承体系的虚函数表

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

typedef void (*VFPTR)();

void print_VFtable(VFPTR vf[])
{
	cout << "虚表地址:0x" << vf << endl;
	for (int i = 0; vf[i] != nullptr; i++)
	{
		printf("[%d]:0x%p->", i, vf[i]);
		vf[i]();
	}
	cout << endl;
}
int main()
{
	Base1 b1;
	Base2 b2;
	Derive d;
	
	// 对象的前4/8个字节存放的是虚表指针
	VFPTR* vfb1 = (VFPTR*)(*(void**)&b1);
	print_VFtable(vfb1);	//打印b1对象的虚表

	VFPTR* vfb2 = (VFPTR*)(*(void**)&b2);
	print_VFtable(vfb2);	//打印b2对象的虚表

	VFPTR* vfd1 = (VFPTR*)(*(void**)&d);
	print_VFtable(vfd1);	//打印d对象的第一张虚表

	VFPTR* vfd2 = (VFPTR*)(*(void**)((char*)&d + sizeof(Base1)));
	print_VFtable(vfd2);	//打印d对象的第二张虚表

	return 0;
}

结论:派生类重写虚函数时是对多个基类的虚函数重写;派生类自己定义的虚函数的地址存放在派生类的第一张虚表当中。

6.继承和多态常见面试问题

6.1概念考察

1. 下面哪种面向对象的方法可以让你变得富有( )
A: 继承 B: 封装 C: 多态 D: 抽象

答案:A。继承可以定义一个与其他类相似的一个新类,并且这两个类保持一定的联系。

2. ( )是面向对象程序设计语言中的一种机制。这种机制实现了方法的定义与具体的对象无关,而对方法的调用则可以关联于具体的对象。
A: 继承 B: 模板 C: 对象的自身引用 D: 动态绑定
 答案:D。多态(动态绑定)可以实现题目描述的效果。

3. 面向对象设计中的继承和组合,下面说法错误的是?()
A:继承允许我们覆盖重写父类的实现细节,父类的实现对于子类是可见的,是一种静态复
用,也称为白盒复用
B:组合的对象不需要关心各自的实现细节,之间的关系是在运行时候才确定的,是一种动
态复用,也称为黑盒复用
C:优先使用继承,而不是组合,是面向对象设计的第二原则
D:继承可以使子类能自动继承父类的接口,但在设计模式中认为这是一种破坏了父类的封
装性的表现
 答案:C。优先应使用组合,因为组合并没有破坏原有类的封装性且耦合度较低。

4. 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化出对象

B:声明纯虚函数的类是虚基类

C:派生类必须实现基类的纯虚函数

D:纯虚函数必须是空函数

答案:A。B中的虚基类是虚继承的概念。

5.关于虚函数的描述正确的是()

A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型

B:内联函数不能是虚函数

C:派生类必须重新定义基类的虚函数

D:虚函数可以是一个static型的函数 

答案:B。虚函数的调用必须通过虚表,如果是内联函数就破坏了这个行为。D中的说法是错误的,因为static函数没有this指针。

6. 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
D:一个类的不同对象共享该类的虚表
答案:D。虚表也是存放在代码区的,与成员函数一样。

7. 假设A类中有虚函数,B继承自A,B重写A中的虚函数,也没有定义任何虚函数,则( )
A:A类对象的前4个字节存储虚表地址,B类对象前4个字节不是虚表地址
B:A类对象和B类对象前4个字节存储的都是虚基表的地址
C:A类对象和B类对象前4个字节存储的虚表地址相同
D:A类和B类虚表中虚函数个数相同,但A类和B类使用的不是同一张虚表
答案:D。B中虚基表的说法是错误的。

8. 下面程序输出结果是什么? ()

#include<iostream>
using namespace std;
class A {
public:
	A(const char* s) { cout << s << endl; }
	~A() {}
};
class B :virtual public A
{
public:
	B(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class C :virtual public A
{
public:
	C(const char* s1, const char* s2) :A(s1) { cout << s2 << endl; }
};
class D :public B, public C
{
public:
	D(const char* s1, const char* s2, const char* s3, const char* s4) :B(s1, s2), C(s1, s3), A(s1)
	{
		cout << s4 << endl;
	}
};
int main() {
	D* p = new D("class A", "class B", "class C", "class D");
	delete p;
	return 0;
}

 A:class A class B class C class D         B:class D class B class C class A
C:class D class C class B class A         D:class A class C class B class D

答案:A。因为是菱形虚拟继承,所以A类成员是B类和C类共享的,所以B和C两个类其中任意一个去初始化A都不合适(它们两个的构造函数显示调用A的构造函数是因为或许在某个场景下需要单独实例化B或C的对象),所以初始化A的任务只能放给D了。初始化列表的初始化顺序与声明顺序有关,很明显:D类是先继承B再继承C,而在继承B之前B已经继承A了,所以初始化顺序为,A->B->C->D。

9. 多继承中指针偏移问题?下面说法正确的是( )

class Base1 { public: int _b1; };
class Base2 { public: int _b2; };
class Derive : public Base1, public Base2 { public: int _d; };
int main(){
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
return 0;
}

A:p1 == p2 == p3 B:p1 < p2 < p3 C:p1 == p3 != p2 D:p1 != p2 != p3
答案:C。Derive类对象模型可以是下面这样:

10. 以下程序输出结果是什么()

class A
{
public:
virtual void func(int val = 1){ std::cout<<"A->"<< val <<std::endl;}
virtual void test(){ func();}
};
class B : public A
{
public:
void func(int val=0){ std::cout<<"B->"<< val <<std::endl; }
};
int main(int argc ,char* argv[])
{
B*p = new B;
p->test();
return 0;
}

 A: A->0         B: B->1         C: A->1         D: B->0         E: 编译出错         F: 以上都不正确

答案:B。上面已经讲解过。

6.2问答题

1. 什么是多态?答:参考本篇博客内容。

2. 什么是重载、重写(覆盖)、重定义(隐藏)?答:参考本篇博客内容与上一篇继承博客。

3. 多态的实现原理?答:参考本篇博客内容。

4. 虚函数可以声明为inline函数吗?答:可以,不过编译器会忽略内联属性,因为虚函数不能做内联函数(inline是一个建议选项)。

5. 静态成员可以是虚函数吗?答:不能,因为静态成员函数没有this指针,使用类作用域限定符直接访问函数无法访问虚表(通过指针运算访问),所以静态成员函数不能被放进虚表。

6. 构造函数可以是虚函数吗?答:不能,因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的

7. 析构函数可以是虚函数吗?什么场景下析构函数是虚函数?答:可以。在继承体系中,外部有动态释放的资源时。

8. 对象访问普通函数快还是虚函数更快?答:如果非多态调用,二者一样快;如果是多态调用,虚函数的访问比普通函数慢,因为调用虚函数需要经过虚表。

9. 虚函数表是在什么阶段生成的,存在哪的?答:虚函数表是在编译阶段就生成的,一般情况下存在代码段(常量区)的。

10. C++菱形继承的问题?虚继承的原理?答:参考继承博客。注意虚基表是虚继承的概念,虚基表存放的是偏移量;虚表是多态的概念,存放的是虚函数的地址。

11. 什么是抽象类?抽象类的作用?答:参考本篇博客内容。

 

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

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

相关文章

【MyBatis】第七篇:动态sql

mybatis中的动态sql&#xff0c;其实就是在mybatis中映射配置文件中通过if等判断语句写sql。现在聊一下&#xff0c;常用的的判断语句。 前面准备&#xff1a; CREATE TABLE student (sid int DEFAULT NULL,sname varchar(10) CHARACTER SET utf8mb3 COLLATE utf8mb3_general…

2023年深圳/东莞/惠州CPDA数据分析师认证报名入口

CPDA数据分析师认证是中国大数据领域有一定权威度的中高端人才认证&#xff0c;它不仅是中国较早大数据专业技术人才认证、更是中国大数据时代先行者&#xff0c;具有广泛的社会认知度和权威性。 无论是地方政府引进人才、公务员报考、各大企业选聘人才&#xff0c;还是招投标加…

计算机网络-传输层

文章目录前言概述用户数据报协议 UDP(User Datagram Protocol)传输控制协议 TCP(Transmission Control Protocol)TCP 的流量控制拥塞控制方法TCP 的运输连接管理TCP 的有限状态机总结前言 本博客仅做学习笔记&#xff0c;如有侵权&#xff0c;联系后即刻更改 科普&#xff1a…

LeetCode经典问题总结笔记—一文搞懂滑动窗口和哈希表结合使用之3. 无重复字符的最长子串问题(第一篇)

今日主要总结一下可以使用滑动窗口和哈希表结合使用解决的一道题目&#xff0c;3. 无重复字符的最长子串 题目&#xff1a;3. 无重复字符的最长子串 Leetcode题目地址 题目描述&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 示…

华尔街分析师:斗鱼2023财年前景暗淡,但盈利能力有望提升

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 华尔街预计斗鱼2023财年收入前景悲观 根据S&P Capital IQ的一致性数据&#xff0c;华尔街卖方分析师预计&#xff0c;斗鱼&#xff08;DOYU&#xff09;的收入将从2022财年的71.93亿元下降到2023财年的67.53亿元&#x…

react -- Context

使用Context简单传参例子 解决父子组件多层嵌套传参&#xff0c;中间不用通过props传值 import React, { useContext } from "react"; // 参数对象 const param { title: "星期四" }; // 创建一个 Context 对象 // const MyContext React.createContex…

基于matlab使用机器学习和深度学习进行雷达目标分类

一、前言此示例展示了如何使用机器学习和深度学习方法对雷达回波进行分类。机器学习方法使用小波散射特征提取与支持向量机相结合。此外&#xff0c;还说明了两种深度学习方法&#xff1a;使用SqueezeNet的迁移学习和长短期记忆&#xff08;LSTM&#xff09;递归神经网络。请注…

【快排与归并排序算法】

作者&#xff1a;指针不指南吗 专栏&#xff1a;算法篇 &#x1f43e;或许会很慢&#xff0c;但是不可以停下&#x1f43e; 文章目录一、快速排序 ( Quick Sort )二、归并排序 ( Merge Sort )总结一、快速排序 ( Quick Sort ) 1.思路 找出一个分界点&#xff0c;随机的调整区间…

openmmlab 语义分割算法基础

本文是openmmlab AI实战营的第六次课程的笔记&#xff0c;以下是我比较关注的部分。简要介绍语义分割&#xff1a;如下图&#xff0c;左边原图&#xff0c;右边语义分割图&#xff0c;对每个像数进行分类应用语义分割在个各种场景下都非常重要&#xff0c;特别是在自动驾驶和医…

【docker知识】联合文件系统(unionFS)原理

一、说明 Docker CLI 操作起来比较简单——您只需掌握Create、Run、InspPull和Push容器和图像&#xff0c;但是谁想过Docker 背后的内部机制是如何工作的&#xff1f;在这个简单的表象背后隐藏着许多很酷的技术&#xff0c; UnionFS&#xff08;统一文件系统&#xff09;就是其…

为什么静默安装未经过数字签名的驱动是不可行的?

我想&#xff0c;在 Windows XP 系统上&#xff0c;造成蓝屏的最主要原因是带有 Bug 的设备驱动程序。 请问在座的&#xff0c;谁赞成&#xff0c;谁反对。 因为驱动运行在内核模式&#xff0c;再也没有更高级别的组件对其进行行为监管&#xff0c;它可以做它想做的任何事情。…

Power BI 筛选器函数---Index实例详解

一、Index函数 语法&#xff1a; INDEX ( <检索行号>, [<关系>], [<OrderBy>],[空白],[PartitionBy] ) 含义&#xff1a; 对指定分区(PartitioinBy)中的行&#xff08;关系表&#xff09;&#xff0c;按指定的列进行排序(OrderBy)后&#xff0c;根据&…

医院智能化解决方案-门(急)诊、医技、智能化项目解决方案

【版权声明】本资料来源网络&#xff0c;知识分享&#xff0c;仅供个人学习&#xff0c;请勿商用。【侵删致歉】如有侵权请联系小编&#xff0c;将在收到信息后第一时间删除&#xff01;完整资料领取见文末&#xff0c;部分资料内容&#xff1a;篇幅有限&#xff0c;无法完全展…

Docker镜像的创建

Docker镜像Docker镜像Docker 镜像是一个特殊的文件系统提供容器运行时所需的程序、库、资源、配置等文件包含一些为运行时准备的一些配置参数&#xff08;如匿名卷、环境变量、用户等&#xff09;镜像不包含任何动态数据&#xff0c;其内容在构建之后也不会被改变。Docker镜像的…

论文中常用的注意力模块合集(上)

在深度卷积神经网络中&#xff0c;通过构建一系列的卷积层、非线性层和下采样层使得网络能够从全局感受野上提取图像特征来描述图像&#xff0c;但归根结底只是建模了图像的空间特征信息而没有建模通道之间的特征信息&#xff0c;整个特征图的各区域均被平等对待。在一些复杂度…

【化学试剂】endo-BCN-PEG4-Pomalidomide,(1R,8S,9S)-双环[6.1.0]壬-四聚乙二醇-泊马度胺纯度95%+

一、基础产品数据&#xff08;Basic Product Data&#xff09;&#xff1a;CAS号&#xff1a;N/A中文名&#xff1a;(1R,8S,9S)-双环[6.1.0]壬-四聚乙二醇-泊马度胺英文名&#xff1a;endo-BCN-PEG4-Pomalidomide二、详细产品数据&#xff08;Detailed Product Data&#xff09…

【软件测试】软件测试工作上95%会遇到的问题,你遇到多少?

目录&#xff1a;导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09;前言 1、测试负责人要进行…

C语言rand和srand用法详解

在实际编程中&#xff0c;我们经常需要生成随机数&#xff0c;例如&#xff0c;贪吃蛇游戏中在随机的位置出现食物&#xff0c;扑克牌游戏中随机发牌。在C语言中&#xff0c;我们一般使用 <stdlib.h> 头文件中的 rand() 函数来生成随机数&#xff0c;它的用法为&#xff…

Redis的事务和锁以及在SpringBoot中的使用

文章目录1、事务2、监视锁3、分布式锁1、事务 Redis中事务的操作主要有三个&#xff1a; # 1、开启事务 # 定事务的开启位置&#xff0c;此指令执行后&#xff0c;后续的所有指令均加入到事务中 1、multi # 2、执行事务 # 设定事务的结束位置&#xff0c;同时执行事务。与mul…

【java】map集合遍历: entrySet()、单独遍历key或者value、 keySet()、iterator遍历

目录 1.增强for循环。利用Map 的 entrySet&#xff08;&#xff09;方法获取元素2.增强for循环。单独遍历key或者value3.增强for循环。利用Map 的 keySet() 方法获取元素4.使用iterator遍历文中实体的定义在上篇笔记中&#xff1a;https://blog.csdn.net/qq_43622777/article/d…