C++多态及多态模型

C++ 
多态及多态模型 

一、名词解释

1)虚函数:类的成员函数前面加上virtual关键字,则这个成员函数是虚函数;虚函数是存在代码段的,虚函数的指针是存在虚表中的;(注意:必须是类的成员函数,静态成员函数不能定义成虚函数)

2)

重写(覆盖:在子类中定义了一个和父类中完全相同的成员函数(函数名,参数,返回类型完全相同,但协变除外)

协变:子类和父类有同名且参数相同的函数,返回类型即:父类的返回类型为父类的指针或引用,子类的返回类型为子类的指针或引用;

 

重定义(隐藏):在子类中定义了一个和父类同名的成员(成员变量,成员函数);这时在子类中要调用父类的此成员,需显示调用;

 

重载:在同一作用域内,函数名相同,参数不同,返回类型可同可不同(根据平台确定,在Windows平台上返回类型可以相同,在linux平台上返回类型不同);注意:c语言不支持函数重载

二、什么是多态?

“多态”顾名思义“多种形态”;具体来讲就是当用基类的指针或引用调用重写的虚函数时,当指向的是父类对象时,调用的就是父类的虚函数,指向子类对象时,调用的就是父类的虚函数。

三、如何形成多态?

1、虚函数的重写;

2、使用基类的指针或引用调用重写的虚函数;

四、如何实现多态

1、虚表:虚函数表是通过一块连续内存来存储虚函数的地址。这张表解决了继承、虚函数(重写)的问题。在有虚函数的对象实例中都存在一张虚函数表,虚函数表就像一张地图,指明了实际应该调用的虚函数。

2、查看虚表

 


由上图可看出a对象中,有一个_vfptr的指针数组;便是a的虚表,里面存了虚函数f1(),f2()的地址;

 

3、单继承对象模型

 

4、多继承对象模型

typedef void(*VFUNC) ();
void PintVtable(int VTable)
{
	printf("虚表:0x%p\n",VTable);
	int* VArray = (int*)VTable;
	for(size_t i = 0 ;VArray[i] != 0 ;i++)
	{
      printf(" 第%d个虚函数地址 :0x%p,->", i , VArray[i]);
      VFUNC f = (VFUNC)VArray[i];
      f();
	}
	cout<<endl;
}
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;
};
void TestMu()
{
   Base1 B1;
   Base2 B2;
   Derive d;
   PintVtable(*((int*)(&d)));
   PintVtable(*((int*)((char*)(&d)+sizeof(B1))));
}

 

5、菱形继承

单继承:一个类只有一个直接父类
多继承:一个类有两个或两个以上直接父类

6、菱形的虚拟继承

class A
{
public:
	virtual void fun1()
	{
		cout<<"A::fun1()"<<endl;
	}
public:
	int a;
};
class B :virtual public A
{
public:
	virtual void fun1()
	{
		cout<<"B::fun1()"<<endl;
	}
	virtual void fun2()
	{
		cout<<"B::fun2()"<<endl;
	}
public:
	int b;
};
class C:virtual public A
{
public:
	virtual void fun1()
	{
		cout<<"C::fun1()"<<endl;
	}
	virtual void fun3()
	{
		cout<<"C::fun3()"<<endl;
	}
public:
	int c;
};
class D:public B,public C
{
public:
	virtual void fun1()
	{
		cout<<"D::fun1()"<<endl;
	}
	virtual void fun2()
	{
		cout<<"D::fun2()"<<endl;
	}
public:
	int d;
};
void Test()
{
	D d;
	d.A::a = 0;
	d.B::a = 1;
	d.C::a = 5;
	d.b = 2;
	d.c = 3;
	d.d = 4;
    PintVtable(*((int*)(&d)));
    PintVtable(*((int*)((char*)(&d)+12)));
    PintVtable(*((int*)((char*)(&d)+28)));
    


}





四、总结

1、子类重写父类的虚函数,要求函数名,参数列表,返回类型完全相同(协变除外)

2、父类中定义了虚函数,在派生类中该函数始终保持虚函数的特性(即在子类该虚函数的virtual关键字可以省略,但最好写上)

3、如果在类外定义虚函数,只能在声明此函数时加virtual;

4、构造函数不能定义成虚函数(此时对象还不完整);

5、不要在构造函数和析构函数里面调用虚函数,在构造函数和析构函数中,对象可能是不完整的,可能会出现未定义的行为;

6、最好将基类的析构函数定义成虚函数;(此时父类的析构函数和子类的析构函数构成重写);

举一个例子说明这个问题:

#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"A::A()"<<endl;
}
~A()
{
cout<<"A::~A()"<<endl;
}
};
class B:public A
{
public:
  B()
{
        _a = new int[10];
cout<<"B::B()"<<endl;
}
~B()
{
delete[] _a;
cout<<"B::~B()"<<endl;
}
private:
int* _a;
};
int main()
{
A* p = new B();
delete p;
   cout<<endl;
}
在上面的代码中,没有将父类的析构函数定义成虚函数,当使用 delete 时,只调用了父类的析构函数,而没有调用子类的析构函数,在上面的例子中,导致了内存泄漏;

而将父类的析构函数定义成虚函数时;

 

这样便实现了多态,调用的是子类的析构函数(注意:子类的析构函数会自动调用父类的构造函数),这样可以避免内存泄漏问题;

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值