【C++进阶之路】多态篇

文章目录

  • 前言
  • 一、概念
    • 1.分类
    • 2.实现条件
      • ①重写虚函数
        • 1.1总结三重
        • 1.2 final与override
      • ②父类的指针或者引用
        • 2.1普通调用VS多态调用
    • 3.抽象类
      • 3.1. 纯虚函数
      • 3.2. 接口继承和实现继承
  • 二、原理及使用
    • 1.虚函数表 —— 虚表
    • 2.默认成员函数
      • 2.1构造函数
      • 2.2析构函数
    • 3. 多继承
      • 3.1普通的多继承 + 虚函数
      • 3.2菱形继承 + 虚函数
      • 3.3菱形虚拟继承 + 虚函数
    • 4.inline与static
      • 4.1inline
      • 4.2 static
  • 总结

前言

 多态,顾名思义,就是一件事物具备不同的形态,是继承之后,面向对象的第三大特性,可以这样说:有了继承才有了类的多态,而类的多态是为了更好的实现继承。

 多态的列车即将起航,不知你准备好了吗?

一、概念

继承与多态相辅相成
举个例子:
 我们都是人(具备人都有的信息——性别,年龄等),在社会上我们又会具备不同的身份——老师,学生,工人等,那这时放假回家,要去买火车票,那这时如果你是学生火车票五折。如果是老师火车票七五折

老师和学生都是人,人又具有不同的身份,而不同身份面临的同一件事情表现的具体形态不同。

因此:老师和学生继承人的信息,从而使人对同一件事表现出不同的形态,这样就是多态。


 那语法层面上是如何实现多态呢?

1.分类

 多态,我们其实已经接触过一种,叫函数重载,根据传参不同而调用同一函数的不同形态。这叫做静态的多态,也叫静态绑定,而我们今天讲的主要是在继承之后延伸出的类的多态,叫动态的多态,也叫动态绑定

这里根据上面的例子,列出一段代码:

#include<iostream>
using namespace std;
class Person
{
public:
	virtual void BuyTickets()
	{
		cout << "全价" << endl;
	}
};

class Student : public Person
{
public:
	virtual void BuyTickets()
	{
		cout << "学生->半价" << endl;
	}
};

class Teacher : public Person
{
public:
	virtual void BuyTickets()
	{
		cout << "老师->七五折" << endl;
	}
};
void BuyTicket(Person & per)
{
	per.BuyTickets();
}
int main()
{
	Student stu;
	Teacher tea;

	BuyTicket(stu);
	BuyTicket(tea);

	return 0;
}

运行结果如下:
在这里插入图片描述

初学者看懂个大概即可,语法和原理下面会细讲。


2.实现条件

主要实现方法有两个。

①重写虚函数

  • 所谓虚函数,就是在函数的前面加上virtual

那重点就在于重写,也叫覆盖

如何才能构成重写呢?或者什么叫做重写呢?

  1. 首先父类至少得有虚函数。
  2. 一般来说,子类虚函数得跟父类的虚函数的函数名,参数类型,返回类型相同,简称三同

细节:

  1. 子类的函数前可不加virtual。
  2. 返回类型可以不同,但是必须是父子类的指针或者引用,且父类只能是父类的指针或者引用,子类必须是子类的指针或者引用。 —— 协变
  3. 重写的是实现。

 到这重写的条件就讲清了,至于什么叫重写,其实很简单就是:在达成重写的条件下子类的虚函数替换掉父类的虚函数,从而达成用指向子类的父类指针,在调用此虚函数时,会调用子类的虚函数,而不是父类的虚函数。

列一段代码,看结果便可明了。

#include<iostream>
using namespace std;
class Person
{
public:
	//其它类的引用和指针也行,但必须是父是父的,子是子的!
	virtual Person* BuyTickets(int val1 = 1)
	{
		cout << "全价" << endl;
		cout << val1 << endl;

		return nullptr;
	}
};

class Student : public Person
{
public:
	virtual Student* BuyTickets(int val2 = 0)
	{
		cout << "学生->半价" << endl;
		cout << val2 << endl;

		return nullptr;
	}
};

