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

news2025/2/24 15:19:20

在这里插入图片描述
在这里插入图片描述

个人主页:🍝在肯德基吃麻辣烫

我的gitee:gitee仓库
分享一句喜欢的话:热烈的火焰,冰封在最沉默的火山深处。


文章目录

  • 前言
  • 一、什么是多态?
  • 二、多态的构成条件
    • 2.1什么是虚函数?
    • 2.2虚函数的重写
    • 2.3 什么是虚函数表?
      • 有虚函数的对象的大小
    • 2.4普通对象调用和实现多态后的对象调用
  • 三、多态的原理
      • 经典题
      • 回到多态的两个条件
    • 多态条件的两个特例:(这一点是C++的大坑)
    • 3.1动态绑定和静态绑定
  • 四、默认成员函数和虚函数的关系
    • 4.1构造函数可以设置成虚函数吗?
    • 4.2 析构函数可以设置成虚函数吗?
    • 一道经典面试题
  • 五、单继承和多继承关系中的虚函数表
    • 5.1单继承关系中的虚函数表
    • 5.2多继承关系中的虚函数表
  • 六、抽象类
  • 写在最后的面试题
  • 总结


前言

本文继C++继承之后讲解C++多态。


一、什么是多态?

单单从概念入手不好理解,应该深入理解多态的实现后再回过头来讲解。
现在简单举个例子:我们在购买高铁票时,往往会有成人票全价,学生票半价的优惠,针对不同的人群给予不同的优惠,这个就是多态(多种形态)。

二、多态的构成条件

多态的两个构成条件为:

1.基类的指针或引用
2.满足虚函数的重写

2.1什么是虚函数?

virtual关键字修饰的类成员函数就是虚函数。

在继承中,子类要想重写父类,父类的成员函数必须是虚函数,而子类的成员函数可以不加virtual,但一般建议加上比较合适。

2.2虚函数的重写

虚函数的重写是:对父类的虚函数的实现进行覆盖,覆盖的内容是子类虚函数的实现。

满足重写的条件:三同。

函数名,参数类型,返回值相同的虚函数,就能满足重写的条件。

2.3 什么是虚函数表?

虚函数表是继承体系中,如果一个函数是虚函数,则该函数的地址会存储在一张虚函数表中,而不是存储在对象中,该虚函数表的地址才存储在对象中。通过虚函数表可以找到对应的虚函数,从而能够进一步实现多态。
在这里插入图片描述

有虚函数的对象的大小

class A
{
public:
	virtual void func1()
	{}

	virtual void func2()
	{}

protected:
	int _a;
};

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

请计算上面的代码中,A这个类的大小。
A有两个虚函数,一个成员,实际上大小为8

原因:
虚函数在内存中是存储在虚函数表中,而不是存在类对象中,类对象在内存中存储的是一个虚函数表指针和成员变量。
指针大小是4字节,成员是int类型,则共为8字节。
在这里插入图片描述

虚函数表本质上是一个函数指针数组。
一般建议:如果不实现多态,就不要设置成虚函数

2.4普通对象调用和实现多态后的对象调用

普通对象调用成员函数是在编译期间就确定了地址,而实现多态后的函数是在运行期间才确定地址。因为子类继承父类,子类先拷贝父类的虚函数表的地址,如果某个函数是虚函数,则会在子类的虚函数表中重写改函数的地址。

所以编译器在遇到父类的指针或引用调用子类的函数时,编译器在编译期间无法确定到底调用谁的函数,只能运行起来去子类的虚函数表中查找,是谁的地址就调用谁。

三、多态的原理

我们通过以下例子来看待多态。

class Person 
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-全价" << endl; 
	}
};

class Student : public Person 
{
public:
	virtual void BuyTicket() 
	{ 
		cout << "买票-半价" << endl; 
	}
};

void Func(Person& p)
{
	p.BuyTicket();
}

int main()
{
	Person Mike;
	Func(Mike);

	Student Johnson;
	Func(Johnson);
	return 0;
}

首先:子类继承了父类,父类在内存中有一张虚表,存的是虚函数的地址。当子类继承了父类后,子类中也同样有一张虚表,只不过是这张虚表上的虚函数的地址是拷贝父类的,如果子类中有虚函数,且满足重写的条件,则子类的虚函数表的虚函数的地址会被重写覆盖成子类的虚函数的地址。

在这里插入图片描述

