多态C++与Java

最近看多态时,发现C++和Java的多态很多相同之处也有一些区别,就总结了一下。

多态作为面向对象的三大特性(封装、继承)之一,顾名思义,让程序运行时有多个状态可以运行,函数的调用是在调用时才确定而不是编译器确定的。
常用的写法是声明一个父类,实际引用的是子类对象,再调用子类中重写(方法名参数均一致)了的父类的方法。java和C++在这里对于重写了的方法就有一个区别,C++要求是必须重写虚函数才可以实现多态,而Java要求的则只是重写了的方法就可以了。
Java没有虚函数的概念,对于一个父类声明,实际引用了子类的对象,它可以调用的方法是父类中的方法,如果某些方法被子类重写,则会调用相应的子类方法(相当于原父方法被替换),而子类自己拥有的,父类中没用的则不允许调用。
C++则要稍复杂一点,多态只是针对虚函数而言,普通的函数,即使重写也不会有效果。所以C++可以调用的是父类中的方法,和对应的子类中被重写了的虚函数,子类中重写的普通函数和独有函数,是无法被调用的。
//C++
//小结:1、有virtual才可能发生多态现象
// 2、不发生多态(无virtual)调用就按原类型调用

#include<iostream>
using namespace std;

class Base
{
public:
    virtual void e(float x)
    {
        cout<<"Base::e(float)"<< x <<endl;
    }
    virtual void f(float x)
    {
        cout<<"Base::f(float)"<< x <<endl;
    }
    void g(float x)
    {
        cout<<"Base::g(float)"<< x <<endl;
    }
    void h(float x)
    {
        cout<<"Base::h(float)"<< x <<endl;
    }
};
class Derived : public Base
{
public:
    virtual void e(float x)
    {
        cout<<"Derived::e(float)"<< x <<endl;    //多态、覆盖
    }
    virtual void f(int x)
    {
        cout<<"Derived::f(int)"<< x <<endl;   //子类独有的方法
    }
    void g(int x)
    {
        cout<<"Derived::g(int)"<< x <<endl;     //这是子类独有的方法
    }
    void h(float x)
    {
        cout<<"Derived::h(float)"<< x <<endl;   //子类重写的普通方法
    }
};
int main(void)
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;
    // 虚函数可以实现多态
    pb->e(3.14f);   // Derived::f(float) 3.14
    pd->e(3.14f);   // Derived::f(float) 3.14

    // 子类独有的方法
    pb->f(3.14f);   // Base::f(float) 3.14
    pd->f(3.14f);   // Derived::f(int) 3

    // g这个方法,父类和子类的方法参数不同,所以是两个不同的方法
    pb->g(3.14f);   // Base::g(float)  3.14
    pd->g(3.14f);   // Derived::g(int) 3 

    // h虽然是被重写了,但是是普通方法,也不能实现多态
    pb->h(3.14f);   // Base::h(float) 3.14
    pd->h(3.14f);   // Derived::h(float) 3.14
    return 0;
}

Java中实现多态可以有继承和接口两种实现方式。继承可以继承普通类和抽象类,接口则是全部为抽象的方法(没有实现),区别如下:

参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的。它根本不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public。你不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且我们可以运行它 接口没有main方法,因此我们不能运行它。
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

C++ 则有虚函数和纯虚函数,其中虚函数,加上关键词virtual,纯虚函数则是函数原型后加上“=0”如virtual ReturnType Function()= 0;包含纯虚函数的类是抽象类(也就是C++中的接口),不可以实现的。

Java抽象类和接口还有一个区别是设计层面,抽象类是自底向上,将几个类的共性抽象出来,如男人、女人、老人、小孩可以抽象出一个类:人;接口是自顶向下的,包含的是抽象的方法,实现该接口的类不一定是同一个类型,比如对于运动了一段路程,人可以是走了一段路程,动物可以是爬了一段路程,飞行器可以使飞行了一段路程,这里就可以把这个方法作为一个接口里的方法。

虚函数表
虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。对于一个类的实例,如果他继承(单继承)的时候,没有虚函数覆盖(重写override),那么它的虚函数表里的顺序是父类的虚函数在前,再跟着自己的虚函数;如果继承的时候虚函数覆盖了,那么覆盖了的虚函数在被覆盖的父类虚函数之前,其余的顺序依旧。对于多继承无覆盖,那么每个父类都有自己的虚函数表,子类的虚函数在第一个父函数虚函数表后;若是有覆盖,覆盖了的虚函数就在对应的父虚函数表中被覆盖的虚函数之前,没有覆盖的虚函数依然在第一个父类虚函数表的最后。