class Teacher : public Person
{
public:
	virtual  Teacher* BuyTickets(int val3 = - 1)
	{
		cout << "老师->七五折" << endl;
		cout << val3 << endl;

		return nullptr;
	}
};
void BuyTicket(Person & per)
{
	per.BuyTickets();
}
int main()
{
	Student stu;
	Teacher tea;

	BuyTicket(stu);
	BuyTicket(tea);

	return 0;
}

运行结果如下:
在这里插入图片描述

  • 解释细节3:壳还是套的父类,仅仅改变了作用域和实现, 如果便于理解,你也可以认为参数名也改了——但实际上底层用的是地址和寄存器,根本不关心参数名,这也是细节1的原因。

1.1总结三重

在这里插入图片描述

1.2 final与override

  1. override——对虚函数的重写进行检查
class Student : public Person
{
public:
	Student* BuyTickets  (int val2 = 0) override
	{
		cout << "学生->半价" << endl;
		cout << val2 << endl;
		return nullptr;
	}
	//void fun1() override
	//{
	//}
	// 
	//此注释代码不符合重写条件,故报错。
};
  1. final
  • 禁止派生类重写此虚函数
class Person
{
public:
	virtual void BuyTickets()final
	{
		cout << "全价" << endl;
	}
};

class Student : public Person
{
public:
	//因为此函数构成重写,派生类会重写虚函数,因此会报错。
	void BuyTickets ()
	{
		cout << "学生->半价" << endl;
	}
};
  • 禁止此类被继承(语法层面) —— C++11
class A final
{};

//因为B继承A,所以会报错。
class B : public A
{};

C++98采用构造函数/析构函数私有来进行实现不可被继承(应用层面)。

  1. 构造私有
class A
{
public:
	static A* CreatObj()
	{
		return new A;
	}
private:
	A()
	{}
};
class B : public A
{
	//原理为父类的私有成员在派生类中不可见。
};
int main()
{
	A* a = A::CreatObj();
	//B b;报错
	return 0;
}
  1. 析构私有
class A
{
public:
	void Destory()
	{
		A::~A();
	}
private:
	~A()
	{}
};
class B : public A
{
public:
	//原理为父类的私有成员在派生类中不可见。
};
int main()
{
	A* ptra = new A;
	ptra->Destory();
	operator delete (ptra);
	//B b;
	//报错
	return 0;
}

②父类的指针或者引用

为啥必须是父类的指针和引用呢?

从概念上理解,是人具有多种形态,而不是老师具有多种形态,因为人是比较抽象的,赋予了某种身份才具象化。
再换一个例子,是植物具有多种形态,还是玫瑰花具有多种形态?其原因还是一样的,植物没有赋予固定的形态,是比较抽象的,而给植物赋予玫瑰花的身份是具象的。

  • 所以父类这种比较抽象的状态是符合多态的。 当然从C++语法上来说父类也可以是具体的。 但都是为了改变父类的指向从而调用父类的虚函数

补充:子类也可以有多种形态,这是把子类当做父类来看的,就比如动物里面有猫,而猫分为很多种,比如波斯猫,布偶猫等。

细节: 指定作用域,可破坏多态的条件

void BuyTicket(Person & per)
{
	per.Person::BuyTickets();
}
  • 为啥不能是子类的指针或者引用?

  • 为啥不是是父类对象?(涉及原理之后再讲)


2.1普通调用VS多态调用

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};
class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
};

class C : public A, public B
{
public:
	virtual void fun1()
	{
		cout << "C::fun1()" << endl;
	}
};

int main()
{
	C c;
	//普通调用
	c.fun1();
	//多态调用
	B* b = &c;
	b->fun1();
	//看汇编代码之后,想一下为什么,不构成多态也去虚函数里面找,再进行调用。
	C* ptrc = &c;
	return 0;
}

汇编图解:
在这里插入图片描述

3.抽象类

3.1. 纯虚函数

  • 在虚函数后面加上 “= 0” 即为纯虚函数,切记语法规定只能是0
class A
{
public:
	virtual void fun1() = 0;
};

纯虚函数所在类是不能实例化的。

补充:空函数——实现啥也没有
如:void func() {}

class B : public A
{}

