Java和C++里面的重写/隐藏/覆盖

首先,无关重载

注:重载是同一个类的各个函数之间的。重写是父类子类之间的。Overload和Overwrite(也叫Override)的区别。

注意:Java里面区分重写(Override/Overwrite)与隐藏(Hide?)。而C++里面区分的是覆盖(Override)和隐藏/重写(Overwrite)。文字游戏,区分清楚就好了。

这里主要谈的是函数重写与隐藏

首先,我的理解:重写和隐藏是互斥的、相对的。父子中都存在的函数,不是重写就是隐藏

重写和隐藏的本质区别是:重写是动态绑定的,根据运行时引用所指向对象的实际类型来决定调用相关类的成员。而隐藏是静态绑定的,根据编译时引用的静态类型来决定调用的相关成员。换句话说,如果子类重写了父类的方法,当父类的引用指向子类对象时,通过父类的引用调用的是子类方法。如果子类隐藏了父类的方法(成员变量),通过父类的引用调用的仍是父类的方法(成员变量)。

:这一句话非常绕,说的是子类隐藏了父类的方法,但调用的还是父类的方法,还不如说是父类隐藏了子类的方法。其实原义是,是针对子类引用说的隐藏,指的是子类引用调用子类,不调用父类;而父类引用仍然调用父类。)

Java的隐藏和C++的隐藏是有区别的。也不能说完全不同,但是重写的覆盖面和默认采用方式不同。

C++里面的重写,一般叫作覆盖

C++里面的隐藏子类会把父类中其他类型的方法都隐藏掉,使得不能调用

Java里面的隐藏只针对参数一样的static函数,参数不一样的static函数,照样不会隐藏,子类能够调用。

下面都有例子。

Java