当我们用父类的指针或引用调用子类的虚函数时,会去访问子类的虚函数表,当我们调用父类的虚函数时,会访问父类的虚函数表,这样就实现了父类指针指向父类对象就调用父类的虚函数表,指向子类对象就调用子类的虚函数表。在这里插入图片描述

所以多态就是:我们想调用父类的函数就传递父类对象给父类指针/引用,想调用子类的函数就传递子类对象给父类指针/引用。

经典题

这里有一道非常经典的坑人题目:

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

问:结果输出什么?
对于一个子类指针p指向子类对象,通过这个指针调用函数test,因为子类继承了父类,父类就成了子类的成员,通过父类的虚表找到test函数的地址调用,在test函数中又调用func函数,因为这是B的指针,调用func函数时会去B对象的虚表中找到func的地址,这个地址不再是从父类那里拷贝的func的地址,这个地址已经被重写成子类的func的地址,所以调用的是子类的func函数。
然而,虚函数的重写只是重写函数的实现,函数的接口仍然继承父类的,这个叫做接口继承,所以参数的缺省值仍然是父类的。
结果输出B->1

回到多态的两个条件

多态的两个条件:

1.父类的指针或引用
2.虚函数的重写

(1)为什么不能是子类的对象赋值给父类的对象,而是子类对象赋值给父类的指针/引用?
因为子类对象赋值给父类对象,切片过程中不会拷贝虚表。所以父类对象只能调用父类的虚表,子类对象才能调用子类的虚表,满足不了多态。

(2)为什么子类的对象赋值给父类的对象不会拷贝虚表?
因为如果拷贝虚表,使用子类对象会调用子类的虚函数,使用父类对象也会拷贝子类的虚函数,就乱套了。

(3)为什么不能是子类的指针或引用?因为父类是子类的一部分,父类赋值给子类指针不会切片,就不能获取父类的虚函数表。

需要注意的以下几点:

(1)虚函数存在哪?虚函数表存在哪?
注意:虚函数和普通函数一样,都是存在代码段的,虚函数表也是存在代码段的。
是虚函数地址存在虚表中,虚函数表的地址存在对象中。
我们可以通过打印地址的方式验证一下虚函数表存在代码段:

void test_where()
{
	Person p;
	Student s;
	//栈
	int a;
	printf("栈->[%p]\n", &a);

	//堆区
	int* ptr = new int;
	printf("堆->[%p]\n", ptr);

	//数据段(静态区)
	static int b;
	printf("静态->[%p]\n", &b);

	//代码段(常量区)
	const char* str = "Hello World";
	printf("常量区->[%p]\n", str);

	printf("虚函数表1->[%p]\n", *((int*)&p));
	printf("虚函数表2->[%p]\n", *((int*)&s));

}

int main()
{
	test_where();
	return 0;
}

思路:获取父类或子类对象的虚函数表指针,也就是取出父类/子类在内存中的前4个字节,将该地址与内存中的栈区,堆区,静态区(数据段),常量区(代码段)的地址进行对比,跟谁的地址比较近,就大致在哪个区域。

(2)子类继承父类后,如果子类也有自己的虚函数,则这些虚函数的地址是按照他们声明的顺序依次存在虚表的最后,因为前面先存父类的虚函数的地址。

多态条件的两个特例:(这一点是C++的大坑)

(1)协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。
(这一点是C++的大坑)

