重载(overload)与重写(override)

提纲.png

一.C++中的重写、重载、重定义

1.重载(overload)

概念

同一个类中的函数具有相同的名称,但是参数的列表不相同的情形,这样的同名不同参数的函数之间,互成为重载函数。

基本条件
  • 同一个类中(相同的作用域中)
  • 函数名称必须相同
  • 函数参数必须不相同,可以是参数类型或者参数个数不同
  • 函数的返回值可以不同

2.重写(override)

概念

也称为覆盖,子类重新定义的父类中有相同的名称或者参数的虚函数,主要在继承关系中出现。

基本条件
  • 子类重写父类中的virtual函数
  • 重写函数和被重写函数的函数名和参数必须一致(具体实现一般不同)
  • 重写函数和被重写函数的返回值相同,要么都返回指针,要么都返回引用
  • 重写函数和被重写函数都是virtual函数(其中重载函数可以带virtual,也可以不带)
注意
  • 静态方法不能被重写,也就是static和virtual不能同时使用
  • C++ 11中新增了final关键字,final修饰的虚函数不能被被重写

3.重定义(redefining)

注意

也叫隐藏,子类重定义父类中的非虚函数,屏蔽了父类的同名函数(相当于创建了一个新的函数,跟父类无关)

基本条件
  • 子类和父类函数的名称相同,参数也相同,父类中的函数不是virtual,父类的函数将被隐藏
  • 子类和父类的函数名称相同,但参数不同,此时不管父类函数是不是virtual函数,都将被隐藏。

4.深入理解C++中的重写(override)与重定义(redefining)

上面我们对C++中的重写(override)与重定义(redefining)做了概念上的区分,只要记住上面的概念特征就嗯呢该区分这两种操作,下面我们来从原理上说明下这两种操作有什么区别:

重写(override)

上面我们已经说了,C++中重写的时候重写的是父类中的虚函数(virtual),因此我们需要从虚函数的工作原理说起。
  由于对于没有使用virtual的函数,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针指向的对象类型来选择方法,因此对于虚函数,到底使用哪个函数(父类或者子类中的函数)是不能再编译器确定的,因为编译器不知道指针或者引用指向的是哪种对象,故编译器需要在程序运行时选择正确的虚方法的代码,这被称为动态联编。为了是动态联编能够在运行时正确的进行决策,必须采用一些跟踪基类的指针或者引用指向的对象类型

通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向虚函数表的指针。虚函数表是一个保存函数地址的数组,每一个地址都对应该类中的一个虚函数。基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向自己(独立)虚函数表的指针。
  如果派生类重写(override)了虚函数,则派生类的虚函数表将保存新函数的地址。

虚函数的工作原理.jpg

因此,当用指针调用一个函数时,回去指针所指对象的虚函数表中去查找被重写后的虚函数。

重定义(redefining)

参照隐藏规则,派生类的成员函数隐藏了基类的同名函数。所谓隐藏就是指派生类类型的对象、引用、指针访问基类和派生类都有的同名函数的时候,访问的是派生类的函数,隐藏了基类同名函数。派生类既然自动继承了基类的成员,那么基类成员就可以被派生类直接访问,那么为什么访问的是派生类的成员函数呢?所以隐藏 规则实际上就是默认的c++名字解析过程。
  在继承机制下,派生类的类域被嵌套在基类的类域中,派生类的名字解析过程如下:

  • 1)首先在派生类中查找改名字
  • 2)如果第一步未查找到,及派生类的类域对改名字无法进行解析,则编译器在外围基类类域查找改名字的定义

所以准确来说,当派生类和基类有同一名字的成员时,派生类成员是隐藏了对基类成员的直接访问。那么如果要访问基类同名成员呢?加上类作用域限定例如:Base::g(float)就可以了

二.Java 方法重载和重写

1.重载(overload)

Java中的重载和C++中的基本一致,这里再强调一下:

  • 重载的方法必须具有不同的参数列表
  • 重载的方法可以有不同的返回值类型
  • 重载的方法可以有不同的访问修饰符

2.重写(override)

若子类中声明的方法与父类中的某一方法具有相同的方法名、返回值类型和参数列表,则子类中的方法将覆盖父类中的方法。 如需要调用父类中原有的方法,可以使用super关键字,该关键字引用了当前类的父类

方法重写的规则如下:

  • 重写方法的参数列表必须与被重写方法的参数列表完全相同
  • 重写方法的返回值类型必须与被重写方法的返回值类型完全相同
  • 重写方法的访问权限不能比被重写方法的访问权限更严格。比如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected
  • 声明为final的方法不能被重写
  • 声明为static的方法不能被重写,但是能够在子类中再次声明,父类中的static方法会被隐藏
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法
  • 构造方法不能被重写

