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

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

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

    (完)

  • 相关阅读:
    HDOJ 4747 Mex
    HDU 1203 I NEED A OFFER!
    HDU 2616 Kill the monster
    HDU 3496 Watch The Movie
    Codeforces 347A A. Difference Row
    Codeforces 347B B. Fixed Points
    Codeforces 372B B. Hungry Sequence
    HDU 1476 Sudoku Killer
    HDU 1987 How many ways
    HDU 2564 词组缩写
  • 原文地址:https://www.cnblogs.com/charlesblc/p/6133605.html
Copyright © 2011-2022 走看看