(2)析构函数的重写(父类和子类的析构函数名不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。

3.1动态绑定和静态绑定

多态可以分为动态多态和静态多态,动态多态也叫做动态绑定,静态多态也叫做静态绑定。

静态绑定有:函数重载
函数重载在编译时进行匹配,cout的自动识别类型的底层也是使用函数重载实现的。
函数重载原理:函数名修饰规则
动态绑定:多态。
编译阶段编译器不知道到底调用子类的虚函数还是父类的虚函数,所以编译器只能通过给定的对象,在运行期间,通过调用虚函数表的地址来进行查看。这个过程叫做动态绑定。
多态原理:虚函数表

四、默认成员函数和虚函数的关系

4.1构造函数可以设置成虚函数吗?

构造函数不能是虚函数,因为虚函数表是在构造函数的初始化列表初始化之后才会生成的。如果构造函数是虚函数,那么调用构造函数的时候要去虚函数表里面找到构造函数的地址来进行调用。那么如何找到虚表呢?这就是一个典型的现有鸡还是先有蛋的问题了。

4.2 析构函数可以设置成虚函数吗?

析构函数可以设置成虚函数,并且强烈建议设置成虚函数。

(1)析构函数加virtual,是不是虚函数?
是,因为在编译器编译阶段,会通过函数名修饰规则,统一将子类的析构函数和父类的析构函数改成:destructor。
(2)为什么要这么处理?

因为在子类析构函数调用完成后必须要调用父类的析构函数对父类的资源释放。设置成虚函数可以完成子类对父类的析构函数的重写。
(3)为什么要让他们构成重写?
因为在下面的场景中,构成重写可以实现父类指针调用子类对象会调用子类的析构,子类析构调用结束会自动调用父类的析构,父类指针调用父类对象会调用父类的析构。从而实现不同对象传递给父类指针/引用,会调用不同对象的函数,实现多态。

如果不设置成虚函数,假如有以下的场景,会出问题:

class Person
{
public:
	~Person()
	{
		cout << "father:~Person" << endl;
	}
};

class Student : public Person
{
public:
	~Student()
	{
		cout << "son:~Student" << endl;
		delete [] ptr;
	}

protected:
	int* ptr = new int[10];
	//new ==> 构造 + operator new()
};

int main()
{
	Person* p = new Person;
	delete p;

	p = new Student;
	delete p;
	return 0;
}

delete p 会处理成以下方式:
p->destructor() + operator delete( p )

普通对象,看当前者的类型。
多态对象,看指向对象的类型。

p是父类对象,不管指向的对象是父类还是子类,都会调用父类的析构函数,在子类申请的空间就得不到释放,会造成内存泄露问题。
如果设置成虚函数,就能够实现虚函数的重写,从而实现多态。

一道经典面试题

设计一个不想被继承的类,如何创建?
(1)构造函数设置成私有

class A
{
private:
	A()
	{}

};

class B : public A
{
public:
	B()
	{}

};

原理:在继承体系中,子类的构造函数必须先去调用父类的构造函数,这里父类构造设置成私有子类就无法调用了。

不过这里出现一个问题,子类无法调用父类的构造,父类也无法调用自己的构造了。
我们可以写一个函数,在函数里面创建一个父类。

class A
{
private:
	A Createobj()
	{
		return A();
	}

	A()
	{}

};

但是又有一个问题:如何调用这个函数呢?因为调用该函数创建对象,而创建对象又需要在函数里面创建。
这里我们可以加一个static解决

class A
{
private:
	static A Createobj()
	{
		return A();
	}

	A()
	{}

};

int main()
{
    A a = A::Createobj();
}

这样就可以通过指定类域访问该函数解决。

五、单继承和多继承关系中的虚函数表

5.1单继承关系中的虚函数表

单继承关系中,子类会拷贝父类的虚函数表,如果子类还有自己的虚函数,则该虚函数的地址会放在虚函数表的最后。

我们通过调试窗口无法看到子类的虚函数。在这里插入图片描述
不过我们可以通过获取虚表的地址来打印虚表的各个虚函数的地址。

typedef void(*VFPTR)();

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; }
	virtual void func4() { cout << "Derive::func4" << endl; }
private:
	int b = 2;
};

void PrintVfptr(VFPTR* arr)
{
	printf("虚表地址是:[%p]\n", arr);
	for (int i = 0; arr[i] != nullptr; i++)
	{
		printf("第%d个虚函数地址是:[%p]\n", i, arr[i]);
	}
	printf("\n");

}

int main()
{
	Base b;

	Derive d;
	int vfptrb = *((int*)&b);
	int vfptrd = *((int*)&d);
	PrintVfptr((VFPTR*)vfptrb);
	PrintVfptr((VFPTR*)vfptrd);

	return 0;
}


  • 思路:1.先获取虚表的地址,取子类对象的地址,强转成int*,再进行解引用,就取到了子类对象的前4个字节,也就是虚表指针。
  • 2.再强转成(VFPTR*)通过打印该指针指向的内容,即可打印虚表的内容。

5.2多继承关系中的虚函数表

这里有几种猜测,子类的未重写的虚函数会放在第一个继承的父类的虚表中,或者放在其他的父类的虚表中。

我们也可以通过打印地址的方式确定。

typedef void(*VFPTR)();

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

private:
	int b1 = 1;
};

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

class Derive1 :public Base1, public Base2
{
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d = 2;
};

