zoukankan      html  css  js  c++  java
  • 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关键字并且父子函数参数完全一致,那么形成覆盖,如果是子类的示例,通过父子指针,都会调用子类的方法;

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

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

    (完)

  • 相关阅读:
    推荐书单
    图解Android
    图解Android
    图解Android
    图解Android
    图解Android
    图解Android
    个人博客平台 http://craft6.cn 上线
    数据库设计教程系列——相关知识点整理
    O2O研究系列——O2O知识思维导图整理
  • 原文地址:https://www.cnblogs.com/charlesblc/p/6133605.html
Copyright © 2011-2022 走看看