一、实验目的
1. 掌握多态性的分类;
2. 动态多态性-虚函数;
3. 理解纯虚函数的概念。
二、实验任务
1.分析以下程序,改正程序错误,写出程序输出结果,并按要求:
(1)思考:输出结果中为什么类A是8个字节,类B是12个字节?分析虚函数的类的结构特点
A的字节大小为: 4(int)+4 (v_ptr)=8 B的字节大小为: 4(int)+4(int)+4 (v_ptr)=12
(2)回答程序中标注的思考题1-5,总结虚函数的调用为什么要用基类对象指针?基类对象指针与派生类对象指针的使用区别是什么?
为什么要用基类对象指针:
当一个派生类对象赋值给基类指针或引用的时候,该指针或引用将指向派生类对象中的虚函数表指针。
在程序执行时,通过基类指针或引用调用虚函数,会调用派生类中的该函数,这种行为被称为动态绑定或动态多态性。
基类对象指针与派生类对象指针的使用区别:
可以将一个父类指针指向一个子类对象,但不能子类指针能否指向父类对象,因为子类中有些信息父类没有,如果用子类指针访问的时候很可能访问到父类没有的一些属性及函数,会出错!
#include <iostream>
using namespace std;
class A
{
public:
int a;
A() {a=2;}
int geta() {return a; }
virtual void show() {cout<<"a="<<a<<endl; }
};
class B:public A
{
public:
int b;
B(){b=6;}
int getb(){ return b; }
void show()
{
A::show();
cout<<"b="<<b<<endl;
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
A first,*p1;
B second,*p2;
cout<<"sizeof(A)="<<sizeof(A)<<" sizeof(p1)="<<sizeof(p1)<<endl; //思考:输出结果?
cout<<"sizeof(B)="<<sizeof(B)<<" sizeof(p2)="<<sizeof(p2)<<endl;
p1=&second; //基类对象指针指向派生类对象
p1->show(); //思考1:此时指针调用的是派生类中的show()函数还是基类中的?
cout<<p1->geta()<<endl; //思考2:程序是否能够正确输出?得出什么结论?
cout<<p1->getb()<<endl; //思考3:程序是否能够正确输出,若不能正确输出,为什么?
p2=&second; //派生类对象指针指向派生类对象
cout<<p2->geta()<<endl; //思考4:程序是否能够正确输出?得出什么结论?
cout<<p2->getb()<<endl; //思考5:程序是否能够正确输出,若不能正确输出,为什么?
return a.exec();
}
实验思考题回答与结果分析:
(1)在X86的条件下,sizeof:A为8 sizeof:B 为4;
(2)此时调用的是派生类的show()函数
(3)程序可以正常输出,p1为基类指针,可以访问基类的非虚构函数
(4)不能,p1为基类指针不能访问派生类中的非虚函数
(5)程序能够正确输出,p2为派生类指针。继承了基类中的geta()非成员函数,可以直接调用
(6)程序能够正确输出,p2可以直接getb()非成员函数
2、分析以下程序,写出程序运行结果,说明为什么要把析构函数声明为虚函数,要求:
- 分别将析构函数定义为虚函数,非虚函数两种情况调试程序。
当将析构函数定义为虚函数,基类与派生类函数全都得到了析构
将析构函数定义为非虚函数,派生类没有析构
- 思考如果把A类中的析构函数前的virtual去掉(即不声明为虚析构函数),通多基类对象指针是否会正确释放派生类对象所占内存?
不能
#include <iostream>
using namespace std;
class A
{
public:
A(){}
virtual~A()
{
cout<<"Delete class A\n";
}
};
class B : public A{
public:
B(){}
~B()
{
cout<<"Delete class B\n";
}
};