纸上得来终觉浅,绝知此事要躬行。
本文的目的,一方面是作者本人希望通过写文章来弄清楚虚函数和多态性,另一方面,也希望读者可以自己练习练习,不写代码,不编程序,只看别人的代码和文章很难把知识转换为自己的。大家一起加油。
一 概念
多态性是面向对象(OOP)语言的一个共有的特性。
OOP 语言的三大特性:封装,继承和多态。下面文章讲述的很好,很干练。我就不班门弄斧了。
http://www.cnitblog.com/Lily/archive/2013/01/03/6860.html
多态的意思就是不同的对象,接受到同一消息的时候,产生不同的动作。
举个例子来说,比如狗,鸟,鱼,人都属于生物这一类,如果发生地震了,他们都接受了逃命这个消息,狗会四条腿的跑走,鸟会飞走,鱼会游走,人则是两条腿的跑走。
我们可以认为这个就是一个多态的例子。
可以参考下面文章的含义
http://blog.youkuaiyun.com/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) (虚函数)
覆盖和隐藏的共同点是:
虚函数,以前我看到这个就头疼,觉得乱七八糟的,而且觉得c++设计的太乱了,又要兼容c,又要面向对象,规则细节一大堆。
光类的初始化之类的都一大堆,导致我每次还没有看到虚函数,就放弃了。终于鼓起勇气好好写一下了。
大家可以看一下java和c++的对比http://blog.youkuaiyun.com/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