多态的概念
多态顾名思义就是一个事物具有多种形态,在面向对象方法中一般是这样描述多态的:向不同的对象发送同一个消息,不同的对象在接受时会 产生不同的行为(即方法)。
在C++中多态又分为静态多态和动态多态。
静态多态:它是通过函数重载来实现的,要求程序编译时就知道函数的全部信息,在程序编译时就能决定要调用的是哪个函数,因此又称编译时的多态性。
动态多态:不再编译时确定调用哪个函数,而是在程序运行过程中才动态的确定操作所针对的对象。因此又称运行时多态。
下面我们来看一段代码,来探讨一下我们C++为什么要引入多态呢?
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdlib.h>
using namespace std;
class Base
{
public:
Base()//基类的构造函数
{}
void Display()
{
cout<<"Base::Display()"<<endl;
}
};
class Derived:public Base
{
public:
Derived()//派生类构造函数
{}
void Display()
{
cout<<"Derived::Display()"<<endl;
}
};
void FunTest()
{
Base b;//定义基类对象
Derived d;//定义派生类对象
Base *pb = &b;//定义基类指针指向b
pb->Display();//输出基类对象b中的数据
pb = &d;//定义基类指针指向d
pb->Display();//希望输出派生类对象d中的数据
}
int main()
{
FunTest();
system("pause");
return 0;
}
由以上结果可以看出利用基类指针pb指向对象d再调用pb->Display()试图输出d的数据信息是做不到的。假如想要输出d的全部数据成员,可以通过对象名调用,如d.Display()或者定义一个派生类的指针pd用pd->Display()来调用,这当然是可以的,但是如果该基类有多个派生类,每个派生类又产生新的派生类,形成同一基类的类族,每个派生类都有同名函数Display()在程序中要调用同一类族中不同类的同名函数,就要定义多个指向派生类的指针变量。这两种方法都不方便,他要求在调用同一类族中不同类的同名函数时采用不同的调用方式。因此C++引入了虚函数的概念,用虚函数解决了了动态多态的问题。
我们先来了解虚函数的概念:所谓虚函数就是在基类声明函数是虚拟的,并不是实际存在的函数,然后在派生类中才正式定义此函数。
虚函数的使用方法:
(1)在基类中用virtual关键字声明成员函数为虚函数,在类外定义虚函数时可以不必再加virtual。
(2)在派生类中重新定义此函数,函数名、函数类型、函数参数个数和类型必须与基类的虚函数相同,根据派生类的需要成新定义函数体
(3)当一个成员函数被声明为虚函数后,其派生类的同名函数都自动成为虚函数,因此在派生类中重新声明该虚函数时可以加virtual关键字也可以不加。但习惯上在每一层声明上都加virtual,是程序更加清晰。
对于上述例子将Display()声明为虚函数,我们来看看结果。
class Base
{
public:
Base()//基类的构造函数
{}
virtual void Display()//声明为虚函数。
{
cout<<"Base::Display()"<<endl;
}
};
class Derived:public Base
{
public:
Derived()//派生类构造函数
{}
virtual void Display()
{
cout<<"Derived::Display()"<<endl;
}
};
void FunTest()
{
Base b;//定义基类对象
Derived d;//定义派生类对象
Base *pb = &b;//定义基类指针指向b
pb->Display();//输出基类对象b中的数据
pb = &d;//定义基类指针指向d
pb->Display();//输出派生类对象d中的数据
}
只能将类的成员函数声明为虚函数,而不能将类外的普通函数声明为虚函数,显然它只能用于类的继承层次中,那么类中哪些函数可以声明为虚函数呢?
(1)普通函数
(2)析构函数
(3)赋值运算符重载(最好不要)
不可以声明为虚函数的有
(1)构造函数
(2)友元函数(不是类的成员函数不能被继承)
(3)静态成员函数(没有this指针且继承体系中只有一份)
对于为什么析构函数有时最好声明为虚函数,而构造函数不可以大家可以参考博客:是我对这些情况的分析。
纯虚函数
纯虚函数的作用:在基类中为其派生类保留一个名字,以便派生类根据据需要对它进行定义,如果在基类中没有保留函数名字,则无法实现多态。
声明纯虚函数的一般形式:virtual 函数类型 函数名 (参数列表) = 0;
例如如下代码:
class Base
{
public:
Base()//基类的构造函数
{}
virtual void DerivedName() = 0;//在基类中声明纯虚函数
};
class Derived1:public Base
{
public:
Derived1()//派生类构造函数
{}
virtual void DerivedName()
{
cout<<"Derived1"<<endl;//在派生类中进行定义输出派生类类名
}
};
void FunTest()
{
Derived1 d;//定义派生类对象
Base *pb = &d;//定义基类指针指向d
pb->DerivedName();
}
抽象类:不用来定义对象,只作为一种基本类型用作继承的类称为抽象类。
总结:
(1)凡是包含纯虚函数的类都是抽象类
(2)因为虚函数不能调用含纯虚函数的类不能建立对象的
(3)如果在派生类中没有对所有纯虚函数进行定义,此派生类仍是抽象类,不能用来定义对象
(4)虽然抽象类不能定义对象,但是定义指向抽象类数据的指针变量。
编译系统在运行阶段将虚函数和类对象“绑定”在一起,并且在运行阶段基类指针变量指向某一个类对象,然后通过指针变量调用该对象中的函数。
C++就是这样利用虚函数和基类指针来实现多态的。
下面让我们具体来探讨实现多态的过程。
现有如下代码:
class Base
{
public:
Base()//基类的构造函数
{}
virtual void FunTest1()//定义了4个基类的虚函数
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"Base::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"Base::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"Base::FunTest4()"<<endl;
}
};
class Derived1:public Base
{
public:
Derived1()//派生类构造函数
{}
virtual void FunTest1()//派生类中对1和3进行了重写
{
cout<<"Derived::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"Derived::FunTest3()"<<endl;
}
};
首先让我们看看派生类的大小
在主函数中加入如下代码:cout<<"sizeof(Derived) = "<<sizeof(Derived)<<endl;
派生类Derived中没有数据成员那么这4个字节是什么呢?让我们创建一个派生类对象d去监视窗口和内存窗口一探究竟。
原来在基类中有一个指针指向基类的虚函数表这就是所谓的虚表指针,虚函数是按照什么顺序排列的呢?我们来测验一下
下面的代码将函数1,2调换了位置让我们看看虚函数表的顺序
Base()//基类的构造函数
{}
virtual void FunTest2()
{
cout<<"Base::FunTest2()"<<endl;
}
virtual void FunTest1()//定义了4个基类的虚函数
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"Base::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"Base::FunTest4()"<<endl;
}
};
由此可见虚函数表的顺序就是虚函数在类中声明的顺序。
由上述例子可以看出派生类中有一个虚表指针指向虚函数表并且虚函数表最后一个字节为NULL;
上述例子中我们对基类的1,3函数进行了重写派生类中的虚函数表将1,3换成类派生类的函数
那如果派生类有自己的虚函数呢
由内存可以看见派生类自己的虚函数在虚函数表中基类虚函数的后边
派生类和基类的虚函数表是否相同呢,派生类是否只简单替换重写的虚函数,并且添加自己的虚函数呢?
由此可见基类与派生类不是同一个虚函数表
综上相信我们对多态的实现方法有了一定的了解,也对虚函数表有了一定的认识那么接下来就一起来看一看多态中单继承的派生类模型:
代码如下:
class Base
{
public:
Base()//基类的构造函数
{}
virtual void FunTest1()//定义了4个基类的虚函数
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"Base::FunTest2()"<<endl;
}
virtual void FunTest3()
{
cout<<"Base::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"Base::FunTest4()"<<endl;
}
int _data1;
};
class Derived:public Base
{
public:
Derived()//派生类构造函数
{}
virtual void FunTest1()//派生类中对1和3进行了重写
{
cout<<"Derived::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"Derived::FunTest3()"<<endl;
}
virtual void FunTest5()
{
cout<<"Derived::FunTest5()"<<endl;
}
int _data2;
};
void FunTest()
{
Derived d;
d._data1 = 1;
d._data2 = 2;
}
int main()
{
cout<<"sizeof(Derived) = "<<sizeof(Derived)<<endl;
FunTest();
system("pause");
return 0;
}
多态中多继承的派生类模型:
有如下代码:
class Base1
{
public:
Base1()//基类的构造函数
{}
virtual void FunTest1()//Base1定义了2个虚函数
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"Base::FunTest2()"<<endl;
}
int _data1;
};
class Base2
{
public:
Base2()
{}
virtual void FunTest3()//Base2定义了2个虚函数
{
cout<<"Base::FunTest3()"<<endl;
}
virtual void FunTest4()
{
cout<<"Base::FunTest4()"<<endl;
}
int _data2;
};
class Derived:public Base1,public Base2
{
public:
Derived()//派生类构造函数
{}
virtual void FunTest1()//派生类中对1和3进行了重写
{
cout<<"Derived::FunTest1()"<<endl;
}
virtual void FunTest3()
{
cout<<"Derived::FunTest3()"<<endl;
}
virtual void FunTest5()
{
cout<<"Derived::FunTest5()"<<endl;
}
int _data3;
};
void FunTest()
{
Derived d;
d._data1 = 1;
d._data2 = 2;
d._data3 = 3;
}
int main()
{
cout<<"sizeof(Derived) = "<<sizeof(Derived)<<endl;
FunTest();
system("pause");
return 0;
}
由上图可以看出多态中多继承的模型图如下:
Base1与Base2的顺序根据继承的先后顺序而定。
多态中菱形继承的派生类模型:
代码如下:
class B
{
public:
B()//基类的构造函数
{}
virtual void FunTest1()//B定义了2个虚函数
{
cout<<"B::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"B::FunTest2()"<<endl;
}
int _data1;
};
class C1:public B
{
public:
C1()
{}
virtual void FunTest1()//派生类中对1进行了重写
{
cout<<"C1::FunTest1()"<<endl;
}
virtual void FunTest3()//派生类C1自己的虚函数
{
cout<<"C1::FunTest3()"<<endl;
}
int _data2;
};
class C2:public B
{
public:
C2()//派生类构造函数
{}
virtual void FunTest2()//派生类中对2进行了重写
{
cout<<"C2::FunTest2()"<<endl;
}
virtual void FunTest4()//派生类C2自己的虚函数
{
cout<<"C2::FunTest4()"<<endl;
}
int _data3;
};
class D:public C1,public C2
{
public:
D()
{}
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;
}
virtual void FunTest5()
{
cout<<"D::FunTest5()"<<endl;
}
int _data4;
};
void FunTest()
{
D d;
d.C1::_data1 = 0;
d._data2 = 2;
d.C2::_data1 = 1;
d._data3 = 3;
d._data4 = 4;
}
int main()
{
cout<<"sizeof(D) = "<<sizeof(D)<<endl;
FunTest();
system("pause");
return 0;
}
则模型图为:
多态中虚继承的派生类模型:
代码如下:
class Base
{
public:
Base()//基类的构造函数
{}
~Base()//基类的析构函数
{}
virtual void FunTest1()//B定义了2个虚函数
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"Base::FunTest2()"<<endl;
}
int _data1;
};
class Derived:virtual public Base
{
public:
Derived()//派生类的构造函数
{}
~Derived()//派生类的析构函数
{}
virtual void FunTest1()//派生类中对1进行了重写
{
cout<<"Derived::FunTest1()"<<endl;
}
virtual void FunTest3()//派生类C1自己的虚函数
{
cout<<"Derived::FunTest3()"<<endl;
}
int _data2;
};
void FunTest()
{
Derived d;
cout<<"sizeof(Derived) = "<<sizeof(d)<<endl;
d._data1 = 1;
d._data2 = 2;
}
int main()
{
FunTest();
system("pause");
return 0;
}
则派生类模型为:
对于 00 00 00 00目前还不知道具体的作用,但是并不是所有情况都会有00 00 00 00请仔细看如下代码
class Base
{
public:
Base()//基类的构造函数
{}
~Base()//基类的析构函数
{}
virtual void FunTest1()//B定义了2个虚函数
{
cout<<"Base::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"Base::FunTest2()"<<endl;
}
int _data1;
};
class Derived:virtual public Base
{//没有为派生类显示定义构造和析构函数
public:
virtual void FunTest1()//派生类中对1进行了重写
{
cout<<"Derived::FunTest1()"<<endl;
}
virtual void FunTest3()//派生类C1自己的虚函数
{
cout<<"Derived::FunTest3()"<<endl;
}
int _data2;
};
void FunTest()
{
Derived d;
cout<<"sizeof(Derived) = "<<sizeof(d)<<endl;
d._data1 = 1;
d._data2 = 2;
}
int main()
{
FunTest();
system("pause");
return 0;
}
我们发现没有为派生类显示定义构造和析构函数的时候模型里没有了00 00 00 00,所以大家要注意区分这两种情况。
多态中菱形虚继承的派生类模型:
代码如下:
class B
{
public:
B()//基类的构造函数
{}
virtual void FunTest1()//B定义了2个虚函数
{
cout<<"B::FunTest1()"<<endl;
}
virtual void FunTest2()
{
cout<<"B::FunTest2()"<<endl;
}
int _data1;
};
class C1:virtual public B
{
public:
C1()
{}
virtual void FunTest1()//派生类中对1进行了重写
{
cout<<"C1::FunTest1()"<<endl;
}
virtual void FunTest3()//派生类C1自己的虚函数
{
cout<<"C1::FunTest3()"<<endl;
}
int _data2;
};
class C2:virtual public B
{
public:
C2()//派生类构造函数
{}
virtual void FunTest2()//派生类中对2进行了重写
{
cout<<"C2::FunTest2()"<<endl;
}
virtual void FunTest4()//派生类C2自己的虚函数
{
cout<<"C2::FunTest4()"<<endl;
}
int _data3;
};
class D:public C1,public C2
{
public:
D()
{}
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;
}
virtual void FunTest5()
{
cout<<"D::FunTest5()"<<endl;
}
int _data4;
};
void FunTest()
{
D d;
cout<<"sizeof(D) = "<<sizeof(d)<<endl;
d.C1::_data1 = 0;
d._data2 = 2;
d.C2::_data1 = 1;
d._data3 = 3;
d._data4 = 4;
}
int main()
{
FunTest();
system("pause");
return 0;
}
所以我们可以从内存图中准确的的出多态中菱形虚继承的模型
同样,在派生类C1,C2,D中去掉析构和构造函数内存中就不存在 00 00 00 00了,所以大家在求派生类对象大小时一定要注意这一点。
好了,到此就把多态中一些基本概念以及各种派生类型的对象模型弄清楚了!