孙鑫视频笔记之——深入理解多态是如何实现的

废话不多说,直接上程序:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Animal
 5 {
 6 public:
 7     Animal()
 8     {
 9         cout << "我是一个类" << endl;
10     }
11     void eat()
12     {
13         cout << "Animal eat!!!" << endl;
14     }
15 };
16 
17 
18 
19 class Dog : public Animal
20 {
21 public:
22     void eat()
23     {
24         cout << "狗吃屎!" << endl;
25     }
26 
27 };
28 
29 
30 int main()
31 {
32     Animal* pAnimal;
33     Dog dog;
34     pAnimal = &dog;//父类的指针指向子类地址,或者说子类地址转化为父类指针
35     pAnimal->eat();
36     return 0;
37 }

 

首先Animal他是一个父类。有一个函数叫eat();

Dog是Animal的子类,并且重写了eat(),(这就是所谓的函数的覆盖,ps:不是重点,呵呵)。

现在,我们想看看父类如何调用子类的eat();这是实现多态的根本。

Animal* pAnimal;   //声明一个父类的指针
Dog dog;                 //创建一个子类的对象
pAnimal = &dog;  //父类的指针指向子类地址,或者说子类地址转化为父类指针

 

这里,不知道您会不会产生一个疑惑,“为什么转换的这么的理所当然?”

要知道Animal和Dog 是不同的类,就像int 和 float也不是随随便便可以转的,

搞不好就是精度丢失,您说是不是。那这里将父类的指针储存子类的地址会不会产生什么副作用呢?答案是否定的。这就不得不说说内存模型了!两个类能否相互转化,就得看他们的内存模型是否匹配。我们看看父类和子类的内存模型是怎样的:

image

我们知道每当创建子类的对象的时候,子类都会调用父类的构造函数,

从而构建父类,(可以理解为,将老爸的基因原原本本的继承下来)。那我们看到

Dog对象的内存分布如上图所示,可以看成老爸的基因在上面,自己的“突变”(基因突变了,哈哈)在下面。

再来看这么一句

pAnimal = &dog;  //父类的指针指向子类地址,或者说子类地址转化为父类指针

因为父类和子类的内存模型是一样的,所以这样转换是没有问题的。

pAnimal->eat();那么这么写,父类能调用到子类的eat()吗?

看看Dog的内存模型,就知道了,其实此时pAnimal->eat();依然调用的是父类的eat();

打印出的是:

image

可惜啊,没有打印出“狗吃屎!”真是可惜了,不急,待哥哥加上一个关键字就万事大吉了!

及在父类的函数eat()前加一个virtual 即可,程序如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class Animal
 5 {
 6 public:
 7     Animal()
 8     {
 9         cout << "我是一个类" << endl;
10     }
11     virtual void eat()
12     {
13         cout << "Animal eat!!!" << endl;
14     }
15 };
16 
17 
18 
19 class Dog : public Animal
20 {
21 public:
22     void eat()
23     {
24         cout << "狗吃屎!" << endl;
25     }
26 
27 };
28 
29 
30 int main()
31 {
32     Animal* pAnimal;
33     Dog dog;
34     pAnimal = &dog;//父类的指针指向子类地址,或者说子类地址转化为父类指针
35     pAnimal->eat();
36     return 0;
37 }

 

image

哈哈,这回爽了吧,啊哈哈~~

 

我们看到仅仅加上一个virtual多态的性质就出来了 ,这是因为,加上virtual之后,C++就会

采用迟绑定技术,而不是在一开始编译的时候就确定调用哪个函数,而是在运行的时候去判断

调用哪个函数,如果子类拥有该函数,则使用子类的,如果子类没有实现(重写)这个函数,

那么还是调用父类的。还有一点值得注意,由于父类的eat是虚函数,子类继承了父类,即使

没有加virtual 子类的eat()也是虚函数!

 

    说完虚函数,再讲纯虚函数,这两个我觉得容易混淆,呵呵,一起来分析下:

将父类的eat()稍加改动:virtual void eat() = 0;这个区别是,函数没有实现。

那么这个就是纯虚函数。拥有纯虚函数的类,被称之为抽象类。抽象类无法创建

实例对象。但是可以被继承,继承他的类,要想实例化对象,必须去实现这个

虚函数。如果不实现,那么该子类同样无法实例化对象,因为此时它也是抽象类了。

   例子就不再举了,自己去试试吧~~

posted @ 2014-07-14 20:17  宋桓公  阅读(436)  评论(0编辑  收藏  举报