C++ 多态&虚函数表 总结

首先我们来简单介绍下多态,多态顾名思义是多种形态的意思。在C++中多态又分为静态多态和动态多态。静态多态中包括重载和泛型编程,是在编译期间完成,编译器根据实参类型(可能会隐式类型转化)确定调那个函数。应注意:宏替换是在预处理期间完成所以不是静态多态。而动态多态是虚函数的调用,编译器在执行期间判断所引用对象的实际类型。

动态绑定的条件:

1.基类必须是虚函数并且在派生类中重写

2.通过基类的指针或引用调用虚函数。

继承体系同名成员函数的关系:

1.重载:在同一作用域中,函数名相同,参数列表不同,返回值可以不同。

2.重写(覆盖):

(1)不在同一作用域(基类和派生类)

(2)函数原型一模一样(函数名,参数,返回值(协变除外))

(3)基类必须是虚函数

(4)访问修饰符可以不同

几点注意:

(1)构造函数不能作为虚函数。因为虚函数是通过对象调用,而对象还没有创建完整虚表地址就无法填入因而就不能调虚函数。

(2)静态成员函数不能作为虚函数,因为虚函数通过对象拿到虚表地址,而静态成员不属于任何对象。

(3)赋值运算写成非虚函数比较好。因为赋值运算本来就是同类型赋值,如果写成虚函数对象多开辟四个字节浪费空间和时间。

(4)友元函数不是类的成员函数因此无法拿到虚表指针。也不能作为虚函数。

(5)析构函数最好给成虚函数,可以先看下面的代码

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

class D:public B
{
public:
	~D()
	{
		cout<<"~D()"<<endl;
	}
};

int main()
{
	B* pb = new D;//创建一个基类的指针指向派生类
	delete pb;
    
	system("pause");
	return 0;
}

这段代码运行结果是调用了基类析构函数~B(),这样做会使D类空间没有释放,会导致内存泄漏,所以析构函数一般给成虚函数。

(6)如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加。

(7)不要在构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为。

纯虚函数:在成员函数的形参列表后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。纯虚函数在派生类中重新定义以后,派生类才能实例化出对象。下面给出一个例子

class Base
{
public:
	virtual void FunTest()=0;//纯虚函数
};

class Derived:public Base
{
public:
	virtual void FunTest()
	{

	}//重定义之后派生类就成为普通的类而不是抽象类
};
int main()
{
	//Base b;
	Derived d;
	return 0;
}
虚表剖析:

class Base
{
public:
	virtual void FunTest1()
	{
		cout<<"B::FunTest1()"<<endl;
	}
	virtual void FunTest2()
	{
		cout<<"B::FunTest2()"<<endl;
	}
	virtual void FunTest3()
	{
		cout<<"B::FunTest3()"<<endl;
	}
};
class Derived:public Base
{
public:
	virtual void FunTest1()
	{
		cout<<"D::FunTest1()"<<endl;
	}
	virtual void FunTest2()
	{
		cout<<"D::FunTest2()"<<endl;
	}
	virtual void FunTest3()
	{
		cout<<"D::FunTest3()"<<endl;
	}
	int _d;
};
typedef void(*Fun)();//定义一个函数指针类型

void FunTest()
{
	Base b;
	Fun* pFun = (Fun*)*(int*)&b;
	while(*pFun)
	{
		(*pFun)();
		pFun = (Fun*)((int*)pFun+1);
	}
	
}
int main()
{
	//Derived d;
	//d.FunTest1();

	FunTest();
	system("pause");
	return 0;
}

上面这个图是Base类的对象模型,在Base类中多了四个字节,里面存放了一个虚表指针,指向一个虚函数表,表中存放了虚函数的地址。

下面是一个我们容易出错的小问题:

        Base* pb = new Base;
	pb->FunTest1();
	pb = (Base*)new Derived;//与强转无关只和当前指向对象有关
	pb->FunTest1();
下面再来看一段程序:
class Base
{
public:
	virtual void FunTest1()
	{
		cout<<"B::FunTest1()"<<endl;
	}
	virtual void FunTest2()
	{
		cout<<"B::FunTest2()"<<endl;
	}
	/*virtual */void FunTest3()
	{
		cout<<"B::FunTest3()"<<endl;
	}
	virtual void FunTest4()
	{
		cout<<"B::FunTest4()"<<endl;
	}
};
class Derived:public Base
{
public:
	virtual void FunTest1()
	{
		cout<<"D::FunTest1()"<<endl;
	}
	virtual void FunTest2()
	{
		cout<<"D::FunTest2()"<<endl;
	}
	virtual void FunTest3()
	{
		cout<<"D::FunTest3()"<<endl;
	}
	virtual void FunTest4()
	{
		cout<<"D::FunTest4()"<<endl;
	}
	int _d;
};
typedef void(*Fun)();

void FunTest(Base& b)
{
	Fun* pFun = (Fun*)*(int*)&b;
	while(*pFun)
	{
		(*pFun)();
		pFun = (Fun*)((int*)pFun+1);
	}
	
}
int main()
{
	Base b;
	Derived d;
	FunTest(b);
	system("pause");
	return 0;
}

根据上面的一些验证我们可以知道:

虚表形成:

基类:和虚函数在基类中出现次序一致。

派生类:先拿一份基类虚函数(派生类重写了就替换基类的)新加的出现在基类虚函数后边。

总结:

多态调用(必须是虚函数)

1.取虚表指针

2.取该虚函数在虚表中的偏移量

3.调用虚函数


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值