Java、C++中子类对父类函数覆盖的可访问性缩小的区别介绍

 更新时间:2018年01月08日 08:44:34   作者:DevBean  
这篇文章主要给大家介绍了关于Java、C++中子类对父类函数覆盖的可访问性缩小的区别的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧。

前言

“Java 和 C++ 中子类对父类函数覆盖的可访问性缩小的问题”的题目看起来比较学术化,但的确是一个容易忽视的问题。本文力求详细阐述这一问题在 Java 以及 C++ 中的区别。

先介绍什么是“子类对父类函数覆盖的可访问性缩小”。对于继承而言,子类可以覆盖父类的“虚函数”——尽管 Java 中没有虚函数这一术语,但可以把 Java 的所有函数都看作虚函数,因为 Java 的所有函数都可以被子类覆盖。这里仅借用“虚函数”这一名词的含义,不深究语言的细节。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 行会有编译错误——这段代码根本不能通过编译! Java 不允许子类在覆盖父类函数时,缩小可访问性。 至于原因,我们可以用一个例子来说明。例如我们在类外部写下面的代码:

Base base = new Base();
base.sayHello();
base = new Child();
base.sayHello();

假如之前的代码可以通过编译,那么就存在这么一种可能:当 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++ 的标准则会明显简化编译器实现,但是对工程而言并不算很好的参考。

PS:C++ 标准的正式版是需要购买的,但是草案可以免费下载。C++ 标准草案的下载地址可以在下面的页面找到: https://isocpp.org/std/the-standard

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

相关文章

  • 实例讲解JAVA 适配器模式

    实例讲解JAVA 适配器模式

    这篇文章主要介绍了JAVA 适配器模式的的相关资料,文中示例代码非常详细,供大家参考和学习,感兴趣的朋友可以了解下
    2020-06-06
  • java组件smartupload实现上传文件功能

    java组件smartupload实现上传文件功能

    这篇文章主要为大家详细介绍了java组件smartupload实现上传文件功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • spring boot 监控处理方案实例详解

    spring boot 监控处理方案实例详解

    这篇文章主要介绍了spring boot 监控处理方案的相关资料,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-07-07
  • Mybatis关联查询之一对多和多对一XML配置详解

    Mybatis关联查询之一对多和多对一XML配置详解

    这篇文章主要介绍了Mybatis关联查询之一对多和多对一XML配置详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • 四种Java线程池用法解析

    四种Java线程池用法解析

    这篇文章主要为大家解析四种Java线程池用法,内容详细,分析细致,感兴趣的小伙伴们可以参考一下
    2016-04-04
  • java设计模式之观察者模式

    java设计模式之观察者模式

    这篇文章主要为大家详细介绍了java设计模式之观察者模式的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-12-12
  • Java中HashMap和TreeMap的区别深入理解

    Java中HashMap和TreeMap的区别深入理解

    首先介绍一下什么是Map。在数组中我们是通过数组下标来对其内容索引的,而在Map中我们通过对象来对对象进行索引,用来索引的对象叫做key,其对应的对象叫做value
    2012-12-12
  • Java中断线程的方法

    Java中断线程的方法

    这篇文章主要介绍了Java中断线程的方法,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-05-05
  • Java获取任意http网页源代码的方法

    Java获取任意http网页源代码的方法

    这篇文章主要介绍了Java获取任意http网页源代码的方法,可实现获取网页代码以及去除HTML标签的代码功能,涉及Java正则操作相关实现技巧,需要的朋友可以参考下
    2017-09-09
  • java实现单词查询小程序

    java实现单词查询小程序

    这篇文章主要为大家详细介绍了java实现单词查询小程序,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-07-07

最新评论