1.多态的定义
1.1简介:通俗来说,就是指多种形态,具体而言,就是完成某件事情,不同的人的解决方法回不同。
1.2按照网上的定义:多态(Polymorphism)按字面的意思就是“多种状态”。在面向对象语言中,接口的多种不同的实现方式即为多态。引用Charlie Calverts对多态的描述——多态性是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作(摘自“Delphi4 编程技术内幕”)。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function) 实现的。
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。
1.3分类:多态分为动态多态和静态多态。
!!!有关多态的分类博主在此贴了一篇博客http://www.cnblogs.com/Leo_wl/p/3667870.html
2.多态的实现
多态是在不同继承关系中的类对象,再调用同一函数,产生了不同的行为。
2.1多态的构造必须包含两个条件
1.调用对象的时候,必须是引用或者指针。
2.被调用的函数必须是虚函数,而且完成了虚函数的重写。
2.2有关多态的构造,看下面一段代码,就符合了多态的定义。
2.3说到重写,这里就必须要提到和它相似的两个名词(重载,重定义)。
2.4虚函数重写的例外:(协变)
虚函数重写有一个例外:重写的虚函数的返回值可以不同,但是必须分别是基类指针和派生类指针或者基类引用和派生类引用。
class A{};
class B : public A{};
class Person{
public:
virtual A* f()
{
return new A;
}
};
class Student : public Person
{
public:
virtual B* f()
{
return new B;
}
};
2.5不规范的重写行为
在派生类中的重写的成员函数可以不加virtual关键字,也是构成重写的,因为继承后基类的虚函数被继承下来了在派生类中依旧保持虚函数的属性,我们只是重写了它。
2.6析构函数的重写问题
基类的析构函数如果是虚函数,那么派生类的析构函数就重写了基类的虚构函数。这里他们的函数不相同,看起来违反了重写的规则,其实可以理解为编译器对函数做了特殊的处理,编译后析构函数的名称统一处理成destructor,这也说明了基类析构函数最好写成虚函数。
class A
{
public:
virtual ~A()
{
cout << "析构A" << endl;
}
};
class B : public A
{
public:
~B()
{
cout << "析构B " << endl;
}
};
void test()
{
A* aut = new A;
B* but = new B;
delete aut;
delete but;
}
2.7接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。
虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。
3.抽象类
在虚函数的后面写上 =0,则这个函数叫做纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽向类不能实例化处对象。派生类继承后也不能实例化处对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现了接口继承。
- 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就 是存在部分的另一部分是自己的成员。 2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重 写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的 叫法,覆盖是原理层的叫法
- 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会 放进虚表。
- 虚函数表本质是一个存虚函数指针的指针数组,这个数组最后面放了一个nullptr。
- 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在 派生类中的声明次序增加到派生类虚表的最后。