C++基础语法——多态

news2025/3/10 23:08:00

1.什么是多态?

多态是面向对象编程中的一个概念,它允许不同的对象对同一个消息作出不同的响应。简单来说,多态是指同一种操作或方法可以在不同的对象上产生不同的行为。这种灵活性使得代码更加可扩展和可维护。在多态中,对象的类型可以是其父类或接口的类型,而实际执行的方法则是对象自身的方法。这样,通过多态性,我们可以在不改变代码的情况下,通过替换对象的类型来改变程序的行为。

举个例子,在买票时,同样是买票,不同的人买票有不同的结果,比如普通人去买得到的结果就是全价,而学生去买得到的结果就是半价,退伍军人去买则是免费。 

2.多态的定义与实现

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。比如Student继承了
Person,Person对象买票全价,Student对象买票半价。代码实现如下

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

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

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

void Func2(Person* p)
{
	p->BuyTicket();
}

int main()
{
	Person ps;
	Student st;
	Func1(ps);
	Func1(st);
	Func2(&ps);
	Func2(&st);

	return 0;
}

①多态的构成条件

在继承中要构成多态需要两个条件:

1. 必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写 

②虚函数

虚函数:

即被virtual修饰的类成员函数称为虚函数。

如 

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

中的BuyTicket函数 

③虚函数的重写

虚函数的重写(覆盖):

派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。

(注:在重写基类虚函数时,派生类的虚函数在不加virtual关键字时,虽然也可以构成重写(因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性),但是该种写法不是很规范,不建议这样使用)即在派生类中可以如此书写

void BuyTicket() 
{ 
    cout << "买票-半价" << endl; 
}

但是在重写的时候有几个例外:

1.协变

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指
针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。举个例子

class A 
{};

class B : public A 
{};

class Person 
{
public:
	virtual A* f() 
	{ 
		return new A; 
	}
};

class Student : public Person 
{
public:
	virtual B* f() 
	{ 
		return new B;
	}
};

2.析构函数的重写

为什么需要对析构函数进行重写呢?我们先来看下面这个例子

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

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

int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;
	delete p1;
	delete p2;

	return 0;
}

在这里由于切片,p1和p2指针指向的都是基类这个部分,他指向的有可能是派生类的一部分,也有可能就是基类,那么在释放空间的时候就无法保证做到先析构派生类再析构基类。因此,只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。而为了构成多态,编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。此外,如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写。

④final和override

不难发现,C++对于重写的要求是比较严格的,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写:

1.final

修饰虚函数,表示该虚函数不能再被重写,意思也很明显,就是“最后”的意思,即这之后不能再对该函数进行重写

以下面的代码为例

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

class Benz :public Car
{
public:
	virtual void Drive() { cout << "Benz-舒适" << endl; }
};

 观察有

2.override

检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译则报错

 以下面的代码为例

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

class Benz :public Car 
{
public:
	virtual void Drive() override { cout << "Benz-舒适" << endl; }
};

 观察有

⑤重载、重写(覆盖)与重定义(隐藏)的区别

3.什么是抽象类

在虚函数的后面写上 =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;
}

 抽象类的设计强制重写虚函数,更加体现了接口封装这一特性(即只使用接口而不需要知道内部的操作)。

4.多态的底层原理

①虚函数表

在多态这一部分常常会考下面这类题

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

在32位平台下我们运行发现 那么这是为什么呢?我们经过调试可以发现