这里B继承了A,纯虚函数也被继承了,因此B也无法进行实例化。

像这样,有纯虚函数的类,就是抽象类。

那如何使用呢?很简单子类将纯虚函数进行重写,不就能使用了么。

  • 因此抽象类,会强制子类重写虚函数(应用)。
class B : public A
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
};

B进行重写后,就不含纯虚函数,也就不是抽象类了。

如果你执意要调用,也是可以的,不过会报错

int main()
{
	B b;
	A* a = &b;
	a->fun1();
	return 0;
}

在这里插入图片描述

3.2. 接口继承和实现继承

  • 普通的继承,直接对成员函数进行复用,俗称接口继承。
  • 多态的继承,对虚函数进行重写,重写的是实现。俗称实现继承。

二、原理及使用

1.虚函数表 —— 虚表

引入:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{}
private:
	char _a = 1;
};
int main()
{
	A a;
	cout << sizeof(A) << endl;
	return 0;
}

运行结果:
在这里插入图片描述
为啥不是4(8字节对齐)呢?难道多了什么吗?

查看监视窗口:

调用构造函数前~
在这里插入图片描述
调用构造函数后~
在这里插入图片描述

  • 首先在构造函数调用前,比我们预想多了一个指针,是void** 类型的,并且没有被初始化。这就足以证明,8字节是咋来的了。
  • 在调用构造函数后,可以看到_vftptr的指针的具体信息,并被初始化了,大概是_vftptr指针指向的是一个数组,数组有两个元素,元素所存的元素的类型为void (*) ()的虚函数指针,也就是一张存放函数指针的表。且最后一个位置存放的应该是虚函数表的结束位置。 之后证明

那这张存放虚函数的表,我们称之为虚函数表,简称虚表。

那虚表是用来干啥呢?当然是肯定是用来实现多态的了,再说细点就是为了实现重写。

既然是这样,那我们对以下代码进行调试。

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{}
	virtual void fun2()
	{}
private:
	char _a = 1;
};
class B : public A
{
public:
	virtual void fun1()
	{}
};
int main()
{
	A a;
	B b;
	return 0;
}

调试结果:
在这里插入图片描述

  • 很显然,不同类的虚表是不同的。根据虚表指针存的值即可看出
  • 当子类生成虚表时,把子类的虚表拷贝下来,然后对构成重写的虚函数进行覆写,这里是对原来的位置进行覆盖实现。而子类的不构成重写的虚函数则继续在虚表的后面进行排列。
  • 至于这里监视窗口为啥看不到fun3,我的理解是监视窗口是站在父类的角度进行查看的,当然只能看到父类重写的虚函数和父类没有被重写的虚函数,如果能看到子类的虚函数不就怪了吗?

至于如何验证第三个位置是fun3,给出如下代码。

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << " A :: fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << " A :: fun2()" << endl;
	}
private:
	int _a = 1;
};
class B : public A
{
public:
	virtual void fun1()
	{
		cout << " B :: fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << " B :: fun3()" << endl;
	}
	int _b = 1;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
}
int main()
{
	B b;
	int ptr = *(int*)(&b);
	Print((FUN_PTR*)ptr);
	return 0;
}

运行结果:
在这里插入图片描述

这是虚表的内存地址(小端):
在这里插入图片描述

总结一下:

  1. 不同类的虚表指针的值是不同的。相同类的虚表指针的值是相同的。(不再证明,有兴趣自己看监视窗口)。
  2. 监视窗口看不到fun3, 是因为站的视角为父类,而fun3是子类的。
  3. vs2019虚表的结束位置为 0
  4. 子类虚表先放父类(虚函数),再重写,再放子类(从上往下)。
  5. 虚表指针随着构造函数的调用而初始化,且虚表指针在对象模型的第一个位置。

下面我们继续讨论遗留下来的问题:

  • 为啥不是是父类对象?
class A
{
public:
	virtual void fun1()
	{}
};
class B : public A
{
public:
	virtual void fun1()
	{}
};
int main()
{
	A a;
	B b;
	a = b;
	//拷贝不拷贝虚表?
	return 0;
}

我们只看赋值之后的监视窗口:

