Java、C++中子類對父類函數(shù)覆蓋的可訪問性縮小的區(qū)別介紹
前言
“Java 和 C++ 中子類對父類函數(shù)覆蓋的可訪問性縮小的問題”的題目看起來比較學(xué)術(shù)化,但的確是一個(gè)容易忽視的問題。本文力求詳細(xì)闡述這一問題在 Java 以及 C++ 中的區(qū)別。
先介紹什么是“子類對父類函數(shù)覆蓋的可訪問性縮小”。對于繼承而言,子類可以覆蓋父類的“虛函數(shù)”——盡管 Java 中沒有虛函數(shù)這一術(shù)語,但可以把 Java 的所有函數(shù)都看作虛函數(shù),因?yàn)?Java 的所有函數(shù)都可以被子類覆蓋。這里僅借用“虛函數(shù)”這一名詞的含義,不深究語言的細(xì)節(jié)。Java 和 C++ 都允許在覆蓋時(shí),改變函數(shù)的可訪問性。所謂“可訪問性”,就是使用 public 、 protected 、 private 等訪問控制符進(jìn)行修飾,用來控制函數(shù)能否被訪問到。通??稍L問性的順序?yàn)椋ㄓ捎?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()
函數(shù)。父類 Base 中,該函數(shù)使用 protected 訪問控制符進(jìn)行修飾。而子類將其改用 public ,這不會有任何問題。 子類對父類函數(shù)覆蓋時(shí),擴(kuò)大可訪問性,通常都不是問題。
當(dāng)子類對父類函數(shù)覆蓋的可訪問性縮小時(shí),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 不允許子類在覆蓋父類函數(shù)時(shí),縮小可訪問性。 至于原因,我們可以用一個(gè)例子來說明。例如我們在類外部寫下面的代碼:
Base base = new Base(); base.sayHello(); base = new Child(); base.sayHello();
假如之前的代碼可以通過編譯,那么就存在這么一種可能:當(dāng) base 指向 new Base() 時(shí), sayHello() 是可以訪問到的,但是當(dāng) base 指向 new Child() 時(shí), sayHello() 卻無法訪問到!在 Java 看來這是一個(gè)矛盾,應(yīng)該避免出現(xiàn)這種問題,因此,Java 從編譯器的角度規(guī)定我們不能寫出上面的代碼。
針對 C++,情況又有所區(qū)別。來看 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++ 中是完全正確的。注意,這里的子類在覆蓋父類函數(shù)時(shí), 縮小 了可訪問性。如果你沒有看出有什么問題,那么我們完全可以在類外部寫出下面的代碼:
Child child; child.sayHello(); // 不能通過編譯,因?yàn)?sayHello() 是 private 的 static_cast<Base&>(child).sayHello(); // 可以通過編譯,因?yàn)?sayHello() 是 public 的
第 2 行調(diào)用是失敗的,因?yàn)樵?Child 中, sayHello()
是 private 的,不能在外部調(diào)用。然而,當(dāng)我們使用 static_cast 將 Child 強(qiáng)制轉(zhuǎn)換成 Base 對象時(shí),事情發(fā)生了改變——對于 Base 而言, sayHello()
是 public 的,因此可以正常調(diào)用。
針對這一點(diǎn),C++ 標(biāo)準(zhǔn)的 Member access control 一章中的 Access to virtual functions 一節(jié)可以找到如下的例子:
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++ 標(biāo)準(zhǔn)給出的解釋是:
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.
簡單翻譯過來有兩條要點(diǎn):
- 訪問控制是在調(diào)用時(shí)檢查的,也就是說,誰調(diào)用了這個(gè)函數(shù),就檢查誰能不能訪問這個(gè)函數(shù)
- 類中成員函數(shù)的可訪問性一般而言是不知道的,也就是說,檢查可訪問性時(shí),并不能知道這個(gè)函數(shù)在定義時(shí)到底是 public 的還是 private 的,因此也就無法據(jù)此檢查可訪問性
正因如此,C++ 的調(diào)用方似乎可以通過一些技巧性轉(zhuǎn)換,“巧妙地”調(diào)用到原本無法訪問的函數(shù)。一個(gè)更加實(shí)際的例子是:Qt 里面, QObject::event()
函數(shù)是 public ,而其子類 QWidget 的 event()
函數(shù)則改變成 protected 。具體可以閱讀 Qt 的相關(guān)代碼。
總結(jié)來說,在子類覆蓋父類函數(shù)時(shí),Java 嚴(yán)格限制了子類不能縮小函數(shù)可訪問性,但 C++ 無此限制。個(gè)人認(rèn)為,從軟件工程的角度來說,Java 的規(guī)定無疑更具有工程上面的意義,函數(shù)的調(diào)用也更加一致。C++ 的標(biāo)準(zhǔn)則會明顯簡化編譯器實(shí)現(xiàn),但是對工程而言并不算很好的參考。
PS:C++ 標(biāo)準(zhǔn)的正式版是需要購買的,但是草案可以免費(fèi)下載。C++ 標(biāo)準(zhǔn)草案的下載地址可以在下面的頁面找到: https://isocpp.org/std/the-standard
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關(guān)文章
mybatis Interceptor對UpdateTime自動處理的實(shí)現(xiàn)方法
這篇文章主要給大家介紹了關(guān)于使用mybatis Interceptor對UpdateTime自動處理的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧2018-12-12Spring中的@ConditionalOnProperty注解使用詳解
這篇文章主要介紹了Spring中的@ConditionalOnProperty注解使用詳解,在 spring boot 中有時(shí)候需要控制配置類是否生效,可以使用 @ConditionalOnProperty 注解來控制 @Configuration 是否生效,需要的朋友可以參考下2024-01-01使用java采集京東商城區(qū)劃數(shù)據(jù)示例
這篇文章主要介紹了java采集京東的全國區(qū)劃數(shù)據(jù)示例,保存成json形式,如想轉(zhuǎn)換到數(shù)據(jù)庫只需反序列化為對象保存到數(shù)據(jù)庫即可2014-03-03關(guān)于weblogic部署Java項(xiàng)目的包沖突問題的解決
這篇文章主要介紹了關(guān)于weblogic部署Java項(xiàng)目的包沖突問題的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01SpringBoot實(shí)現(xiàn)各種參數(shù)校驗(yàn)總結(jié)(建議收藏!)
本文深入解析了Spring?Validation的使用方法、實(shí)現(xiàn)原理及最佳實(shí)踐,詳細(xì)介紹了各種參數(shù)校驗(yàn)場景,如requestBody和requestParam/PathVariable的使用,并探討了分組校驗(yàn)、嵌套校驗(yàn)和自定義校驗(yàn)的高級應(yīng)用,需要的朋友可以參考下2024-09-09SpringMVC中Model和ModelAndView的EL表達(dá)式取值方法
下面小編就為大家分享一篇SpringMVC中Model和ModelAndView的EL表達(dá)式取值方法,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-03-03