在b1这个对象中,除了_b这个成员外,还有一个_vfptr指针,这个指针我们称之为虚函数表指针(vf表示virtual function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表(注:不要与虚基表混淆)。我们再往后分析,既然基类有虚基表指针,那么它的派生类又如何呢?虚表与虚表指针有何作用?当函数变多时虚表中该如何存放?针对这些问题我们对上述代码作出一些改进

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

首先我们观察d,

可以发现

1. d中也有虚表指针,但是这个指针指向的虚表与父类不同,且可以看到在各自的虚表中

2. d对象的Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,因此虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法

3. 而Func2继承下来后是虚函数,因此放进了虚表,Func3也继承下来了,但不是虚函数,所以不会放进虚表,而放进代码段

4. 还可以看到_vfptr本质上是一个函数指针数组,一般而言会在最后一个位置放上nullptr

5. 在这里我们便可以对派生类生成的虚表做一个总结:

        a.先将基类中的虚表内容拷贝一份到派生类虚表中 

        b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 

        c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。

在这里我们还要探究一个具体的问题,就是虚函数与虚表存放的位置是哪里?首先我们要明白,虚函数与一般的成员函数类似都是存放在代码段的,只是这些虚函数的指针被取出存放在了虚表中,那我们具体要如何验证呢?以以下代码为例

int main()
{
	Base b;
	Derive d;

	// 栈区
	int a = 0;
	printf("栈区:%p\n", &a);

	// 堆区
	int* pc = new int[10];
	printf("堆区:%p\n", pc);

	// 静态区
	static int c = 1;
	printf("静态区:%p\n", &c);

	// 常量区
	const char* ch = "hello world";
	printf("常量区:%p\n", ch);

	// 虚表
	// &d:取得Derive类对象d的地址
	//(int*)&d:将Derive类对象的地址强制转换为int* 类型的指针
	//*((int*)&d):对强制转换后的指针进行解引用操作,获取指针所指向的整数值,也就是对象中虚函数表的起始地址
	printf("d虚表地址:%p\n", *((int*)&d));
	printf("b虚表地址:%p\n", *((int*)&b));


	return 0;
}

 在vs中运行可以发现

在g++运行有

那么我们基本可以确定虚表和虚函数类似,就存放在常量区(即代码段位置)

②多态的原理与底层

那我们分析了这么多,那多态到底是怎么做到传Student调用Student的函数,传Person调用Person的函数呢?以下列代码为例

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

1. 观察上图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
2. 观察上图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中
找到虚函数是Student::BuyTicket。
3. 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。

至此,我们就可以明确形成多态的条件:

1. 父类的指针或引用(注:不能是父类对象,因为如果使用切片拷贝,不会拷贝虚表,因为如果拷贝虚表的话,在其他地方使用父类时会产生bug)

2. 虚函数的重写

那么我们再来看看多态的底层,观察它到底是如何运行的,以以下代码为例

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

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

int main()
{
	Base b;
	Derive d;
	Base* ptr1 = &b;
	ptr1->func1();

	Base* ptr2 = &d;
	ptr2->func1();

	return 0;
}

可以看到

满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。 

③动态绑定与静态绑定

1. 静态绑定又称为前期绑定(早绑定),在程序编译期间确定了程序的行为,也称为静态多态,如:函数重载
2. 动态绑定又称后期绑定(晚绑定),是在程序运行期间,根据具体拿到的类型确定程序的具体行为,调用具体的函数,也称为动态多态。如:虚函数的调用

5.继承关系中的虚表

①单继承中的虚表

我们以下列代码为例

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

int main()
{
	Base b;
	Derive d;

	return 0;
}

在调试模式中我们可以看到

可以看到,这里的d对象中只有func1与func2函数,可以认为这里出了一点小bug,我们用以下代码来验证

typedef void(*vfptr)();

void PrintVTable(vfptr* vTable)
{
	// 依次取虚表中的虚函数指针打印并调用。调用就可以看出存的是哪个函数
	cout << " 虚表地址: " << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		vfptr f = vTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Base b;
	Derive d;

	// 思路:取出b、d对象的头4bytes,就是虚表的指针,前面我们说了虚函数表本质
	// 是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr
	// 1.先取d的地址,强转成一个int*的指针
	// 2.再解引用取值,就取到了b对象头4bytes的值,这个值就是指向虚表的指针
	// 3.再强转成vfptr*,因为虚表就是一个存vfptr类型(虚函数指针类型)的数组。
	// 4.虚表指针传递给PrintVTable进行打印虚表
	// 注:需要说明的是这个打印虚表的代码经常会崩溃,因为编译器有时对虚表的处
	// 理不干净,虚表最后面没有放nullptr,导致越界,这是编译器的问题。我们只需
	// 要点目录栏的 - 生成 - 清理解决方案,再编译就好了。
	vfptr* pd = (vfptr*)(*(int*)&d);
	vfptr* pb = (vfptr*)(*(int*)&b);
	PrintVTable(pb);
	PrintVTable(pd);

	return 0;
}

运行如下

可以发现,编译器确实是自动隐藏了Derive后面的几个虚函数

②多继承中的虚表

那么在知道了单继承的虚表后,多继承的虚表又是如何体现的呢?我们以以下代码为例

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 PrintVTable(vfptr* vTable)
{
	cout << " 虚表地址:" << vTable << endl;
	for (int i = 0; vTable[i] != nullptr; ++i)
	{
		printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);
		vfptr f = vTable[i];
		f();
	}
	cout << endl;
}

