一、基于虚函数(virtual)的多态
#include <iostream>
using namespace std;
class CWater
{
public:
void Show()
{
cout << "CWater :: Show" << endl;
}
};
class CBeer : public CWater
{
public:
void Show()
{
cout << "CBeer :: Show" << endl;
}
};
class CMilk : public CWater
{
public:
void Show()
{
cout << "CMilk :: Show" << endl;
}
};
class CCoco : public CWater
{
public:
void Show()
{
cout << "CCoco :: Show" << endl;
}
};
//==============================================================
void Bottle(CWater *p)
{
p->Show();
}
int main()
{
CBeer *beer = new CBeer;
CMilk *milk = new CMilk;
CCoco *coco = new CCoco;
Bottle(beer);
Bottle(milk);
Bottle(coco);
system("pause");
return 0;
}
输出结果为:
出现了上节所提到的问题:父类无法调用子类的成员函数。
解决方式为:多态—虚函数的使用
输出结果为:
虚函数:通过父类的指针调用实际的子类的成员函数,前提是子类中需要重写这个函数,重写是名一样,参数列表也一样
如上图所示,只能输出第一个函数Show(),因为它与父类函数名一样,参数列表也一样。
多态提高了复用性和扩展性。
https://blog.csdn.net/qmroom/article/details/3085356
二、多态的实现原理
★调用函数的两种方式:
#include <iostream>
using namespace std;void Show(int a)
{
cout << "AA" << a << endl;
}int main()
{
//函数名(); //调用函数的方法—>功能是固定的
Show(1);//(*函数指针名)(); //调用函数指针的方法—>功能是可变的,可扩展的
void (*p)(int a); //定义一个函数指针
p = &Show;//给函数指针赋值
(*p)(2);
system("pause");
return 0;
}
//========================================================================
定义一个父类,函数参数为空,输出长度为1。
#include <iostream>
using namespace std;
class CFather
{
public:
void AA()
{
cout << "CFather :: AA" << endl;
}
};
int main()
{
cout << sizeof(CFather) << endl; //输出长度为1
system("pause");
return 0;
}
加上虚函数,再次运行,输出长度为4,是一个函数指针。
//=======================================================================
在父类中定义三个函数,两个虚函数AA和BB,普通函数CC,主函数中定义函数指针,并开辟父类空间
#include <iostream>
using namespace std;
class CFather
{
public:
virtual void AA()
{
cout << "CFather :: AA" << endl;
}
virtual void BB()
{
cout << "CFather :: BB" << endl;
}
void CC()
{
cout << "CFather :: CC" << endl;
}
};
int main()
{
cout << sizeof(CFather) << endl;
CFather*p = new CFather;
system("pause");
return 0;
}
在system("pause");处下断点,可以发现,父类指针p是一个函数指针,其中包括虚函数AA和BB (都是父类的)
//========================================================================
接上文,再设计一个子类,包括三个函数,两个虚函数(AA和DD),一个普通函数(CC);并改为开辟子类空间。
#include <iostream>
using namespace std;
class CFather
{
public:
virtual void AA()
{
cout << "CFather :: AA" << endl;
}
virtual void BB()
{
cout << "CFather :: BB" << endl;
}
void CC()
{
cout << "CFather :: CC" << endl;
}
};
class CSon : public CFather
{
virtual void AA()
{
cout << "CFather :: AA" << endl;
}
void CC()
{
cout << "CFather :: CC" << endl;
}
virtual void DD()
{
cout << "CFather :: DD" << endl;
}
};
int main()
{
cout << sizeof(CFather) << endl;
CFather*p = new CSon;
system("pause");
return 0;
}
在system("pause");处下断点,可以发现,父类指针p是一个函数指针,其中包括虚函数AA和BB (AA是子类的;BB是父类的,因为子类中未定义虚函数BB;之所以没有虚函数DD,是因为定义的是父类指针,只能看到父类成员的变化)
总结:
每个类中定义的多个函数构成了一个虚函数列表,开辟父类空间则vfptr调用父类虚函数列表,开辟子类空间则vfptr调用子类虚函数列表。若子类重写了父类的函数,则重写的覆盖了父类函数,开辟子类空间,输出为重写的。虚函数输出长度为4,多出来的就是vfptr,它标记了对象调用哪个虚函数列表。
//========================================================================
★怎样取出虚函数列表里的内容?
CFather*p = new CSon;//p指向对象空间的首地址,但只想要前四个字节(vfptr)的首地址
对象前4个字节vfptr(是一个二级指针)装了数组的首地址,数组的每个元素装了函数的地址,那么,怎样拿到函数的地址?
注意:函数指针不能进行地址偏移,因为函数几个字节不知道。每个指针占4个字节,只要从首地址向后偏移4个字节就能拿到下一个元素。
*(int*)p代表数组的首地址 ,因为地址的类型就是函数的类型(里面每个元素都是函数指针,不能做地址偏移),因此将它当作整型地址,可以向后偏移4个字节,((int*)*(int*)p+1)得到每个元素的地址。*((int*)*(int*)p+1)就取得这个地址对应的内存空间的内容,这个内容就是函数的地址。
#include <iostream>
using namespace std;
class CFather
{
public:
virtual void AA()
{
cout << "CFather :: AA" << endl;
}
virtual void BB()
{
cout << "CFather :: BB" << endl;
}
void CC()
{
cout << "CFather :: CC" << endl;
}
};
class CSon : public CFather
{
virtual void AA()
{
cout << "CFather :: AA" << endl;
}
void CC()
{
cout << "CFather :: CC" << endl;
}
virtual void DD()
{
cout << "CFather :: DD" << endl;
}
};
int main()
{
cout << sizeof(CFather) << endl;
CFather*p = new CSon;//p指向对象空间的首地址,但只想要前四个字节(vfptr)的首地址
typedef void (*PFUN)();
PFUN aa = (PFUN)*((int*)*(int*)p+0);//取得这个地址前4个字节装的内容,这个内容是虚函数列表的首地址。那怎样拿出每个元素呢?
PFUN bb = (PFUN)*((int*)*(int*)p+1);
PFUN cc = (PFUN)*((int*)*(int*)p+2);
PFUN dd = (PFUN)*((int*)*(int*)p+3);
(*cc)();
system("pause");
return 0;
}
本节详见视频12-1
三、虚析构
虚析构:通过父类的指针完整删除一个子类的对象,防止内存泄漏。
#include <iostream>
using namespace std;
class CFather
{
public:
CFather()
{
cout << "CFather" << endl;
}
virtual ~CFather()
{
cout << "~CFather" << endl;
}
};
class CSon : public CFather
{
public:
CSon()
{
cout << "CSon" << endl;
}
~CSon()
{
cout << "~CSon" << endl;
}
};
int main()
{
{
CFather *son = new CSon;
delete son;
}
system("pause");
return 0;
}
输出结果为:
总结:
1、什么是多态?
父类的指针指向一个子类的对象,然后通过父类的指针调用这个实际的子类的成员函数,这种技术使父类的指针有多种形态。
2、多态是基于什么来实现的?
多态是基于虚函数实现的,虚函数要基于重写才可以。
3、虚析构
虚函数构造前提是:子类中需要重写这个函数,名一样,参数列表也一样。但是虚函数为什么不需要重写?因为子类重写函数是为了确定要调用哪一个,但析构函数只有一个,只要获得了析构函数的地址,那么无需重写也能实现虚析构。
4、虚函数实现多态的原理?
1)最基本的是函数指针
2)需要维护一个由函数指针组成的虚函数列表(vtable),还需要一个记录使用哪个列表的指针(vfptr)。
3)当调用一个虚函数,通过vfptr找到对应的vtable,调用表里的函数指针,如果在子类中重写了,表中装的就是子类的函数(覆盖),调用的也就是子类。
4)vtable每个类包含一个,编译器存在,vfptr是在创建对象的时候存在的,每个对象有一个,在构造函数里初始化,指向对应的虚函数列表
5、多态的优缺点?
优点:提高复用性和扩展性
缺点:1、空间 : 虚函数会占用虚函数列表的空间,虚函数类越多,列表就越多,占用空间就越 多。
2、效率:普通函数通过函数名和函数地址就能调用这个函数;
虚函数要通过指针vfptr找到虚函数列表vtable,再找到里面对应的函数指针才能调用函 数。间接过程,效率比普通函数低一些。
3、安全性:只要知道对象首地址,就可以取出指针vfptr,拿到虚函数列表,调用里面的函 数。如果有一些函数是私有的,不允许使用的,但它又是个虚函数,那么也能调用里面的 函数,因此安全性低。
6、设计原则之一:开闭原则——对扩展是开放的、对修改是关闭的,即在不修改一个软件实体的基础上去扩展其功能。
(6条消息) 设计原则:面向对象设计原则详解_hguisu的博客-CSDN博客_面向对象设计
本节详见12-2视频