一、多态
多态是指同样的消息被不同类型的对象接收时导致不同的行为。消息是指对类的成员函数的调用,不同的行为是指不同的实现,即调用了不同的函数。
二、多态的实现
从实现的角度可以划分两类:即编译时的多态和运行时的多态。
编译时的多态是在编译的过程中确定了同名操作的具体操作对象,运行时的多态则是在运行过程中才会动态的确定操作所针对的具体对象。这种确定操作的具体对象的过程称为绑定,指的是计算机程序自身彼此关联的过程,也就是把一个标识符名和一个存储地址联系在一起的过程。编译阶段完成的绑定称为静态绑定,也叫早绑定,运行阶段完成的绑定称为动态绑定,或者晚绑定。
二、多态性的举例
看看下面的代码:
class Shape
{
public:
Shape(){}
virtual void Draw(){}
void fn1(){}
virtual void fn2(){cout<<"Shape :: fn2"<<endl;}
virtual void fn3(){cout<<"Shape :: fn3"<<endl;}
};
class Triangle:public Shape
{
public:
Triangle(){}
virtual void Draw(){cout<<"Triangle Draw"<<endl;}
virtual void fn3(){cout<<"Triagnle : fn3"<<endl;}
virtual void fn4(){cout<<"Triangle : fn4"<<endl;}
};
class Circle:public Shape
{
public:
Circle(){}
virtual void Draw(){cout<<"Circle Draw"<<endl;}
virtual void fn2(){cout<<"Circle fn2"<<endl;}
virtual void fn5(){cout<<"Circle fn5"<<endl;}
};
void test(Shape &a) //引用形式
{
a.Draw();
a.fn2();
a.fn3();
}
void test1(Shape *a) //指针形式
{
a->Draw();
a->fn2();
a->fn3();
}
void main()
{
cout<<sizeof(Shape)<<endl; //4
Triangle t;
Circle c;
t.Draw(); //Triangle Draw
c.Draw(); //Circle Draw
cout<<endl;
test(t); //Triangle Draw Shape::fn2 Triangle:fn3
test(c); //Circle Draw Circle:fn2 Shape::fn3
cout<<endl<<endl;
test1(&t);
test1(&c);
}
如果一个类中有虚函数,则在本类中会增加一个VPTR,VPTR指向VTable,VTable中存放本类中虚函数的入口地址。下面给出我自己画的图来解释:(红色表明是子类对父类成员的造或者是新增的成员)
每个类都有一个虚表,在派生类的虚表中基类声明的函数对应的指针放在前面,派生类新增的虚函数的对应指针放在后面。每个多态类型的对象中都有一个指向当前类型的虚表的指针,该指针在构造函数中被赋值,通过基类的指针或引用调用一个虚函数时,就可以通过虚指针找到该对象的虚表,进而找到存放该函数的指针的虚表条目。将该条目中存放的指针读出后,就可获得应当被调用的函数的入口地址,然后调用该虚函数,这就是动态绑定的过程。
这里需要提到继承的步骤:全盘吸收,改造成员,最后添加新的成员。
所以Triangle类和Circle类先将父类完完整整的继承过来,然后在修改添加,最后的结果如上图所示。
运行结果:
执行一个类的构造函数时,首先被执行的是基类的构造函数,因此构造一个派生类的对象时,该对象的虚表指针首先会被指向基类的虚表。只有当基类构造函数执行完后,虚表指针才会被指向派生类的虚表,这就是基类构造函数调用虚函数时不用调用派生类构造函数的原因。
总结>实现多态要满足的条件:
1. 至少要含有两个类,且必须有父子关系
2. 实现多态的函数必须同名,同参,函数执行体可以不同
3. 基类中的同名同参函数必须有virtual关键字,子类可以不用写virtual
4. 必须是基类指针或引用指向基类指针或着是派生类指针或引用