前言
“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 html" target="_blank">对象时,事情发生了改变——对于 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.
简单翻译过来有两条要点:
正因如此,C++ 的调用方似乎可以通过一些技巧性转换,“巧妙地”调用到原本无法访问的函数。一个更加实际的例子是:Qt 里面, QObject::event() 函数是 public ,而其子类 QWidget 的 event() 函数则改变成 protected 。具体可以阅读 Qt 的相关代码。
总结来说,在子类覆盖父类函数时,Java 严格限制了子类不能缩小函数可访问性,但 C++ 无此限制。个人认为,从软件工程的角度来说,Java 的规定无疑更具有工程上面的意义,函数的调用也更加一致。C++ 的标准则会明显简化编译器实现,但是对工程而言并不算很好的参考。
PS:C++ 标准的正式版是需要购买的,但是草案可以免费下载。C++ 标准草案的下载地址可以在下面的页面找到: https://isocpp.org/std/the-standard
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对小牛知识库的支持。
问题内容: 得知子类的类变量无法访问父类的类变量而没有特别指出父类的名称,我感到很惊讶: 为什么在定义By时我必须引用Ax,而不仅仅是x?这与我对实例变量的直觉是相反的,并且因为在定义B之后我可以引用Bx。 问题答案: 在Python中,在创建类之前,将在其自己的名称空间中执行类的主体(此后,该名称空间的成员将成为该类的成员)。因此,当解释器达到y = x + 1时,此时B类尚不存在,因此没有父类
问题内容: 与我的其他问题略相关:以下内容之间有什么区别: 同样,最后2个之间的差异是我最感兴趣的。 问题答案: 任何包中的类都可以访问公共类。 具有默认访问权限()的类仅对同一包中的其他类可见。 private和protected修饰符只能应用于内部类。 私有类仅对其封闭类以及同一封闭类中的其他内部类可见。 受保护的类对于同一包中的其他类以及扩展该封闭类的类都是可见的。
好吧,我不确定如何表达这个标题(因为我知道子类通常被称为继承类,这不是我在问题中提到的(是的,我知道我从我的例子中的抽象类继承方法。这更多的是为了避免这里的代码重复,并且与我的问题无关),所以我将尽可能地给出一个最好的例子。 我有一个大约有130个属性的类对象,由我的winforms项目中的私有DataTable对象支持。这些多数属性将在数据表中查找一些列(并且可能在将来更新数据表中的所述值)。其
问题内容: 我有一个扩展其超类的子类的对象。子类中有一个重写的方法,可以使用对象调用该方法。可以使用子类对象调用超类的函数吗? 考虑上面的代码。 它在这里打印 子类去 。相反,我必须打印 超类去 。 问题答案: 不,这是不可能的,如果您认为需要,请重新考虑您的设计。覆盖方法的全部要点是替换其功能。如果一个不同的类对该类的内部工作非常了解,那么您将完全取消封装。
我想创建一个抽象的父类,其中包含方法公共抽象使用();和使用方法在不同的子类是不同的,但利用父方法的所有Rest,但我希望父类包含一个私有变量,但它需要设置从特定的孩子取决于孩子所以像我有一个家长车,我想与汽车一般工作,但为特定目的的use()方法和设置汽车图像我使用保时捷类这是一个孩子。我不希望汽车图像属性是可访问的,但同时如果它的私人我不能从子构造函数设置它,有什么想法吗?我的问题是如何设置一
当我创建一个带有父对象引用的子对象时,比如