void PrintVfptr(VFPTR* arr)
{
	printf("虚表地址是:[%p]\n", arr);
	for (int i = 0; arr[i] != nullptr; i++)
	{
		printf("第%d个虚函数地址是:[%p]\n", i, arr[i]);
	}
	printf("\n");

}

int main()
{
	Derive1 d;

	int vfptrb1 = *((int*)&d);
	
	Base2* p2 = &d;
	//自动切片,p2就指向Base2对象的首地址
	int vfptrb2 = *(int*)p2;
	//int vfptrb2 = *((int*)((char*)&d + sizeof(Base1)));

	PrintVfptr((VFPTR*)vfptrb1);
	PrintVfptr((VFPTR*)vfptrb2);

	return 0;
}

在这里插入图片描述

通过打印可以看到,子类中未重写的虚函数放在第一个继承的父类的虚表中。

但是这里有一个问题:为什么重写了func1,在Base1的func1的地址和Base2的func1的地址不一样?
在这里插入图片描述

在ptr1和ptr2调用func1的过程中,调用的是Derive的func1函数,因为func1已经被重写了。

而在内存中,ptr1和ptr2指向的地址如下:

在这里插入图片描述

因为ptr1指向的地址刚好是d对象的首地址,ptr1和this指针是重叠的,无需偏移,而ptr2需要偏移Base1字节才与this指针重叠。
这就导致在调用func1函数前,ptr2需要先偏移,才能调用。我们看到的是在偏移之前的ptr2的地址,这就是为什么看到的func1的地址不同,调用的确实同一个函数的原因,实际上ptr2会偏移。

六、抽象类

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

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

比如上面这个就是抽象类。
抽象类的特性:

  • 1.不能实例化出对象,子类继承后也不能实例化出对象,只有重写虚函数才能实例化出对象。
  • 2.可以定义指针或引用.

普通的对象是实现继承,而实现多态的对象是接口继承。

抽象类的作用:间接强制子类虚函数必须重写,否则无法实例化对象。

写在最后的面试题

  1. 什么是多态?
  2. 什么是重载、重写(覆盖)、重定义(隐藏)?
  3. 多态的实现原理?
  4. inline函数可以是虚函数吗?
  5. 静态成员可以是虚函数吗?
  6. 构造函数可以是虚函数吗?
  7. 析构函数可以是虚函数吗?
  8. 对象访问普通函数快还是虚函数更快?
  9. 虚函数表是在什么阶段生成的,存在哪的?
  10. C++菱形继承的问题?虚继承的原理?
  11. 什么是抽象类?抽象类的作用?