先说Java的隐藏(参考 Link


覆盖则指的是父类引用指向了子类对象,调用的时候会调用子类的具体方法;
隐藏指的是“子类把父类的属性或者方法隐藏了”,即将子类强制转换成父类后,调用的还是父类的属性和方法。(引号内的容易引起歧义,可以忽略)

(1) 变量只能被隐藏(包括静态和非静态),不能被覆盖

(2) 可以用子类的静态变量隐藏父类的静态变量,也可以用子类的非静态变量隐藏父类的静态变量,也可以用非最终变量(final)隐藏父类中的最终变量;

(3) 静态方法(static)只能被隐藏,不能被覆盖;

(4) 非静态方法可以被覆盖;

(5) 不能用子类的静态方法隐藏父类中的非静态方法,否则编译会报错;

(6) 不能用子类的非静态方法覆盖父类的静态方法,否则编译会报错;

(7) 不能重写父类中的最终方法(final);

(8) 抽象方法必须在具体类中被覆盖;

简单讲,父类和子类的方法的静态性必须一样。要么都有static,要么都没有,否则会编译报错,已实验。

注:Java,我认为的,对于“隐藏”,好的记忆方法是指向子类实例的父类指针(引用),看到的仍然是父类的方法,而把子类的方法给“隐藏”了。C++里面,因为涉及到参数不同的父类函数被隐藏,那才是叫作真的隐藏

实例,我在Intellij上面实验了,如下:

package com.company;


class Solution {

}

class SuperClass {
    public static int i = 1;
    public int j = 2;
    public final int k = 3;

    public static void method1() {
        System.out.println("SuperClass Method1");
    }

    public void method2() {
        System.out.println("SuperClass Method2");
    }

    public final void method3() {
        System.out.println("SuperClass Method3");
    }

}

class SubClass extends SuperClass {

    public static int i = 2;//无论是不是static,都能隐藏父类的变量i
    public static int j = 1;
    public final int k = 4;//无论是不是final,都能隐藏父类的变量k

    public static void method1() {
        System.out.println("SubClass Method1");
    }

    public void method2() {
        System.out.println("SubClass Method2");
    }

    /*public final void method3() {
        System.out.println("SuperClass Method3");
    }*/
}

public class Main {

    public static void main(String[] args) throws InterruptedException {

        SuperClass sc = new SubClass();
        System.out.println("i = " + sc.i); // 所有的成员变量,只能被隐藏
        System.out.println("j = " + sc.j);
        System.out.println("k = " + sc.k);
        sc.method1();//静态方法只能被隐藏
        sc.method2();



        SubClass subc = new SubClass();
        System.out.println("i = " + subc.i);
        System.out.println("j = " + subc.j);
        System.out.println("k = " + subc.k);

        subc.method1();
        subc.method2();

        // Your Codec object will be instantiated and called as such:
        //System.out.printf("ret:%d
", ret);

        System.out.println();

    }

}

打印结果:

i = 1
j = 2
k = 3
SuperClass Method1
SubClass Method2
i = 2
j = 1
k = 4
SubClass Method1
SubClass Method2

把上面子类里面变量的static和final去掉:

    public int i = 2;//无论是不是static,都能隐藏父类的变量i
    public static int j = 1;
    public int k = 4;//无论是不是final,都能隐藏父类的变量k

打印的结果和原来的一致:

i = 1
j = 2
k = 3
SuperClass Method1
SubClass Method2
i = 2
j = 1
k = 4
SubClass Method1
SubClass Method2

C++

而C++里面的隐藏,和Java里面的隐藏的语义,不太一样,参考 Link:

 如果派生类的函数与基类的函数同名, 但是参数不同. 此时, 不论有无 virtual 关键字, 基类的函数将被隐藏(注意别与重载混淆).
如果派生类的函数与基类的函数同名, 并且参数也相同, 但是基类函数没有 virtual 关键字. 此时, 基类的函数被隐藏(注意别与覆盖混淆).

也就是说,C++的重写,只跟virtual关键字有关。如果没有这个关键字,那么父类中的方法和子类是没有关系的。即使用了virtual,如果方法参数不一样,也不重载,而是采用隐藏。(隐藏函数和被隐藏函数参数列表可以相同,也可以不同,但函数名一定同;当参数不同时,无论基类中的函数是否被virtual修饰,基类函数都是被隐藏,而不是被重写。)

Java默认是重载只有static方法和变量,是不重载,而采用隐藏的。

C++代码示例如下,在m42n03机器的 /home/work/data/code/overloadnhide 目录:

#include  <iostream>

using namespace std;
class Base
{
    public:
    virtual void f(float x){cout << "Base::f(float) "  << x << endl;}
    virtual void f1(float x){cout << "Base::f1(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 f(float x){cout << "Derived::f(float) "  << x << endl;}
    virtual void f1(int x){cout << "Derived::f1(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()
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;

    // No hide , only overwrite
    pb->f(3.14f);
    pd->f(3.14f);             //Derived::f(float) 3.14

    pb->f1(3.14f);
    pd->f1(3.14f);

    // hide
    pb->g(3.14f);             //Base::g(float) 3.14
    pd->g(3.14f);             //Derived::g(int)  3       (surprise!)

    // hide
    pb->h(3.14f);             //Base::h(float) 3.14         (surprise!)
    pd->h(3.14f);             //Derived::h(float)  3.14

    return 0;
}

编译命令及输出:

g++ -Wall -o test test.cpp

test.cpp: In function `int main()':
test.cpp:35: warning: passing `float' for converting 1 of `virtual void Derived::f1(int)'
test.cpp:39: warning: passing `float' for converting 1 of `void Derived::g(int)'

有两个类型转换的warning

运行命令:

Derived::f(float) 3.14
Derived::f(float) 3.14

Base::f1(float) 3.14
Derived::f1(int) 3

Base::g(float) 3.14
Derived::g(int) 3

Base::h(float) 3.14
Derived::h(float) 3.14

可以看到,只有第一种情况(有virtual,并且父子类方法参数一样)才是Override覆盖,其他的情况全是隐藏。

java 的函数是没有 virtual 关键字的, 但是派生类和基类只要函数名和参数相同, 那么该函数就被覆盖了. 如果反过来想, 相对于 C++, 那不是 java 的每个函数都是虚函数吗?  可能C++ 在于效率上考虑, 不想所有的函数都使用动态联编.

g(float) 和 g(int) 是不同的函数, C++编译后在符号库中的名字分别是 _g_float 和 _g_int.即使他们都有 virtual 关键字, 但是因为是分别存在与派生类和基类中的不同函数, 所以在不存在覆盖的关系. 

而且,C++里面一旦看到一个名称是g的函数,就不会再往上看父类中有没有其他参数类型的函数,如果子类中定义的g函数类型不对,直接编译报错。不管父类中的正确方法有没有加virtual函数,都是这样的。

但是,指向子类实例的父类指针,是可以正确调用这个其他类型的参数的方法的。

说明不同类型的同名函数,在C++子类中被隐藏了。

关于函数隐藏的更具体的例子

C++

这次先看C++的例子(感觉C++的例子更极端):

#include  <iostream>

using namespace std;
class Base
{
    public:
    virtual void f(float x){cout << "Base::f(float) "  << x << endl;}
    virtual void f(int x, int y){cout << "Base::f(int, int) "  << x << "," << y <<  endl;}
    virtual void f1(float x){cout << "Base::f1(float) "  << x << endl;}
            void g(float x){cout << "Base::g(float) "  << x << endl;}
            void g(int x, int y){cout << "Base::g(int, int) " << x << "," << y << endl;}
            void h(float x){cout << "Base::h(float) "  << x << endl;}
};


class Derived : public Base
{
    public:
    virtual void f(float x){cout << "Derived::f(float) "  << x << endl;}
    virtual void f1(int x){cout << "Derived::f1(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()
{
    Derived d;
    Base *pb = &d;
    Derived *pd = &d;

    // No hide , only overwrite
    pb->f(3.14f);
    pd->f(3.14f);             //Derived::f(float) 3.14

    pb->f(1, 2);
    pd->f(1, 2);  // to be removed

    pb->f1(3.14f);
    pd->f1(3.14f);

    // hide
    pb->g(3.14f);             //Base::g(float) 3.14
    pd->g(3.14f);             //Derived::g(int)  3       (surprise!)

    // diffrent param
    pb->g(1, 2);             
    pd->g(1, 2);  // to be removed

    // hide
    pb->h(3.14f);             //Base::h(float) 3.14         (surprise!)
    pd->h(3.14f);             //Derived::h(float)  3.14

    return 0;
}
                  

注意上面飘红的部分,是新加的。

编译,直接出错。错误在pd调用的两个地方:

g++ -Wall -o test test.cpp

test.cpp: In function `int main()':
test.cpp:37: error: no matching function for call to `Derived::f(int, int)'
test.cpp:19: note: candidates are: virtual void Derived::f(float)
test.cpp:40: warning: passing `float' for converting 1 of `virtual void Derived::f1(int)'
test.cpp:44: warning: passing `float' for converting 1 of `void Derived::g(int)'
test.cpp:48: error: no matching function for call to `Derived::g(int, int)'
test.cpp:21: note: candidates are: void Derived::g(int)

去掉pd调用的两行,见上面代码注释(// to be removed)部分。编译通过:

$ g++ -Wall -o test test.cpp
test.cpp: In function `int main()':
test.cpp:40: warning: passing `float' for converting 1 of `virtual void Derived::f1(int)'
test.cpp:44: warning: passing `float' for converting 1 of `void Derived::g(int)'

$ ./test 
Derived::f(float) 3.14
Derived::f(float) 3.14

Base::f(int, int) 1,2

Base::f1(float) 3.14
Derived::f1(int) 3

Base::g(float) 3.14
Derived::g(int) 3

Base::g(int, int) 1,2

Base::h(float) 3.14
Derived::h(float) 3.14

可见,父类函数加不加virtual,都不影响它在被覆盖的子类里面,已经不可见了。

JAVA

关于这个例子,Java就完全不一样,上代码:

package com.company;


class Solution {

}

class SuperClass {
    public static int i = 1;
    public int j = 2;
    public final int k = 3;

    public static void method1() {
        System.out.println("SuperClass Method1");
    }

    public static void method1(int a) {
        System.out.println("SuperClass Method1 with int " + a);
    }

    public void method2() {
        System.out.println("SuperClass Method2");
    }

    public void method2(int a) {
        System.out.println("SuperClass Method2 with int " + a);
    }

    public final void method3() {
        System.out.println("SuperClass Method3");
    }

}

class SubClass extends SuperClass {

    public  int i = 2;//无论是不是static,都能隐藏父类的变量i
    public static int j = 1;
    public  int k = 4;//无论是不是final,都能隐藏父类的变量k

    public static void method1() {
        System.out.println("SubClass Method1");
    }

    public void method2() {
        System.out.println("SubClass Method2");
    }

    /*public final void method3() {
        System.out.println("SuperClass Method3");
    }*/
}

public class Main {

    public static void main(String[] args) throws InterruptedException {

        SuperClass sc = new SubClass();
        System.out.println("i = " + sc.i); // 所有的成员变量,只能被隐藏
        System.out.println("j = " + sc.j);
        System.out.println("k = " + sc.k);
        sc.method1();//静态方法只能被隐藏
        sc.method1(3);
        sc.method2();
        sc.method2(3);
        sc.method3();



        SubClass subc = new SubClass();
        System.out.println("i = " + subc.i);
        System.out.println("j = " + subc.j);
        System.out.println("k = " + subc.k);

        subc.method1();
        subc.method1(3);
        subc.method2();
        subc.method2(3);
        subc.method3();

        // Your Codec object will be instantiated and called as such:
        //System.out.printf("ret:%d
", ret);

        System.out.println();

    }

}

增加的内容,见以上飘红的部分。编译,通过!

i = 1
j = 2
k = 3
SuperClass Method1
SuperClass Method1 with int 3
SubClass Method2
SuperClass Method2 with int 3
SuperClass Method3
i = 2
j = 1
k = 4
SubClass Method1
SuperClass Method1 with int 3
SubClass Method2
SuperClass Method2 with int 3
SuperClass Method3

从上面可以看出。和C++不一样!

子类中没有定义的,父类中有的不同参数的函数,照样能够在父类和子类引用里面调用(实例都是子类的实例)

总结:

Java里面,

默认Override,如果是子类的示例,那么不管引用是通过父类和子类,都会调用子类的方法;

如果是static方法,那么即使是子类的实例,父类引用和子类引用,会分别调用各自的方法;

如果子类中没有实现某种参数的方法,父类中有同名的,不管是不是static,都会调用父类的方法。

C++里面,

默认不是Override,除非加上virtual关键字并且父子函数参数完全一致,那么形成覆盖,如果是子类的示例,通过父子指针,都会调用子类的方法;

其他的情况,即使是子类的实例,父子指针,会分别调用各自的方法;

如果子类中没有实现某种参数的方法,父类中有同名的,子类指针不能调用,编译报错;父类指针(子类实例)能够调用父类的方法。

(完)

原文地址:https://www.cnblogs.com/charlesblc/p/6133605.html