int main()
{
	Derive d;

	// 找到d中的Base1,打印它的虚表
	vfptr* vTableb1 = (vfptr*)(*(int*)&d);
	PrintVTable(vTableb1);

	// 找到d中的Base2,打印它的虚表
	vfptr* vTableb2 = (vfptr*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);

	return 0;
}

运行结果如下

可以看出,多继承派生类的未重写的虚函数放在第一个继承基类部分的虚表中,而非放在两个虚表中

6.一些继承与多态中的问题

①概念题

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


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


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


4. 以下关于纯虚函数的说法,正确的是( )
A:声明纯虚函数的类不能实例化对象 B:声明纯虚函数的类是虚基类
C:子类必须实现基类的纯虚函数 D:纯虚函数必须是空函数


5. 关于虚函数的描述正确的是( )
A:派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B:内联函数不能是虚函数
C:派生类必须重新定义基类的虚函数 D:虚函数可以是一个static型的函数 


6. 关于虚表说法正确的是( )
A:一个类只能有一张虚表
B:基类中有虚函数,如果子类中没有重写基类的虚函数,此时子类与基类共用同一张虚表
C:虚表是在运行期间动态生成的
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类使用的不是同一张虚表
 

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

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
 

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: 以上都不正确
 

参考答案与解析:

1. A  

继承是在已有的基类上做出改进,进而产生派生类,因此可以变得富有

2. D   

根据描述我们可以知道这里描述的就是多态,根据答案最贴切的就是D选项

3. C   

优先使用组合,而不是继承,是面向对象设计的第二原则

4. A   

B:声明纯虚函数的类是抽象类

C:子类可以不实现基类的纯虚函数

D:纯虚函数可以有具体的实现,也可以为空函数,具体实现取决于需求和设计。

5. B

B:内联函数在编译前就已经展开,无法再将此函数的地址存放在虚表中

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

C:派生类可以不重新定义基类的虚函数

D:虚函数有this指针,而static函数没有this指针,因此虚函数不能是static型的函数

6. D

A:一个类可能有多张虚表,如一个多继承类

B:就算不重写也会生成不同虚表,如下图所示

C:虚表是在编译期间动态生成的

// 代码示例:
class Base
{
public:
	virtual void func1() { cout << "Base::func1" << endl; };
private:
	int b;
};

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

int main()
{
	Base b;
	Derive d;


	return 0;
}

观察有

7. D

A:A类对象的前4个字节存储虚表地址,B类对象前4个字节也是是虚表地址

B:A类对象和B类对象前4个字节存储的都是虚表的地址
C:A类对象和B类对象前4个字节存储的虚表地址不同,因为B类对象重写了虚表

8. A   

运行结果如下

理解:

在构造D类对象时,先构造B类与C类对象,而在构造B类与C类对象时,又会先构造A类对象,因此最后的结果如图所示

9. C   

理解如图所示

首先由于切片,p1指向了d中的Base1的地址,p2指向了d中的Base2的地址,p3则直接指向d的地址,而由于继承关系,Base1的地址与d的首地址重合,因此最终可以得到p1 == p2 != p3

10. B

经过运行可以得到如下结果

理解:

这里是B类对A中的func函数进行了重写,但是重写仅仅只是重写了实现,即调用的是A类的func函数,因此val为1,实现是B类的func函数,因此最后显示B->

②问答题

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

参考答案与解析:

1.

答:多态分为静态多态与动态多态。静态多态指的是函数重载,而动态多态指的是继承中派生类对虚函数的重写和父类指针(引用)的调用。

2.

答:重载是在同一作用域中,函数名相同,而参数或返回值不同;重写是在继承中,基类中某一函数为虚函数,而派生类中也有一个同名虚函数,且函数参数,返回值都与基类中的函数相同;重定义是在继承中,基类与派生类为同名函数,那么派生类就重定义了基类的函数。

3.

答:静态多态是根据C++进行编译时的函数名修饰规则形成不同的符号表来实现的,而动态多态是根据虚函数表来实现的。

4.

答:可以是虚函数,但是这个函数会默认地忽略掉inline的属性,因为如果要是inline函数会在编译时展开,这样就无法将虚函数存放到虚表中了。

5.

答:不能,因为静态函数没有this指针,在使用类::函数时,无法调用虚表,因此无法是虚函数。

6.

答:不能,因为虚表指针是在构造函数的初始化列表阶段产生的。

7.

答:可以,而且最好在基类中就将析构函数设置为虚函数,在如下场景Per为基类,Stu为子类,