在这里插入图片描述

  • 可见,虚表是不会被拷贝过去的,因此,无法完成指向父类调子类的情况(多态),并且如果拷贝过去子类能调用父类的虚函数,就乱套了!因此是不拷贝虚表的。
  • 为啥不是是父类对象?

那虚表存在哪呢?给出如下代码进行验证。

#include<iostream>
class A
{
public:
	virtual void fun1()
	{}
};
int main()
{
	A a;
	//虚表的地址
	void** ptr = (void**)(*(int*)&a);
	//栈区的地址
	int _a = 0;
	//静态区的地址
	static int b = 0;
	//常量区地址
	const char* str = "abc";
	//堆的地址
	int* ptr1 = new int;
	printf("虚表地址->%p\n", ptr);
	printf("栈区地址->%p\n", &_a);
	printf("堆区地址->%p\n", ptr1);
	printf("静态区地址->%p\n", &b);
	printf("常量区地址->%p\n", str);
	return 0;
}

运行结果:
在这里插入图片描述

  • 可见虚表地址与常量区地址仅仅差8个字节。
  • 因此:虚表至少在VS2019下是存在常量区的

2.默认成员函数

2.1构造函数

  • 语法上,不允许在构造函数前加virtual。

那为什么呢?

利用之前得到的结论,虚表指针是在构造函数调用时才被初始化的! 如果构造函数是虚函数,那虚表指针都没有初始化,如何调用虚函数呢?典型的先有虚函数指针 还是 先调用构造函数的问题。因此语法上禁掉了。

2.2析构函数

  • 语法上,允许在析构函数前加virtual。

这是为啥呢?

举一段错误代码,一看便知:

#include<iostream>
using namespace std;
class A
{
public:
	~A()
	{
		cout << "A::~A()" << endl;
	}
	int _a = 0;
};

class B : public A
{
public:
	~B()
	{
		cout << "B::~B()" << endl;
	}
	int _b = 1;
};

int main()
{
	B* b = new B;
	A* a = b;
	delete a;

	return 0;
}

运行结果 :
在这里插入图片描述

  • 很明显的问题出来了,竟然没有调用子类的析构函数。这是由于向上转换发生的切割现象。会导致内存泄漏的问题。

如何解决?

  • 析构函数前加virtual,那假设上面的代码加上virtual,那delete 子类指针会形成多态,指向父类调用父类的析构函数,指向子类调用子类的析构函数。

3. 多继承

3.1普通的多继承 + 虚函数

举出如下代码进行实验:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "A::fun2()" << endl;

	}
	int _a = 0;
};

class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _b = 0;
};

class C : public A ,public B
{
public:
	virtual void  fun1()
	{
		cout << "C::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "C::fun3()" << endl;
	}
	int _c = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
	cout << endl;
}
int main()
{
	C c;
	void** vftptr1 = (void **)(*(int*)(&c));

	B* ptr = &c;
	void** vftptr2 = (void **)(*(int*)(ptr));


	Print((FUN_PTR*)vftptr1);
	Print((FUN_PTR*)vftptr2);

	return 0;
}

首先我们要看初始化之后的类C的对象模型
在这里插入图片描述

  • 细节:可以根据_vfptr的的信息看出有几个元素。这里A的有四个,B的有三个。
    根据此画出对象模型:
    在这里插入图片描述
    运行结果:
    在这里插入图片描述
  • 总结:继承的子类的不构成重写虚函数不会再生成虚表(除非没有)而是将虚函数放在第一个父类的虚表中 ; 多继承父类不共享虚表,而是各用个的。

除此之外,这里还会衍生出一个问题:运行结果C::fun1()的地址竟然不同,这是为什么呢?

将上述代码进行简化:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};
class B
{
public:
	virtual void fun1()
	{
		cout << "B::fun1()" << endl;
	}
};

class C : public A, public B
{
public:
	virtual void fun1()
	{
		cout << "C::fun1()" << endl;
	}
};

int main()
{
	C c;
	B* b = &c;
	A* a = &c;

	a->fun1();
	b->fun1();
	return 0;
}

调用函数的反汇编流程图:
在这里插入图片描述

  • 可见,b对象在调用真正的fun1时拐了一个弯,然后再调用fun1。

