關于public,protected和private三個關鍵詞的理解
可以說,接觸C++的時間也不短了,但是對這三個詞一直都是一知半解,直到今天,才覺得自己基本上領悟了這個三個關鍵詞的真正含義,而且也能把握的更準確一些,而我理解這三個詞的角度應該說是很獨特很獨特的,沒準世界上沒有其他的人會這樣“有見地”的去如此理解,哈哈,好了,閑話少說,下面就給出我的理解:
分三個層次:即對于類自身而言;對類外部而言;對子類繼承方式而言。
對于類自身而言:
public代表完全不受限制;
protected代表這些東東(函數或者成員)除了我自己用之外還能給我的后代用,當然我指得是在后代的內部用而不是通過后代去暴露我的隱私(注意,這些東東是我的,我有權利決定為它們作主,既然我已經表態了要給我的后代用,因此我就不會理會我的后代是用何種方式<public,protected還是private都無所謂>來繼承我);
private代表這些東東只能我自己用啦,別人再也不要覬覦它們了,不管是誰(包括我的后代)。
對于類外部而言:
當沒有繼承時,只有被聲明為public的東東才能“.”或者“->”出來,其他的兩種聲明方式都不可以向外部暴露;當存在繼承時,也很簡單,只有這個東東的“從聲明到一步步的繼承方式都為public”時,才可以向外部暴露,除此之外就別無它法了。
對于子類的繼承方式而言:
public繼承方式,啥都不改變,基類的成員或函數聲明時啥樣就是啥樣;
protected繼承方式,基類給我的東東(也就是用public或protected修飾的東東)都被變成protected的了,哈哈,雖然作為子類我沒啥牛的,但是現在至少我通過自己的努力把基類的public都給搞成protected的啦,因此要是有一些類再從我這里派生的話,基類的public的東東它們都只能當作protected的來用啦,別再想暴露基類的這些public的東東了,因為我已經做了手腳(用protected繼承),從而限制了我的后代的行為;
private繼承方式,把基類給我的東東都變成private的了,哈哈,這下我的后代得哭了,因為基類的好東東它們啥也不能用了,看來我雖然是子類,還是有一定權利的喲,至少我可以控制我后代的行為。
對于這部分,相對于其他兩部分來說,也是最難理解的,因此我再多說兩句,說到本質上,關于基類,當前類,當前類的子類這三者而言,既然它們都是在一條鏈條上,因此彼此之間肯定是有關系的,這種關系就表現在上級對下級的控制上,也就是上級直接影響的對象是緊挨著它的下級,就是通過這樣的方式(上級在自身所擁有的東東上施加的各種修飾符;對于那些不是上級自身定義的東東<而是繼承過來的東東>,雖然沒有辦法直接施加修飾符,但是可以通過用不同的修飾符繼承來達到控制的效果)來一環扣一環的影響下去的。
因此,我自身定義的東東我說了算,基類的東東就只有基類說了算啦,準備要給子類的東東是由基類和我來一起說了算的。因此舉個例子來說的話,A中有個public或protected的變量m_nA,B通過private的方式繼承了A,那么m_nA在B內當然是可以隨便使用的了,但是如果C從B那里繼承,即便是public繼承,那么C中是不可以使用m_nA的,因為B已經壞壞的通過private繼承方式把m_nA這個資源給截獲了,別人不能再用了,哈哈,B太壞了。
總結出一句很經典的話:不同的基承方式只能影響子類(子類能不能使用這些好東東)和外部(這些東東能不能暴露給外部),而對自身沒有影響。
為了把這篇文章便得豐滿一些,我再拽點別的吧,當然也是與這三個詞相關的很重要的東西,哈哈。
在這三個詞中,我愿意把public自己歸為一類,而把另兩個歸為一類,因為這兩類有本質的區別,首先來說說public,D : public B,代表的是一種isa的關系,也就是“D是一個B”,就像說“XXX是一個人”一樣正確,提到public繼承,你能想到什么?你可以問問自己,我估計你想到的應該是“基類的函數或者成員在子類的可見性之類的”,但是我問大家一個問題“我們可以隨意的把兩個類X和Y拿過來,然后讓Y: public X嗎?”你當然會說不行,為啥不行?我告訴你答案:從語法上來說,這么做是完全可行的,但從語義上來理解是不可行的,比如基類是人,你總不能從這個基類繼承出一個磚頭的子類來吧?那么如何來驗證這種繼承的可行性呢?說到這里大家就必須清楚繼承或者派生的本質含義了——D是B;D更具體,B更一般化;“只要是B能做的事情,D一定要也能做才行”。看到它的本質了吧?那么知道了這些就太容易說出為啥磚頭不能繼承人了,因為人能做的事情磚頭是做不了的。因此我們通常在面向對象中提到的基承都是指的public,而protected和private這兩種方式已經完全破壞了基承的語義和體系,它們的存在只是為了滿足特定的需要,在這兩種方式之下,D和B沒有任何概念上的關系,我們不能說D就是B或者其他用于描述他們彼此之間關系的話,因為他們已經不存在任何關系了。那么這兩種方式的存在是要做什么呢?答案是:他們要描述一種“根據某物實現(is-implemented-in-terms-of)”的關系,也就是說,D要利用B的成員函數的代碼或成員變量來構建自身,D只是為了從B那里得到一些實惠和好處。因此在這兩種繼承方式下,不能夠再類型轉化了(因為B和D已經沒有關系了,D不是B,B更不是D),進而帶來的一個直接后果就是我們不能利用通常的方法來進行多態了,比如在B和D中都定義一個virtual void test()的函數,然后試圖利用D d; B* pb=&d; pb->test()來調用D的test函數,這是根本行不通的因為B* pb=&d這一步已經是錯的了,這種轉型會失敗。既然提到了多態,那就順帶說一下,只要是發生在子類和基類之間,只要是在基類中被virtual聲明的函數就都可以實現多態性,而實現的方法是“只要你可以通過一定的方法把一個對多態函數的調用給暴露出去即可”;從根本上來說,在基類中聲明函數為virtual,就是為了允許子類能夠重新定義該函數的,大家都知道類的成員函數內部(除了被static函數修飾的之外)都是隱含了一個this指針的,當這個進入到一個成員函數內部時,只要滿足“this實際指向的是子類,且基類已經把某個函數定義為virtual的了,當然子類中確實已經override了該函數,然后后就可以快樂的實現多態了”。下面我就給出一個在如此“艱難”的困境下,多態依然可以突破層層束縛完成其職責的一段代碼,來吧,欣賞一下:
class P1{
virtual int foo(){return 10;}
public:
void test(){cout<<foo()<<endl;}
};
class P2 : private P1{
virtual int foo(){return 20;}
public:
void haha(){test();}
};
(new P2)->haha();//輸出竟然是20,成功應用了多態性,注意這里面隱藏了兩個private,因此是三個private的限制。
最后再說說virtual繼承的方式,其實這個東西沒啥奇怪的,主要就是用在鉆石型的多重繼承問題中,比如B和C繼承自A,而D又繼承自B和C,因此構成一個鉆石,如果不加任何其他處理的話,那么在D中就會有兩份A,這顯然不對,因此為了防止此情況出現,就需要B和C在繼承A的時候,要用virtual這種繼承方式了,這樣就可以保證在D中只有一份A而不是兩份。還有一點需要針對virtual繼承來說的就是“初始化這樣一個基類A時,要把初始化的代碼放在最最深的子類初始化處”,對于通常的繼承體系初始化,也是一種一環扣一環的去操作,也就是第N層子類初始化時負責N-1層的類,而N-1層的類在初始化時負責N-2層的,依次上溯。但是對于基類被virtual繼承的情況,比如它出現在第M層,那么除了要在第M+1層初始化代碼中給它初始化外,還需要在M+2,M+3,...,直至最最深的第N層處的初始化代碼中給它初始化,也就是需要在多個地方填寫它的初始化代碼,大家都知道一個類只能被初始化一次,那么對于它而言,咱們給它寫了這么多用于初始化的代碼,它到底調用哪一份來初始化自己呢,其實很簡單,就是隨著你定義的那個類走,比如你定義了一個出現在第K層的(M<=K<=N)某個類,自然這個類有一份用于初始化它的代碼,那么OK,就用這份代碼就可以,而不會依次調用從K層到M層的所有初始化代碼。