Per* p = Stu s; delete p; 这里如果没有将析构函数设置为虚函数,那么最后会导致p不知道自己该调用哪个析构函数。

8.

答:如果是普通对象的调用,那么调用速度没有什么变化;如果是父类的指针(或引用)的调用,那么普通函数的调用速度更快,因为调用虚函数时会在虚函数表中去寻找。

9.

答:在编译阶段,正常情况存储在代码段(常量区)。

10.

答:菱形继承时会造成数据冗余和二义性;虚继承通过形成虚基表来存储偏移量,借此解决数据冗余的问题。

11.

答:抽象类是包含了纯虚函数在内的类;抽象类要求了在派生类中重写纯虚函数,体现出了接口继承关系。

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

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

相关文章

弯道超车必做好题集锦三(C语言编程题)

目录 前言&#xff1a; 1.单词倒排 方法1&#xff1a;scanf匹配特定字符法 方法2&#xff1a; 双指针法 2.统计每个月兔子的总数 方法1&#xff1a;斐波那契数列 方法2&#xff1a;斐波那契的递归 3.珠玑妙算 方法&#xff1a;遍历 4.寻找奇数&#xff08;单身狗&#…

【图解算法数据结构】分治算法篇 + Java代码实现

文章目录 一、重建二叉树二、数值的整数次方三、打印从 1 到最大的 n 位数四、二叉搜索树的后序遍历序列五、数组中的逆序对 一、重建二叉树 public class Solution {int[] preorder;HashMap<Integer, Integer> dic new HashMap<>();public TreeNode buildTree(in…

可视化流程设计平台有啥优势?

在流程化办公发展趋势逐渐明朗的今天&#xff0c;运用什么样的平台可以帮助广大用户朋友实现这一目标&#xff1f;可视化流程设计平台是轻量级、更灵活、易操作、效率高的平台&#xff0c;可以快速定制客户专属的框架平台&#xff0c;为每一位客户朋友做好数据管理&#xff0c;…

【小沐学Unity3d】3ds Max 骨骼动画制作(蒙皮修改器skin)

文章目录 1、简介2、蒙皮修改器3.1 骨骼对象测试3.2 Biped对象测试 3、动画制作4、FBX导出结语 1、简介 “蒙皮”修改器是一种骨骼变形工具&#xff0c;主要设计用于通过另一个对象对一个对象进行变形来创建角色动画。可使用骨骼、样条线和其他对象变形网格、面片和 NURBS 对象…

【Java 基础篇】Java 数组使用详解:从零基础到数组专家

如果你正在学习编程&#xff0c;那么数组是一个不可或缺的重要概念。数组是一种数据结构&#xff0c;用于存储一组相同类型的数据。在 Java 编程中&#xff0c;数组扮演着非常重要的角色&#xff0c;可以帮助你组织、访问和操作数据。在本篇博客中&#xff0c;我们将从零基础开…

如何使用C++11原子操作实现自旋锁

什么是自旋锁&#xff1f; C自旋锁是一种低层次的同步原语&#xff0c;用于保护共享资源的访问。自旋锁是一种轻量级的锁&#xff0c;适用于短时间的资源锁定。 自旋锁的特点&#xff1a;当一个线程尝试获取已经被另一个线程占有的自旋锁时&#xff0c;这个线程会进入一个循环…

从入门到精通,30天带你学会C++【第六天:与或非三兄弟和If判断语句(博主目前最长文章,2514字)】(学不会你找我)

目录 前言 计算机里的真和假 与或非三兄弟 ​编辑与运算&#xff08;&&&#xff09; 具体说明表格&#xff1a; 举个栗子1&#xff1a; 或运算&#xff08;||&#xff09; 具体说明表格&#xff1a; 举个栗子2&#xff1a; 非运算&#xff08;!&#xff09…

python编写MQTT订阅程序

Download | Eclipse Mosquitto 1、下载&#xff1a; https://mosquitto.org/files/binary/win64/mosquitto-2.0.17-install-windows-x64.exe 2、安装&#xff1a; 3、conf配置 1)使用notepad打开“C:\Program Files\mosquitto\mosquitto.conf”另存为c:\myapp\msquitto\mo…

C++的多重继承

派生类都只有一个基类,称为单继承(Single Inheritance)。除此之外,C++也支持多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。 多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。 …

漏洞发现-web应用发现探针类型利用(43)

关于在真实环境下面&#xff0c;这个漏洞该如何发现 这里老师把它分成了三块第一类是 #已知cms 如常见的dedecms&#xff0c;discuz&#xff0c;wordpress等源码结构&#xff0c;这些都是网上比较知名的php源码的cms的名称&#xff0c;这是我们在国内常见的几个程序&#xf…

【Java 基础篇】Java 方法使用详解:让你轻松掌握方法的奥秘

如果你正在学习Java编程&#xff0c;方法是一个不可或缺的重要概念。方法允许你将代码组织成可重用的块&#xff0c;提高了代码的可维护性和可读性。在本篇博客中&#xff0c;我们将深入探讨Java方法的使用&#xff0c;从基础概念开始&#xff0c;逐步介绍如何定义、调用、传递…

Netty-ChannelPipeline

EventLoop可以说是 Netty 的调度中心&#xff0c;负责监听多种事件类型&#xff1a;I/O 事件、信号事件、定时事件等&#xff0c;然而实际的业务处理逻辑则是由 ChannelPipeline 中所定义的 ChannelHandler 完成的&#xff0c;ChannelPipeline 和 ChannelHandler应用开发的过程…

剑指 Offer 44. 数字序列中某一位的数字(中等)

题目&#xff1a; class Solution { //本题单纯找规律&#xff0c;要注意通过n%digits来判断有几个位数为digits的数 public:int findNthDigit(int n) {long base 9, digits 1; //digits代表位数while(n-base*digits>0){ //该循环是为了确定目标数字所在…

找不到msvcp140.dll的解决方法【msvcp140.dll修复工具下载】

今天&#xff0c;我将为大家分享一个与我们日常工作息息相关的话题——msvcp140.dll重新安装的5种解决方法。在接下来的时间里&#xff0c;我将向大家介绍什么是msvcp140.dll,为什么会丢失&#xff0c;以及它的用途。最后&#xff0c;我将为大家提供5种解决方法&#xff0c;帮助…

【人工智能】—_神经网络、前向传播、反向传播、梯度下降、局部最小值、多层前馈网络、缓解过拟合的策略

神经网络、前向传播、反向传播 文章目录 神经网络、前向传播、反向传播前向传播反向传播梯度下降局部最小值多层前馈网络表示能力多层前馈网络局限缓解过拟合的策略 前向传播是指将输入数据从输入层开始经过一系列的权重矩阵和激活函数的计算后&#xff0c;最终得到输出结果的过…

useEffect 不可忽视的 cleanup 函数

在 react 开发中&#xff0c; useEffect 是我们经常会使用到的钩子&#xff0c;一个基础的例子如下&#xff1a; useEffect(() > {// some code here// cleanup 函数return () > {doSomething()} }, [dependencies])上述代码中&#xff0c; cleanup 函数的执行时机有如下…

[dasctf]misc1

不确定何种加密方式 P7NhnTtPUm/L3rmkP/eAhx5Vnbc2YyatkXCePJ0Wh2NYfqXGZCpZdCesMmEAihhUYI1PjoLq6FedZ7MSclA9h0/Dy4CavBwVg5RHr8XJmfbtuWkxK2Gn3sNTEzQi0p 1t_15_s3cR3t_k3y 也许是密钥

html5——前端笔记

html 一、html51.1、理解html结构1.2、h1 - h6 (标题标签)1.3、p (段落和换行标签)1.4、br 换行标签1.5、文本格式化1.6、div 和 span 标签1.7、img 图像标签1.8、a 超链接标签1.9、table表格标签1.9.1、表格标签1.9.2、表格结构标签1.9.3、合并单元格 1.10、列表1.10.1、ul无序…

vmware虚拟机远程开发

目录 1. 下载vmware2. 下载ubuntu镜像3. 安装4. 做一些设置4.1 分辨率设置4.2 语言下载4.3 输入法设置4.4 时区设置 5. 直接切换管理员权限6. 网络6.1 看ip6.2 ssh 7. 本地编译器连接远程服务器7.1 创建远程部署的配置7.2 文件同步7.3 远程启动项目 8. ubuntu安装golang环境8.1…

C++学习笔记总结练习:多态与虚函数

1 多态 多态分类 静态多态&#xff0c;是只在编译期间确定的多态。静态多态在编译期间&#xff0c;根据函数参数的个数和类型推断出调用的函数。静态多态有两种实现的方式 重载。&#xff08;函数重载&#xff09;模板。 动态多态&#xff0c;是运行时多态。通过虚函数机制实…