Java vs C++:子类覆盖父类函数时缩小可访问性的不同设计
Java 和 C++ 都是面向对象的语言,允许对象之间的继承。两个语言的继承都设置有允许子类覆盖父类的“虚函数”,加引号是因为 Java 中没有虚函数这一术语,但是我们的确可以把 Java 的所有函数等同于虚函数,因为 Java 类的所有非 static 函数都可以被子类覆盖,这里仅借用“虚函数”这一名词的含义,不深究语言的术语问题。
Java 和 C++ 都允许在子类覆盖父类时,改变函数的可访问性。所谓“可访问性”,就是使用 public 、protected、private 等访问控制符进行修饰,用来控制函数能否被访问到。通常可访问性的顺序为(由于 C++ 中没有包的概念,因此暂不考虑包访问控制符,这并不影响这里的讨论):
public > protected > private
以 Java 为例:
class Base {
protected void sayHello() {
System.out.println("Hello in Base");
}
}
class Child extends Base {
public void sayHello() {
System.out.println("Hello in Child");
}
}
注意这里的 sayHello() 函数,父类 Base 中,该函数使用 protected 访问控制符进行修饰,而子类将其改用 public,这不会有任何问题。子类对父类函数覆盖时,扩大可访问性,通常都不是问题。
本文要讲的是,当子类对父类函数覆盖的可访问性缩小时,Java 和 C++ 采取了不同的策略。
首先以 Java 为例,看下面的代码:
class Base {
public void sayHello() {
System.out.println("Hello in Base");
}
}
class Child extends Base {
private void sayHello() {
System.out.println("Hello in Child");
}
}
上面的代码中,第 8 行 **private void sayHello() {**会有编译错误,导致这段代码根本不能通过编译。因为 Java 不允许子类在覆盖父类函数时,缩小函数的可访问性,至于原因,我们可以用一个例子来说明。
例如我们在外部调用时使用下面的代码:
Base base = new Base();
base.sayHello();
base = new Child();
base.sayHello();
假如之前的代码可以通过编译,那么就存在这么一种可能:由于 Java 是运行时绑定,当 base 指向 new Base() 时, sayHello() 是可以访问到的,但是当 base 指向 new Child() 时,sayHello() 却无法访问到!在 Java 看来这是一个矛盾,应该避免出现这种问题,因此,Java 从编译器的角度规定我们不能写出上面的代码。
而在 C++ 中,情况就不同了,来看 C++ 的例子:
class Base {
public:
virtual void sayHello() {
std::cout << "Hello in Base";
}
}
class Child : public Base {
private:
void sayHello() {
std::cout << "Hello in Child";
}
}
这段代码在 C++ 中是完全正确的,可以通过编译。注意,这里的子类在覆盖父类函数时,缩小了可访问性。如果你没有看出有什么问题,那么我们完全可以在外部调用时使用下面的代码:
Child child;
child.sayHello(); // 不能通过编译,因为 sayHello() 是 private 的
static_cast<Base&>(child).sayHello(); // 可以通过编译,因为 sayHello() 是 public 的
第 2 行调用是失败的,因为在 Child 中,sayHello() 是 private 的,不能在外部调用。然而,当我们使用 static_cast 运算符将 Child 强制转换成 Base 类型时,事情发生了改变——对于 Base 而言,sayHello() 是 public 的,因此可以正常调用。
针对这一点,C++ 标准的《Member access control》一章中《Access to virtual functions》一节可以找到如下的例子:
class B {
public:
virtual int f();
};
class D : public B {
private:
int f();
};
void f() {
D d;
B* pb = &d;
D* pd = &d;
pb->f(); // OK: B::f() is public, D::f() is invoked
pd->f(); // error: D::f() is private
}
对此,C++ 标准给出的解释是:
Access is checked at the call point using the type of the expression used to denote the object for which the member function is called ( B* in the example above). The access of the member function in the class in which it was defined (D in the example above) is in general not known.
简单翻译过来有两条要点:
- 访问控制是在调用时检查的,也就是说,谁调用了这个函数,就检查谁能不能访问这个函数。
- 成员函数的可访问性一般是不知道的,也就是说,运行时检查可访问性时,并不能知道这个函数在定义时到底是 public 的还是 private 的。
正因如此,C++ 的调用方可以通过一些技巧性转换,“巧妙地”调用到原本无法访问的函数。一个现实的例子是:在 Qt 里面,QObject::event() 函数是 public 的,而其子类 QWidget 的 event() 函数则改变成 protected。具体细节可以阅读 Qt 的相关代码。
总结来说,在子类覆盖父类函数时,Java 严格限制了子类不能缩小函数可访问性,但 C++ 无此限制。
个人认为,从软件工程的角度来说,Java 的规定无疑更具有工程上面的意义,函数的调用也更加一致。C++ 的标准则会明显简化编译器实现,但是对工程而言并不算很好的参考。毕竟,一个明显标注了 private 的函数,无论任何情况都不应该允许在外部被调用。
PS:C++ 标准的正式版是需要购买的,但是草案可以免费下载。C++ 标准草案的下载地址可以在下面的页面找到:https://isocpp.org/std/the-standard
作者介绍
程梁,软件工程师。目前专注于 Angular 项目研发,同时对 Java 服务器端开发、Qt 桌面开发等都有浓厚的兴趣,个人博客 https://www.devbean.net。
https://my.oschina.net/editorial-story/blog/1821891
Java vs C++:子类覆盖父类函数时缩小可访问性的不同设计的更多相关文章
- Java:关于子类继承父类接口时,由于权限没有设定的更广,出错的一个小tip
今天在写笔记的时候,写的地方出现了小叉叉错号. 发现问题: 这里出错了!原因是因为在子类覆写父类的方法的时候,权限不能开的比父类更低! 加了public后,纠错成功. 由于接口类型下的方法默认都是pu ...
- Java中子类覆盖父类方法所必须满足的条件
因为太喜欢,所以转来,侵删! 参考自:http://www.it165.net/pro/html/201504/39284.html 一.描述 子类重写(覆盖)父类的方法必须满足的条件:1.父类中的方 ...
- C++/JAVA/C#子类调用父类函数情况[留存]
时间久了就容易记不清了,特留存备用查看 c++ 1.构造函数调用 常用初始化列表 或者显示调用 1.1同一个类中构造函数调用构造函数 尽量不要这样做,因为结果不确定!避免麻烦(C++11增加 ...
- Java学习笔记13---如何理解“子类重写父类方法时,返回值若为类类型,则必须与父类返回值类型相同或为其子类”
子类重新实现父类的方法称重写:重写时可以修改访问权限修饰符和返回值,方法名和参数类型及个数都不可以修改:仅当返回值为类类型时,重写的方法才可以修改返回值类型,且必须是父类方法返回值的子类:要么就不修改 ...
- java基础 静态 static 问在多态中,子类静态方法覆盖父类静态方法时,父类引用调用的是哪个方法?
多态 package com.swift.jiekou; public class Jicheng_Tuotai_jingtai_diaoyong { public static void main( ...
- java怎么调用子类中父类被覆盖的方法
public class b { { void show() { System.out.println("b"); } } public class c extends b { v ...
- Java基础-继承-子类与父类执行顺序
代码 public class Test { public static void main(String[] args) { new Circle(); } } class Draw { publi ...
- C#子类重写父类函数的两种方法
(1)使用Virtual关键字Override从写 父类子类代码如下,不能修改public 为其它权限 public virtual void Clear() { UpdateView(); } pu ...
- java基础 super 子类调用父类
如果希望在子类中,去调用父类的构造方法,要求在子类的构造函数调用 example如下: package test; /* * 如果希望在子类中,去调用父类的构造方法,要求在子类的构造函数调用 * */ ...
随机推荐
- 概率编程:《贝叶斯方法概率编程与贝叶斯推断》中文PDF+英文PDF+代码
贝叶斯推理的方法非常自然和极其强大.然而,大多数图书讨论贝叶斯推理,依赖于非常复杂的数学分析和人工的例子,使没有强大数学背景的人无法接触.<贝叶斯方法概率编程与贝叶斯推断>从编程.计算的角 ...
- 【RHEL7/CentOS7网络配置】
目录 网卡配置文件 查网卡信息 测试网络是否正常 使用 nmtui 命令配置网络 使用 nm-connection-editor 工具配置网络 修改回6.x版本的网卡名 Rhel/CentOS网络配置 ...
- [codewars_python]Sum of Digits / Digital Root
Instructions In this kata, you must create a digital root function. A digital root is the recursive ...
- windows下plsql 设置 里面timestamp显示的格式
http://blog.csdn.net/cwjcsu/article/details/9216981
- 【ios开发学习 - 第二课】iOS项目文件夹结构
文件夹结构 AppDelegate Models Macro General Helpers Vendors Sections Resources 一个合理的文件夹结构首先应该是清晰的.让人一眼看 ...
- 怎么样让用户认为产品更有价值?让他们DIY吧!
怎么样让用户认为产品更有价值?用户不须要镶钻.贴金的产品,答案可能比你想的简单,那就是在产品里加入DIY的元素. 几年前,学者做了一系列的调查.他们发现当人们自己打造产品的时候.他们会更加珍惜它,并觉 ...
- STM32的IO口的8种配置
STM32的IO口的8种配置 1 STM32的输入输出管脚有以下8种可能的配置:(4输入+2输出+2复用输出) ① 浮空输入_IN_FLOATING ② 带上拉输入_IPU ③ 带下拉输入_IPD ④ ...
- AngularJS实现cookie跨域
前后端分离被越来越多的公司重视利用.然后带来的最棘手的问题就是.用户信息应怎样保存. 一.场景描写叙述 以Java为后台,AngluarJS做前端为例进行描写叙述:当用户在界面登录时.需把用户信息(如 ...
- Spark MLlib Deep Learning Convolution Neural Network (深度学习-卷积神经网络)3.3
3.Spark MLlib Deep Learning Convolution Neural Network(深度学习-卷积神经网络)3.3 http://blog.csdn.net/sunbow0 ...
- 整理一些PHP开发安全问题
整理一些PHP开发安全问题 php给了开发人员极大的灵活性,可是这也为安全问题带来了潜在的隐患.最近须要总结一下以往的问题,在这里借翻译一篇文章同一时候加上自己开发的一些感触总结一下. 简单介绍 当开 ...