关于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层的所有初始化代码。