3.Java没有“重定义”这个概念

三.重写的意义

重写伴随着继承,他的的意义主要在于实现多态,用父类的引用来操作子类对象,但在实际的运行中对象将运行自己重写的方法。
  实际应用中,用得最多的一种运行时多态,就是用只知道父类型(可能是类,更多的可能是接口)的定义,这样只能调用父类型的方法,继承该父类并重写该方法的子类,就会自动执行重写了的方法(这在大型软件里很常用,父类型和子类型可能是不同的人编写的,以利于协作编程)。

public class OverrideTest {
    public static void main (String[] args) {
        Animal h = new Horse();
        h.eat();

        Opertion o = new Horse();
        o.run(30);

    }
}

class Animal {
    public void eat(){
        System.out.println ("Animal is eating.");
    }
}

class Horse extends Animal implements Opertion{
    public void eat(){
        System.out.println ("Horse is eating.");
    }
    public void buck(){
    }
    @Override
    public void run(int time) {
        System.out.println ("Horse is run: "+ time +"s");
    }
}
public interface Opertion {
    void run(int time);
}

输出为:

Horse is eating.
Horse is run: 30s

在Java中, Animal h = new Horse(); 中引用h指向子类Horse对象,因此其调用的也是子类Horse的eat方法。但是,如果调用子类特有的方法,如上例的h.buck(); 会出现编译错误,除非强制转换为子类对象((Horse) h).buck();
  同时需要注意的是向上转形是自动的,即Animal h = new Horse();父类的引用指向子类对象是可以的,但是子类的引用指向父类对象就是错的,必须显示的进行强制转换。

C++中由于动态联编,指针或者引用指向的虚函数可能会与上述行为略有不同。

四.Java和C++的一些比较

1.Java和C++中的重写

可以看到,Java中重写只要写一个和父类函数同名同参数的同返回值类型的函数即可(这在C++中就是“重定义”),不需要抽象函数(即C艹中的虚函数),因此Java中的普通函数就能起到C艹中虚函数的作用。

2.Java中为什么没有“重定义”?

上面我们说了,只要是一个和父类函数同名同参数的同返回值类型的函数,不管加没加“@override”注解,他就是重写的函数。此时这个重载的函数就相当于C艹中“重定义”的函数。只不过Java中需要用super关键字来调用父类中的函数(需要参数相同),而C++中需要用作用域解析运算符“::”来调用父类中的方法。

3. Java抽象函数与C++纯虚函数

Java中没有虚函数的概念,因为C++的虚函数是用来让子类重写的,然而Java中普通方法就可以被子类重写,因此没有必要用虚函数。
  Java中的抽象函数相当于C艹中的纯虚函数,两者换汤不换药:

  • C++中纯虚函数形式为:virtual void print() = 0;
  • Java中纯虚函数形式为:abstract void print();

4.Java抽象类与C++抽象类

C++类中只要含有一个纯虚函数,该类就是抽象类
  Java抽象类是用abstract修饰声明的类,当然类中至少要含有一个抽象方法,不然声明的抽象类没有意义

5.Java接口和C++虚基类

接口的存在是为了形成一种规约,他更多是对“行为”的一种抽象(抽象类是对“属性”的一种约束)。在三种的例子中我们也定义了一个接口
  接口中的方法会被隐式地指定为public abstract,接口中的变量会被隐式地指定为public static final变量,并且接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法。从这里可以隐约看出接口和抽象类的区别,接口是一种极度抽象的类型,它比抽象类更加“抽象”,并且一般情况下不在接口中定义变量。

  • C++中接口其实就是全虚基类。
  • Java中接口是用interface修饰的类。

6. 小结

  • C++虚函数 == Java普通函数
  • C++纯虚函数 == Java抽象函数
  • C++虚基类 == Java抽象类

推荐阅读更多精彩内容

  • 重载与重写是Java面向对象编程的重要概念。 重写(Override)重写是子类继承父类后,对父类允许访问(子类有...
    冷月的帅哥阅读 73评论 0 0
  • 1.C和C++的区别?C++的特性?面向对象编程的好处? 答:c++在c的基础上增添类,C是一个结构化语言,它的重...
    杰伦哎呦哎呦阅读 6,123评论 0 42
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 8,605评论 1 27
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 4,520评论 0 62
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 3,563评论 1 115