1.多态分为静态多态和动态多态。
静态多态是函数重载,本质上是函数名修饰规则来完成。
动态多态:继承中的虚函数重写 + 父类指针或引用两个条件完成。
动态多态的本质上是由虚函数表的实现来完成。
2.重载:两个函数必须在同一作用域,函数名和参数类型必须相同。
重写(覆盖):两个函数在父类和子类的作用域,且要求满足函数名,参数类型,返回值必须相同(协变例外),两个函数必须是虚函数。
重定义(隐藏)两个函数在父类和子类的作用域,且要求满足函数名相同,两个父类和子类的重名函数不构成重写就一定是重定义。
3.多态实现的原理:
静态多态:函数名修饰规则
动态多态:虚函数表
4.不可以,因为一个函数如果设置成内联,就是一段代码,没有产生地址,无法将地址放进虚表,就不能是虚函数了。(声明:如果一个内联函数被virtual修饰,那么该函数就会自动忽略inline的属性,而成为一个虚函数。
5. 静态成员函数不能是虚函数,因为静态成员函数没有this指针,使用类型::成员函数的方式来调用该函数,但这种方式无法访问虚函数表,所以静态成员函数无法放进虚表里,无法实现多态,就没有意义。
6.构造函数不能是虚函数,构造函数不能是虚函数,因为虚函数表是在构造函数的初始化列表中生成的,如果构造函数是虚函数,又得取到虚函数表里面找到构造函数的地址,所以是现有构造函数还是先有虚表呢?
7.析构函数可以是虚函数,并且强烈建议析构函数设置成虚函数。因为父类和子类的析构函数会被编译器统一编译成destructor,有些特殊场景必须实现多态才能解决,请参考本文内容。
8.对象访问普通函数和虚函数是一样快的,因为它们都是在编译期间就确定。但如果是指针对象或引用对象调用,是普通函数快,因为调用虚函数会去到虚表中查找地址,速度会慢一些。
9.虚函数表在编译阶段生成的,存在代码段,具体参考本文章的案例。
10.菱形继承的问题:数据冗余和二义性。菱形虚拟继承原理:生成一张虚基表,存放的是该对象相对于父类对象的偏移量,通过偏移量可以访问父类的成员,不再需要在每个子类中存一份父类的成员。
11.抽象类:具有纯虚函数的类叫做抽象类。而在虚函数后面加上 = 0,就是纯虚函数。抽象类的意义:强制子类进行虚函数的重写,并且抽象类体现了接口继承关系。

总结

多态内容就讲到这里。

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

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

相关文章

【简化程序设计】C++STL“容器适配器“之栈和队列

【STL】容器适配器之栈和队列 stack的介绍和使用stack的介绍stack的使用stack的模拟实现 queue的介绍和使用queue的介绍queue的使用queue的模拟实现 priority_queue的介绍和使用priority_queue的介绍priority_queue的使用priority_queue的模拟实现 容器适配器什么是容器适配器&…

【数据分享】1999—2021年地级市文化/体育/卫生/社会保障等公共服务相关指标(Excel/Shp格式)

1999-2021年地级市的人口相关数据、各类用地面积数据、污染物排放和环境治理相关数据、房地产投资情况和商品房销售面积、社会消费品零售总额和年末金融机构存贷款余额、地方一般公共预算收支状况、工业企业数、固定资产投资和对外经济贸易数据、科技创新指标数据、劳动力就业及…

一起学算法(枚举篇)

概念 枚举&#xff1a;就是把满足题目条件的所有情况都列举出来&#xff0c;然后一一判断寻找最优解的过程 1.最值问题 1.两个数的最值问题 两个数的最小值&#xff0c;利用Java的运算符就可以实现 int Min(int a,int b){return a<b?a:b; } 2.n个数的最值问题 当有n…

禁用右键菜单AMD Software: Adrenalin Edition

本文参考链接&#xff1a; 删除win11右键一级菜单的AMD驱动栏 - 哔哩哔哩 windows11 求助删除右键菜单方法_windows11吧_百度贴吧 Windows安装最新的AMD显卡驱动后&#xff0c;右键菜单会多出AMD Software: Adrenalin Edition。使用一些右键菜单管理工具也没能屏蔽禁用掉该功…

机器学习---线性回归、多元线性回归、代价函数

1. 线性回归 回归属于有监督学习中的一种方法。该方法的核心思想是从连续型统计数据中得到数学模型&#xff0c;然后 将该数学模型用于预测或者分类。该方法处理的数据可以是多维的。 回归是由达尔文的表兄弟Francis Galton发明的。Galton于1877年完成了第一次回归预测&…

数据分析基础-Excel图表的美化操作(按照教程一步步操作)

一、原始数据 包含月份和对应的销量和产量。 时间销量产量1月60722月38673月28344月58685月67596月72357月61428月24319月556710月243511月122112月2645 二、原始的图表设计-采用Excel自带模板 三、优化思路 1、删除多余元素 2、弱化次要元素 对于可以弱化的元素&#xff0c…

NASM汇编

1. 前置知识 1. 汇编语言两种风格 intel&#xff1a;我们学的NASM就属于Intel风格AT&T&#xff1a;GCC后端工具默认使用这种风格&#xff0c;当然我们也可以加选项改成intel风格 2. 代码 1. 段分布 .text: 存放的是二进制机器码&#xff0c;只读.data: 存放有初始化的…

弯道超车必做好题锦集一(C语言选择题)

前言&#xff1a; 编程想要学的好&#xff0c;刷题少不了&#xff0c;我们不仅要多刷题&#xff0c;还要刷好题&#xff01;为此我开启了一个弯道超车必做好题锦集的系列&#xff0c;每篇大约10题左右。此为第一篇选择题篇&#xff0c;该系列会不定期更新&#xff0c;后续还会…

《EalsticSearch从入门到实战》-CRUD+JAVA常用操作

目录 《EalsticSearch从入门到实战》 windows环境安装elasticsearchkibana并完成JAVA客户端查询《EalsticSearch从入门到实战》-CRUDJAVA常用操作 前言 上一篇《windows环境安装elasticsearchkibana并完成JAVA客户端查询》中我们已经完成了EalsticSearchKibana环境的安装&a…

页面生成图片或PDF node-egg

没有特别的幸运&#xff0c;那么就特别的努力&#xff01;&#xff01;&#xff01; 中间件&#xff1a;页面生成图片 node-egg 涉及到技术node egg Puppeteer 解决文书智能生成多样化先看效果环境准备初始化项目 目录结构核心代码 完整代码https://gitee.com/hammer1010_ad…

web自动化测试,定位不到元素的原因及解决方案

1.动态id定位不到元素 分析原因&#xff1a;每次打开页面&#xff0c;ID都会变化。用ID去找元素&#xff0c;每次刷新页面ID都会发生变化。 解决方案&#xff1a;推荐使用xpath的相对路径方法或者cssSelector查找到该元素。        2.iframe原因定位不到元素 分析原因…

SQL注入之布尔盲注

SQL注入之布尔盲注 一、布尔盲注介绍二、布尔盲注的特性三、布尔盲注流程3.1、确定注入点3.2、判断数据库的版本3.3、判断数据库的长度3.4、猜解当前数据库名称&#xff08;本步骤需要重复&#xff09;3.5、猜解数据表的数量3.6、猜解第一个数据表名称的长度3.7、猜解第一个数据…

【React】关于组件之间的通讯

&#x1f31f;组件化&#xff1a;把一个项目拆成一个一个的组件&#xff0c;为了便与开发与维护 组件之间互相独立且封闭&#xff0c;一般而言&#xff0c;每个组件只能使用自己的数据&#xff08;组件状态私有&#xff09;。 如果组件之间相互传参怎么办&#xff1f; 那么就要…

Python 算法交易实验65 算法交易二三事

说明 对算法交易的一些内容做一些回顾和反思吧。 老规矩&#xff0c;先chat一下 道理说的都对&#xff0c;如果要补充就推荐再看一本书量化交易:如何建立自己的算法交易事业,我觉得这样就比较完整了。 简单来说&#xff0c;把量化当成事业&#xff0c;而不是一种投机&#…

一起学算法(计算排序篇)

概念&#xff1a; 计数排序&#xff08;Counting sort&#xff09;是一个非基于比较稳定的线性时间的排序算法 非基于比较&#xff1a;之前学的排序都是通过比较数据的大小来实现有序的&#xff0c;比如希尔排序等&#xff0c;而计数排序不需要比较数据的大小而进行排序&…

数据结构:谈快速排序的多种优化和非递归展开,以及排序思想归纳

文章目录 写在前面快速排序的基本体系快速排序的优化快速排序的非递归实现排序分类总结插入排序选择排序交换排序归并排序 写在前面 快速排序作为效率相当高的排序算法&#xff0c;除了对于特殊数据有其一定的局限性&#xff0c;在大多数应用场景中都有它特有的优势和应用&…

PHP8的数据类型转换-PHP8知识详解

什么是数据类型转换&#xff1f; 答&#xff1a;数据从一个类型转换成另外一个类型&#xff0c;就是数据类型转换。 在PHP8中&#xff0c;变量的类型就是由赋值决定的&#xff0c;也就是说&#xff0c;如果 string 赋值给 $var&#xff0c;然后 $var 的类型就是 string。之后…

Python:给MySQL创建1000张表和创建1张有50个字段的表

1、创建1000张表 import pymysqldbhost "10.1.1.143" dbuser "root" dbpassword "123456" dbname "demo_cg1000" dbport 3306 dbconn pymysql.connect(hostdbhost, userdbuser, passworddbpassword, dbdbname, portdbport)mycu…

前端学习--vue2--2--vue指令基础

写在前面&#xff1a; 前置内容 - vue配置 文章目录 插值表达式v-html条件渲染v-show和v-ifv-ifv-if的扩展标签复用组件 v-show v-on /事件v-bind /&#xff1a;属性v-modelv-for 循环元素v-slotv-prev-cloak vue指令只的是带有v-前缀的特殊标签属性 插值表达式 插值表达式{…

【MATLAB第62期】基于MATLAB的PSO-NN、BBO-NN、前馈神经网络NN回归预测对比

【MATLAB第62期】基于MATLAB的PSO-NN、BBO-NN、前馈神经网络NN回归预测对比 一、数据设置 1、7输入1输出 2、103行样本 3、80个训练样本&#xff0c;23个测试样本 二、效果展示 NN训练集数据的R2为&#xff1a;0.73013 NN测试集数据的R2为&#xff1a;0.23848 NN训练集数据的…