C++多态

什么是多态?

首先看语法,满足两个条件:虚函数的重写基类指针或引用

1.虚函数的重写:以下代码继承函数前加virtual关键字。(继承要求三同:函数名,参数和返回值的类型)

2. 基类指针或引用:以下代码“A* pa = &bb;”

#include<iostream>

using namespace std;

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

class B : public A
{
public:
	virtual void func1() {
		cout << "B::void func1()" << endl;
	}
	virtual void func2() {
		cout << "B::void func2()" << endl;
	}
};
intmain()
{
    A aa;
	B bb;

	A* pa = &bb;
	pa->func1();
    return 0;
}

再看原理

子类继承时,从更好理解的角度看,先把父类的虚表copy下来,再把需要重写实现的虚函数地址覆盖过去,得到子类的虚表。(那什么是虚表呢?实际上是一个函数指针数组,里面存虚函数 的地址。)

把子类的指针或引用给父类指针或引用,形成切片,此时子类的虚函数表会copy给父类指针,这样就会调用子类自己的某个虚函数。同时一般的父类指针或引用可以调用父类自己的虚函数,这样一来,父类的指针或引用可以调用子类和父类的续保函数,这就叫多态。

那为什么不把子类对象直接赋值给父类,而绕一点,用指针和引用呢?

很简单,看以下代码

void test1()
{
	A aa;
	B bb;

	aa = bb;
	aa.func1();//结果是调用A的func1,可见对象的切片不会拷贝虚表
			   //若拷贝,则父类对象可以调用子类的函数,不合理,但父类指针可以
} 

void test2()
{
	A aa;
	B bb;

	A* pa = &bb;
	pa->func1();//结果是调用B的func1,可见指针的切片会拷贝覆盖A父类的虚表
}

可见,对象的截断并不会改变虚表,只会改变一般的成员变量。这也很合乎逻辑,因为若果和指针一样,把虚表copy过去,那么父类对象既可以调用自己的函数,也可以调用子类的函数,这显然是不合理的。(C++语法规定,父类只能调用自己的函数,子类可以调用父类)

接下来来到第二个问题:虚表存放在哪里?

以下代码可以验证

#include<iostream>

using namespace std;

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

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

	int* b = new int;
	printf("堆:%p\n", b);

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

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

	A aa;
	printf("虚表A:%p\n", *((int*)&aa));//对象里面头四个字节存的是虚表地址
										//(int*)&aa是对象的地址,强转为int*,取前4个字节得到对象(本质是函数指针数组,里面存虚函数的地址)的地址,
										//虚表地址是对象指针指向的第一个内容,解引用下得到虚表地址

	B bb;
	printf("虚表B:%p\n", *((int*)&bb));

	return 0;
}

显而易见,虚表地址理常量区最近,故存在常量区。这也很合理,因为虚表生成后是不可改变的。

再补充一句,子类的新虚函数地址会加在自己的虚表末尾,但是vs2019的监视窗口看不见。

最后看多继承的虚表

多继承会继承多张虚表,但是监视窗口看不见cc新加的func3,于是我设计了以下代码调用两张表上的函数。


#include<iostream>

using namespace std;

typedef void(*VFT_PTR)();

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

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

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

void PrintVFT(VFT_PTR* table)
{
	for (int i = 0; table[i] != nullptr; ++i)
	{
		cout << table[i] << "->";
		VFT_PTR f = table[i];
		f();
	}
}

int main()
{
	C cc;

	int vft1 = *((int*)&cc);
	cout << "从A那继承的虚表" << endl;
	PrintVFT((VFT_PTR*)vft1);

	B* pb = &cc;
	int vft2 = *((int*)pb);
	cout << "从B那继承的虚表" << endl;
	PrintVFT((VFT_PTR*)vft2);

	return 0;
}

 

显而易见,C新加的func3会添加在第一张虚表。 

然而新的问题又来了,cc的func1,func2只有一个,但是两张表里有两个分别有两个func1,func2,按理来相同的函数只有一份,怎么回事呢?

其实是从第二份虚表调用func1时,编译器会把fun1的地址进行加减,变得和第一份虚表的地址一样,然后调用同一个func1。为什么要这么复杂呢?调用成员虚函数不仅需要虚表,还需要this指针,从第一份虚表调用func1,this指针刚好一样,所以直接传过去就好了,(这也是单继承的this指针传参方法)而第二份虚表调用func1时,this指针值不一样了,所以要进行调整,加减几个字节,再传过去,和func1调用是一个道理。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值