为啥要这样这样做呢?

  • 看关键动作——对ecx减8,ecx存放的是this指针,对this指针减8,到C对象的this指针位置,通过C的this指针再进行调用fun1。为啥要这样做呢?因为fun1的作用域是C的类域,直接用B的this指针显然不合理。
  • 因此:调整B的this指针是为了类域的独立性,那A对象咋不用呢?因为A的this指针本就可以当做D的this指针进行使用,没必要再偏。

3.2菱形继承 + 虚函数

同样给出一段代码实验:

#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 (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
	cout << endl;
}
int main()
{
	D d;
	void** vftptr1 = (void**)(*((int*)(&d)));

	C* b = &d;
	void** vftptr2 = (void**)(*((int*)(b)));

	Print((FUN_PTR*)vftptr1);
	Print((FUN_PTR*)vftptr2);

	return 0;
}

初始化对象后的D类的监视窗口:
在这里插入图片描述
运行结果:
在这里插入图片描述
据此画出D类对象的对象模型:

在这里插入图片描述

  • 其实跟多继承差不多,也就多套了一层,这里解释一下,B类的fun1重写A类的fun1,D类的fun2重写B类的fun2,D类的fun3放在第一个父类对象的虚表中。C类同理这里就不多说了。

3.3菱形虚拟继承 + 虚函数

贴出一段代码进行实验:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{
		cout << "A::fun1()" << endl;
	}
	int _a = 0;
};

class B : virtual public A
{
public:
	virtual void fun2()
	{
		cout << "B::fun2()" << endl;
	}
	int _b = 0;
};

class C : virtual public A
{
public:
	virtual void fun2()
	{
		cout << "C::fun2()" << endl;
	}
	int _c = 0;
};
class D : public B , public C
{
public:
	virtual void fun3()
	{
		cout << "D::fun3()" << endl;
	}
	virtual void  fun4()
	{
		cout << "D::fun4()" << endl;
	}
	int _d = 0;
};
typedef void (*FUN_PTR)();
void Print(FUN_PTR * arr)
{
	for (size_t i = 0; arr[i] != nullptr; i++)
	{
		printf("%p->", arr[i]);
		FUN_PTR ptr = arr[i];
		ptr();
	}
	cout << endl;
}

int main()
{
	D d;
	void** vftptr1 = (void**)(*((int*)(&d)));

	C* b = &d;
	void** vftptr2 = (void**)(*((int*)(b)));

	A* a = &d;
	void** vftptr3 = (void**)(*((int*)(a)));

	Print((FUN_PTR*)vftptr1);
	Print((FUN_PTR*)vftptr2);
	Print((FUN_PTR*)vftptr3);
	return 0;
}

d初始化后的监视窗口:
在这里插入图片描述

运行结果:
在这里插入图片描述
结合内存画出对象模型:

在这里插入图片描述

  • D单独的虚函数放在第一张虚表中,如果除A的虚表外,没有虚表可以放,那就放自己的虚表中。
  • B,C的各自的虚函数,分别存在两张虚表中。
  • A,存放一张虚表。
  • 类对象第一个位置存放的是虚表指针,而不是虚基表指针。 因此虚基表指针指向的第一个位置变成了ff ff ff fc,转换成int也就是 - 4 ,这是虚表指针的地址相对类的this指针偏移量(this指针 - 虚表指针的地址)。第二个位置存的是虚表指针的地址相对于A的偏移量。第三个位置存的是0,个人理解:表示终止位置

此外,我们还需注意避免不同子类重写基类的问题:

#include<iostream>
using namespace std;
class A
{
public:
	virtual void fun1()
	{}

};
class B : virtual public A
{
public:
	virtual void fun1()
	{}

};

class C : virtual public A
{
public:
	virtual void fun1()
	{}

};
class D : public B, public C
{
public:
	virtual void fun2()
	{}
	

};

编译结果:
在这里插入图片描述
画个图理解一下:

在这里插入图片描述

  • 问题:二义性,A不知该继承谁的。
  • 解决方法:交给孙子类决定。

代码如下:

class A
{
public:
	virtual void fun1()
	{}
	int _a = 0;
};
class B : virtual public A
{
public:
	virtual void fun1()
	{}
	int _b = 0;
};

class C : virtual public A
{
public:
	virtual void fun1()
	{}
	int _c = 0;
};
class D : public B, public C
{
public:
	virtual void fun1()
	{}
	virtual void fun2()
	{}
	int _d;
};

4.inline与static

4.1inline

补充知识:

1.内联函数,可以说不是函数,是一段代码。
2.类里面的成员函数,默认inline修饰。
3. inline修饰函数,不一定是内联函数,取决于函数的实现是否复杂,最终还是要编译器决定的。但内联函数一定是被inline修饰的!

  • 虚函数前是可以加inline的,但其不是内联函数,原因是因为虚函数是需要被放在虚表中的。

4.2 static

  • static是不能修饰虚函数的,其原因在于虚函数是为了实现多态的,而多态的条件是父类的指针或者引用,其本质上都是传了子类的this指针,但static修饰的函数是没有this指针的,无法实现多态,因此不能用static修饰虚函数

总结

 今天的分享就到这里了,如果觉得文章不错,点个赞鼓励一下吧!我们下篇文章再见

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用Ubuntu交叉编译Linux内核源码并部署到树莓派4B上

参考文章 1. 配置交叉编译环境 之前在ubuntu上配置过了&#xff0c;直接跳过 2.获取Linux内核源码 Linux内核源码链接 到链接里面选择自己合适版本的内核源码下载下来&#xff0c;然后传到ubuntu中进行解压 3.Linux内核源码的配置 参考文章 厂家配linux内核源码&#xff…

深入理解Gradle构建系统的工作原理

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

使用贝叶斯滤波器通过运动模型和嘈杂的墙壁传感器定位机器人研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【C++】多态的实现及其底层原理

个人主页&#xff1a;&#x1f35d;在肯德基吃麻辣烫 我的gitee&#xff1a;gitee仓库 分享一句喜欢的话&#xff1a;热烈的火焰&#xff0c;冰封在最沉默的火山深处。 文章目录 前言一、什么是多态&#xff1f;二、多态的构成条件2.1什么是虚函数&#xff1f;2.2虚函数的重写2…

ICASSP 2023 | Cough Detection Using Millimeter-Wave FMCW Radar

原文链接&#xff1a;https://mp.weixin.qq.com/s?__bizMzg4MjgxMjgyMg&mid2247486540&idx1&sn6ebd9f58e9f08a369904f9c48e12d136&chksmcf51beb5f82637a3c65cf6fa53e8aa136021e35f63a58fdd7154fc486a285ecde8b8521fa499#rd ICASSP 2023 | Cough Detection Usi…

Qt中postevent造成内存泄漏问题的通用解决方案

在Qt中由QCoreApplication统一管理Qt事件的收发和销毁,其中sendEvent为阻塞式发送,用于单线程的事件发送;postevent为非阻塞式发送,构造事件的线程和接受事件的线程可以为两个线程。 最近在做一个个人项目ShaderLab 需要绘制OpenGL实时渲染的图像,由于OpenGL渲染基本都放…

论文笔记——Influence Maximization in Undirected Networks

Influence Maximization in Undirected Networks ContributionMotivationPreliminariesNotations Main resultsReduction to Balanced Optimal InstancesProving Theorem 3.1 for Balanced Optimal Instances Contribution 好久没发paper笔记了&#xff0c;这篇比较偏理论&…

python+django+mysql项目实践一(环境准备)

python项目实践 环境说明: Pycharm 开发环境 Django 前端 MySQL 数据库 Navicat 数据库管理 创建Pycharm项目 安装Django 在pycharm文件—设置进行安装 新建Django项目 注意项目创建目录 项目默认目录文件说明: __init__.py asgi.py 【异步接受网络…

深度学习实践——模型推理优化练习

系列实验 深度学习实践——卷积神经网络实践&#xff1a;裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 深度学习实践——模型推理优化练习 模型推理优化练习架构设计练习知识蒸馏练习模型剪枝练习参数量化练…

如何高效实现文件传输:小文件采用零拷贝、大文件采用异步io+直接io

