博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java和C++里面的重写/隐藏/覆盖
阅读量:5944 次
发布时间:2019-06-19

本文共 10552 字,大约阅读时间需要 35 分钟。

首先,无关重载。

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

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

 

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

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

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

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

 

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

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

 

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

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

下面都有例子。

 

Java

先说Java的隐藏(参考 )

 
覆盖则指的是父类引用指向了子类对象,调用的时候会调用子类的具体方法;
隐藏指的是“子类把父类的属性或者方法隐藏了”,即将子类强制转换成父类后,调用的还是父类的属性和方法。(引号内的容易引起歧义,可以忽略)(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\n", ret);  System.out.println(); } }

打印结果:

i = 1j = 2k = 3SuperClass Method1SubClass Method2i = 2j = 1k = 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 = 1j = 2k = 3SuperClass Method1SubClass Method2i = 2j = 1k = 4 SubClass Method1 SubClass Method2

 

C++

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

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

 

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

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

 

 

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

#include  
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.cpptest.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.14Derived::f(float) 3.14Base::f1(float) 3.14Derived::f1(int) 3Base::g(float) 3.14Derived::g(int) 3Base::h(float) 3.14Derived::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  
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.cpptest.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.cpptest.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.14Derived::f(float) 3.14Base::f(int, int) 1,2Base::f1(float) 3.14Derived::f1(int) 3Base::g(float) 3.14Derived::g(int) 3Base::g(int, int) 1,2Base::h(float) 3.14Derived::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\n", ret);        System.out.println();    }}

 

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

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

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

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

 

总结:

Java里面,

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

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

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

 

C++里面,

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

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

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

 

(完)

 

转载于:https://www.cnblogs.com/charlesblc/p/6133605.html

你可能感兴趣的文章
iOS9 HTTP 不能正常使用的解决办法
查看>>
Numpy中的random模块中的seed方法的作用
查看>>
史上最全的数据库面试题,不看绝对后悔
查看>>
Chrome百度不显示中文字体
查看>>
用java数组模拟登录和注册功能
查看>>
javaScript实现归并排序
查看>>
关于jsb中js与c++的相互调用
查看>>
GraphQL 01--- GraphQL 介绍及资源总结
查看>>
nginx配置访问密码,让用户输入用户名密码才能访问
查看>>
串结构练习——字符串匹配
查看>>
CF Round #426 (Div. 2) The Useless Toy 思维 水题
查看>>
UVA 122 Trees on the level 二叉树 广搜
查看>>
POJ-2251 Dungeon Master
查看>>
tortoisesvn的安装
查看>>
大S变"汪太"!与汪小菲注册结婚
查看>>
我是怎么使用最短路径算法解决动态联动问题的
查看>>
sublime Text 2 配置以及 Python环境搭建
查看>>
[2778]小明的花费预算 (二分查找)SDUT
查看>>
Runnable接口介绍(中文文档)
查看>>
URAL 1353 Milliard Vasya's Function DP
查看>>