明明白白c++ 虚函数和多态性

来源:互联网 发布:清除垃圾软件 编辑:程序博客网 时间:2024/06/05 02:46

纸上得来终觉浅,绝知此事要躬行。

本文的目的,一方面是作者本人希望通过写文章来弄清楚虚函数和多态性,另一方面,也希望读者可以自己练习练习,不写代码,不编程序,只看别人的代码和文章很难把知识转换为自己的。大家一起加油。

一 概念

多态性是面向对象(OOP)语言的一个共有的特性。

OOP 语言的三大特性:封装,继承和多态。下面文章讲述的很好,很干练。我就不班门弄斧了。

http://www.cnitblog.com/Lily/archive/2013/01/03/6860.html


多态的意思就是不同的对象,接受到同一消息的时候,产生不同的动作。

举个例子来说,比如狗,鸟,鱼,人都属于生物这一类,如果发生地震了,他们都接受了逃命这个消息,狗会四条腿的跑走,鸟会飞走,鱼会游走,人则是两条腿的跑走。

我们可以认为这个就是一个多态的例子。

可以参考下面文章的含义

http://blog.csdn.net/livelylittlefish/article/details/2171445

 不好意思以前这里写错了(讲的很清楚,多态有两种C++,一种是覆盖,编译的时候确定,静态绑定,一种是重载,运行的时候确定,动态绑定。)

后来理解了一下,多态c++有两种,一种是静态绑定,例如重载,另外一种是动态绑定,带有virtual关键字的。

c++ primer里面有句话,在编译的时候确定非virtual的函数


重载,我这里就不说了,就是函数的参数表不一样。

二 隐藏

这个其实很好理解,类似于全局变量和局部变量,那么局部变量就会隐藏全局变量。

局部函数和全局函数,局部函数的概念这里指 static函数。

看下面一段代码

在static.cpp里面定义了一个print.

#include <iostream>using namespace std;static void print(){   cout<<"static"<<endl;}void callstatic(){  print();}

在global.cpp里面定义另一个print

#include <iostream>using namespace std;void print(){   cout<<"global"<<endl;}extern void callstatic();int main(){    callstatic();print();}

程序输出的结果是:

    static

    global

程序很简单,说一下需要注意的地方:

1 如果static.cpp里面不加入关键字static在print函数前面,编译的时候会报链接错误。

      c/c++里面不是在类里面的函数,不加关键字static就是全局函数,所有有两个全局函数,连接器就会报错。

       static保证函数只是在此文件中使用。

2 extern这个关键字,对于函数是可有可无的,都表示函数声明。

   它会去找函数的定义,在连接的时候,上面和这个结合起来,大家就明白为什么要static来表示函数了。

   但是extern在对全局变量声明的时候,必须用。(ps:后面我会写一下,声明,定义,初始化三个之间的关系,对于初学者就不至于稀里糊涂了)

3 我联想到全局变量,局部变量,静态变量三者直接的关系,也放在后面的文章写吧。(太多容易混淆了,写文章的时候,发现处处机关,处处学问。)

而类的隐藏和这里的隐藏应该是一个意思

类里面的隐藏,废话少说了,代码为证。

#include <iostream>using namespace std;class A{public:   void print()   {       cout <<"class A"<<endl;   }};class B:public A{public:    void print(){    cout <<"class B"<<endl;    }};int main(){        A a;A *pA;B b;B *pB;        cout<<sizeof(A)<<" "<<sizeof(B)<<endl;        a.print();        b.print();        pA = &a;pA->print();        pA = &b;pA->print();        //pB = &a; 从类型‘A*’到类型‘B*’的转换无效     //pB->print();     pB = &b;   pB->print();}

结果为:

1  1

class A

class B

class A

class A

class B

有几个需要注意的:

1 class B:public class A 这个public不能少,一旦少了,变成了私有继承,那么pA = &b就会报错,错误:‘A’是‘B’不可访问的基类  

这里解释一下,pA指向&b, 其实就是指向b继承的a部分,如果这部分是私有的话,那么指针自然无法访问。   画图比较麻烦,如果不了解的话可以评论里面回复,我会解答,或者补发一下图文,不然就当理解了。

2 如果class B: private class A  这个时候,那么在class B里面会有两个print,一个是继承A的私有的,另一个自己的。   

我自己做了一下测试,总结一下隐藏的意思并不是说父类里面的函数没有了,调用函数的类指针,对象,引用的类型决定了调用哪个函数?   指针,对象或者引用是A则调用A, 是B则调用B, 。另外其实私有继承或者方法是私有的,隐藏本身意义 不大,不用太去深究,因为外部无法调用。

我觉得隐藏更多是出于兼容c的语法,而不是为了设计的多样性,这样很容易混淆,而且实际应用价值不是很大。

所以在java里面不存在的隐藏,因为java本身没有指针,这样就没有意义。

写这么多只是表面隐藏的语法,至于其实际应用价值,我认为没有。反而觉得利大于弊。

三 覆盖,或者重写(ovrride)  (虚函数)

覆盖和隐藏的共同点是:

函数名字,函数参数必须一样。
不同点是:
关键字 virtual是否存在。

虚函数,以前我看到这个就头疼,觉得乱七八糟的,而且觉得c++设计的太乱了,又要兼容c,又要面向对象,规则细节一大堆。

光类的初始化之类的都一大堆,导致我每次还没有看到虚函数,就放弃了。终于鼓起勇气好好写一下了。

大家可以看一下java和c++的对比http://blog.csdn.net/fuxingwe/article/details/8849028还有http://www.cnblogs.com/harryguo/archive/2008/06/16/1222976.html

写了下java里面的多态性的实现,可以看到java就完全摒弃了隐藏,这样看起来更加舒服点。

#include <iostream>using namespace std;class A{public:   void print()   {       cout <<"class A"<<endl;   }   virtual void vrPrint()   {cout <<"class A vr"<<endl;   }};class B:public A{public:    void print(){    cout <<"class B"<<endl;    }virtual void vrPrint()    {cout <<"class B vr"<<endl;    }};int main(){    A a;A *pA;B b;B *pB;cout<<sizeof(A)<<" "<<sizeof(B)<<endl;a.vrPrint();b.vrPrint();pA = &a;pA->vrPrint();pA = &b;pA->vrPrint();pB = &b;pB->vrPrint();}

输出结果是:
4 4
class A vr
class B vr
class A vr
class B vr
class B vr
有两个变化
1 类大小变了, 上面的大小是1,而这里是4,
2 pA = &b  时候, pA的打印变了。  
看一下这个时候pa的虚函数表



引用c++ primer里面的话:引用和指针的静态类型与动态类型可以不同,这是 C++ 用以支持多态性的基石。

下图是虚函数表:可以看到A 和B的完全不一样。



那么 怎么去调用父类的方法呢?

pA = &b;

pA->A::print();

这样就可以了,如果你是在继承类的函数里面想调用,切记要加上::限定符,不然就是死循环,因为它不断的调用自己。 来自c++ primer