C++——多态

C++的三大件的最后一件——多态

目录

一多态

1概念

2构成条件 

3重写(覆盖)

4C++的override和final

5三重的对比 

二多态的原理

1虚函数表

 2单继承中的虚函数表

3虚表的位置

4多继承中的虚函数表(了解)


一多态

1概念

通俗来说:多种形态;

具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态

比如买票这个行为:

当普通人买票时,是全价买票;学生买票时,是半价买票;军人买票时是优先买票。

2构成条件 

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

 那么:虚函数重写又是什么呢?

3重写(覆盖)

子类对象对父类的虚函数进行重写必须要满足三点(三同):函数名相同,参数相同,返回值相同 

以购票为例:

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 ps;
	Student st;
	Func(ps);
	Func(st);
	return 0;
}

 但重写有两个例外:

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派生类重写虚函数可以不加virtual 

可以理解为派生类继承了基类的virtual;但还是建议加上! 

3.析构函数的重写(基类与派生类析构函数的名字不同

在最后处理的时候,编译器把父类与子类的析构函数统一处理成destructor函数名构成重写

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

class Student : public Person {
public:
	virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生类Student的析构函数重写了Person的析构函数,下面的delete对象调用析构函数,才能构成多态,才能保证p1和p2指向的对象正确的调用析构函数。
int main()
{
	Person* p1 = new Person;
	Person* p2 = new Student;

	delete p1;
	delete p2;
	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()
{
	B* p = new B;
	p->test();
	return 0;
}

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

正常理解:A,B对象的fuc构成重写,p->test()去调用func()时会去调用B对象的fuc,所以选D

但是:答案选的是B:这就很怪^?^ 

原因:虚函数重写的是函数体的实现,其它部分是复用父类的(val用的是父类的缺省值)

4C++的override和final

由于构成重写的条件相对严格,而够不够成重写编译器都不会报错,C++会我们提供了final和override来检查是否构成重写;

final:修饰虚函数,表示该虚函数不能再被重写
 

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

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

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

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

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

5三重的对比 

重载、重写(覆盖)、重定义(隐藏)的对比: 

二多态的原理

1虚函数表

// 这里常考一道笔试题:sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};

可能你会说:不就只有_b变量吗?我们通过监视窗口来观察: 

 除了_b变量,还存在着这个指针,那这个指针是什么?指向的内容又是什么?

对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function),简称虚表指针
要了解指向的内容,我们通过下面的代码来演示:(满足多态的情况)

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

private:
	int _i = 1;
};

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

	int _j = 2;
};

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

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

	Person p1;
	Func(&p1);

	Student Johnson;
	Func(&Johnson);

	return 0;
}

1.p指针指向的内容可以说都是父类的对象模型(子类也是,只不过子类重写了父类的那部分)

2.同类型的虚表是共享的;而不同类型的虚表是不同的

3.当满足多态的条件时,就会去指向对象的虚表中找到对应的虚函数进行调用

(这是在运行时就已经确定的)

 

 而不满足多态时:在编译链接时就根据对象来类型,确定调用函数

(上面p引用改为p指针就不满足多态了)

 2单继承中的虚函数表

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

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

在上面的代码中,d对象会拷贝Base类型中的虚表,重写func1()的虚函数(覆盖);

然后将自己的虚函数依次填到虚表中;但通过监视窗口我们可以看到:

fuc3()和fuc4()没有写进虚表中,但打开内存窗口观察却(疑似)有?

(要明白:虚函数表本质上是函数指针数组)

我们就要想办法来打印出虚函数表:

typedef void(*VFPTR)();//函数指针数组要先进行typedef

void PrintVFPTR(VFPTR* ptr)
{
	for (int i = 0; i < 4; i++)
	{
		printf("%p->", ptr[i]);//打印地址

		VFPTR Ptr = ptr[i];
		(*Ptr)();//根据地址进行调用
	}
}

int main()
{
	Base b;
	Derive d;
	//打印b中虚函数表
	//int ptr = (int)d;//不支持强制
	VFPTR* ptr = (VFPTR*)(*( (int*)(&d) ));//取地址进行强转
	PrintVFPTR(ptr);

	return 0;
}

不仅把地址给打印出来,还把地址所在的位置的虚函数给打印出来了,更好的进行证明!!

3虚表的位置

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

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

int main()
{
	int i = 0;
	static int j = 1;
	int* p1 = new int;
	const char* p2 = "xxxxxxxx";
	
	Person p;
	Student s;
	Person* p3 = &p;
	Student* p4 = &s;

	printf("栈:%p\n", &i);
	printf("静态区:%p\n", &j);
	printf("堆:%p\n", p1);
	printf("常量区:%p\n", p2);
	printf("Person虚表地址:%p\n", *(int*)p3);
	printf("Student虚表地址:%p\n", *(int*)p4);

	return 0;
}

从打印结果可以看出:

虚表存在常量区中;要注意:对象里面存的是虚表的地址,而不是虚表!!

4多继承中的虚函数表(了解)

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;
	
	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
	PrintVTable(vTableb1);

	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
	PrintVTable(vTableb2);
	return 0;
}

通过d对象的模型来进行分析: 

如果继承的父类中有虚表:子类中的虚函数就放到先继承的父类的虚表,自己就不在创建虚表!! 

而多继承中的菱形继承也是类似的: 

//菱形虚拟继承
class A
{
public:

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

	int _a;
};

//class B : public A
class B : virtual public A
{
public:
	virtual void func2() { cout << "B::func2" << endl; }

	int _b;
};

//class C : public A
class C : virtual public A
{
public:
	virtual void func3() { cout << "C::func3" << endl; }

	int _c;
};

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

	int _d;
};

int main()
{
	D d;
	//cout << sizeof(d) << endl;

	//菱形继承的对象模型跟多继承类似
	d.B::_a = 1;
	d.C::_a = 2;
	d._b = 3;
	d._c = 4;
	d._d = 5;

	return 0;
}

通过内存窗口来分析: 

C++中有了多继承,就有了菱形虚拟继承,实现与理解成本非常高,一般在实践中很少用到 

以上便是在学习多态中的一些相关内容:有问题欢迎在评论区指出,感谢!! 

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值