一般会如何实现文件传输&#xff1f; 服务器提供文件传输功能&#xff0c;需要将磁盘上的文件读取出来&#xff0c;通过网络协议发送到客户端。如果需要你自己编码实现这个文件传输功能&#xff0c;你会怎么实现呢&#xff1f; 通常&#xff0c;你会选择最直接的方法&#xf…

QT基于TCP协议实现数据传输以及波形绘制——安卓APP及Windows程序双版本

文章代码有非常非常之详细的解析&#xff01;&#xff01;&#xff01;诸位可放心食用 这个玩意我做了两个&#xff0c;一个是安卓app&#xff0c;一个是Windows程序。代码并非全部都是由我从无到有实现&#xff0c;只是实现了我想要的功能。多亏了巨人的肩膀&#xff0c;开源…

小研究 - 一种复杂微服务系统异常行为分析与定位算法(一)

针对极端学生化偏差&#xff08;&#xff25;&#xff58;&#xff54;&#xff52;&#xff45;&#xff4d;&#xff45; &#xff33;&#xff54;&#xff55;&#xff44;&#xff45;&#xff4e;&#xff54;&#xff49;&#xff5a;&#xff45;&#xff44; &#…

Java中的生产者/消费者模型

一、什么是生产者/消费者模型 生产者-消费者模型&#xff08;Producer-Consumer problem&#xff09;是一个非常经典的多线程并发协作的模型。 比如某个模块负责生产数据&#xff0c;而另一个模块负责处理数据。产生数据的模块就形象地被称为生产者&#xff1b;而处理数据的模…

后端一次返回大量数据,前端做分页处理

问题描述&#xff1a;后端接口返回大量数据&#xff0c;没有做分页处理&#xff0c;不支持传参pageNum&#xff0c;pageSize 本文为转载文章&#xff0c;原文章&#xff1a;后端一次返回大量数据&#xff0c;前端做分页处理 1.template中 分页 <el-paginationsize-chang…

39.手机导航

手机导航 html部分 <div class"phone"><div class"content"><img class"active" src"./static/20180529205331_yhGyf.jpeg" alt"" srcset""><img src"./static/20190214214253_hsjqw…

DHCP部署与安全详解

文章目录 一、DHCP是什么&#xff1f;二、DHCP相关概念三、DHCP优点四、DHCP原理1. 客户机发送DHCP Discovery广播包&#xff08;发现谁是DHCP服务器&#xff09;2. 服务器响应DHCP Offer广播包3. 客户机发送DHCP Request广播包4. 服务器发送DHCP ACK广播包 五、DHCP续约六、部…

ElasticSearch基本使用--ElasticSearch文章一

文章目录 官网学习必要性elasticsearch/kibana安装版本数据结构说明7.x版本说明ElasticSearch kibana工具测试后续我们会一起分析 官网 https://www.elastic.co/cn/ 学习必要性 1、在当前软件行业中&#xff0c;搜索是一个软件系统或平台的基本功能&#xff0c; 学习Elastic…

使用 OpenCV 进行图像模糊度检测(拉普拉斯方差方法)

写在前面 工作中遇到&#xff0c;简单整理人脸识别中&#xff0c;对于模糊程度较高的图像数据&#xff0c;识别率低&#xff0c;错误率高。虽然使用 AdaFace 模型&#xff0c;对低质量人脸表现尤为突出。但是还是需要对 模糊程度高的图像进行丢弃处理当前通过阈值分类&#xff…

Java开发中的分层开发和整洁架构

分层开发(横向拆分) 分层开发的概念: maven多模块开发项目管理.可以利用这种管理功能,实现一个项目的多层次模块开发–分层开发. 比如,当前项目HelloController依赖HelloService 这样做目的: 复杂开发过程.解耦(不调整依赖关系,无法解耦).分层开发(横向拆分)和纵向拆分的区别…

c# 此程序集中已使用了资源标识符

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 CS1508 此程序集中已使用了资源标识符“BMap.NET.WindowsForm.BMapControl.resources” BMap.NET.WindowsForm D:\MySource\Decompile\BMap.NET.WindowsForm\CSC 1 活动 运行程序时&a…