【1】程序1

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class Base
  5. {
  6. private:
  7. int m_nBase;
  8. public:
  9. Base(int nValue = );
  10. virtual ~Base();
  11. virtual void print();
  12. };
  13. Base::Base(int nValue):m_nBase(nValue)
  14. {
  15. }
  16. Base::~Base()
  17. {
  18. cout << "Base: " << "~Base" << endl;
  19. }
  20. void Base::print()
  21. {
  22. cout << "Base: " << m_nBase << endl;
  23. }
  24. class BA : public Base
  25. {
  26. private:
  27. int m_nBA;
  28. public:
  29. BA(int nValue = );
  30. ~BA();
  31. void print();
  32. };
  33. BA::BA(int nValue):m_nBA(nValue)
  34. {
  35. }
  36. BA::~BA()
  37. {
  38. cout << "BA: " << "~BA" << endl;
  39. }
  40. void BA::print()
  41. {
  42. cout << "BA: " << m_nBA << endl;
  43. }
  44.  
  45. void main()
  46. {
  47. BA ba;
  48. Base base = ba;
  49. base.print();
  50. Base& refBase = ba;
  51. refBase.print();
  52. }
  53. /*输出结果:
  54. Base: 100
  55. BA: 200
  56. Base: ~Base
  57. BA: ~BA
  58. Base: ~Base
  59. */

main函数中的两句代码看上去大意差不多,差别就在于一个是引用而一个不是引用,然后结果却相去甚远,大家明白这是为什么吗?

大家在看到结果的时候第一反应一定是这两个看似相同的对象却调用了不同的方法(print()),想到这里说明已经足够产生了疑问。

那么我们来看这中间到底发生了什么呢?

第一句,看上去不难懂,是用一个BA类型的对象用来初始化Base类型的对象,这中间发生了强制转换,强制转换有问题吗?

我们不都会经常用到强制转换,很多时候我们为了数据的精准而将int转换为double,但是大家有没有为了数据精准而把double转换为int的呢?

显然没有,因为潜意识里我们知道如果将double数据转换为int,那么小数点后面的东西就会被扔掉。

同样,如果把派生类转换为基类,也可以是说把子类转换为父类,由于子类是继承了基类的所有方法。

所以,子类中的方法只会比基类多不会少,这样以来,大家应该就明白了。同样,这种转换发生了切割。

简单点说来,此时base虽然是用ba对象来初始化,事实上它彻彻底底的就是base类型的对象,所以它调用的一切方法都是base类的。

那么,引用为什么就可以正确显示呢?

这就是关键了,这里和大家说一下,不但引用的可以做到,指针也一样可以做到。

所以,当我们使用父类引用或者指针来引用子类时,子类仍然正确保留它覆盖的方法。

上面这点要记住,下面我们来继续新内容,先看下面的:

【2】程序2

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. {
  9. cout<<""<<endl;
  10. }
  11. };
  12.  
  13. class something
  14. {
  15. public:
  16. something()
  17. {
  18. cout<<""<<endl;
  19. }
  20. };
  21.  
  22. class child : public parent
  23. {
  24. public:
  25. child()
  26. {
  27. cout<<""<<endl;
  28. }
  29. protected:
  30. something Mysomething;
  31. };
  32.  
  33. void main()
  34. {
  35. child MyChild;
  36. }
  37. /*
  38. 1
  39. 2
  40. 3
  41. */

现在大家思考一下,我们这个程序会输出什么?

可能有些朋友会问,主函数不过只是构造了一个child的对象而已,并没有什么输出语句。

主页君是不是脑子被门夹了?还是进水了呢?

当然,可能有些又会想,这个程序构造了一个child对象,理所应当要调用child的构造函数,所以应该会输出3,这种想法合情合理,

或许,厉害的朋友一定看出来了,要构造child对象:

首先,要调用child的父类的构造函数,所以最开始会输出1,接着在child构造出对象之前会初始化child的相关数据(Mysomething),

这时就会调用something的构造函数,于是又输出2,最后才是child的构造函数,所以才输出1。

嗯,看来确实是123,如果说大家都能够想到这一层,那么关于继承我真没啥好给大家说的了,不过为了照顾一下其他朋友,还是决定说说。

还是上面的例子,我们再把析构函数加上去看看会发生什么:

【3】程序3

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. { cout<<""<<endl; }
  9. ~parent()
  10. { cout<<"~1"<<endl; }
  11. };
  12.  
  13. class something
  14. {
  15. public:
  16. something()
  17. { cout<<""<<endl; }
  18. ~something()
  19. { cout<<"~2"<<endl; }
  20. };
  21.  
  22. class child : public parent
  23. {
  24. public:
  25. child()
  26. { cout<<""<<endl; }
  27. virtual ~child()
  28. { cout<<"~3"<<endl; }
  29. protected:
  30. something Mysometing;
  31. };
  32.  
  33. void main()
  34. {
  35. child * point = new child();
  36. delete point;
  37. }
  38. /*
  39. 1
  40. 2
  41. 3
  42. ~3
  43. ~2
  44. ~1
  45. */

通过上面的例子,大家应该能够想到会输出什么了。

child * point = new child();构造对象,所以当程序执行这句代码的时候和上面的例子一样,自然会输出123。

但是,当程序执行delete point的时候就会逐一调用相应的析构函数,那么从哪里开始呢?

当然从child开始,然后再析构相关数据,最后才析构父类。

我们不妨再换个思路去思考一个问题,如果我们将上面的析构函数中的virtual去掉,会是什么样的呢?输出还是一样的输出:

【4】程序4

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. { cout<<""<<endl; }
  9. ~parent()
  10. { cout<<"~1"<<endl; }
  11. };
  12.  
  13. class something
  14. {
  15. public:
  16. something()
  17. { cout<<""<<endl; }
  18. ~something()
  19. { cout<<"~2"<<endl; }
  20. };
  21.  
  22. class child : public parent
  23. {
  24. public:
  25. child()
  26. { cout<<""<<endl; }
  27. ~child()
  28. { cout<<"~3"<<endl; }
  29. protected:
  30. something Mysometing;
  31. };
  32.  
  33. void main()
  34. {
  35. child * point = new child();
  36. delete point;
  37. }
  38. /*
  39. 1
  40. 2
  41. 3
  42. ~3
  43. ~2
  44. ~1
  45. */

我们再把执行片段修改一下:

【5】程序5

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. { cout<<""<<endl; }
  9. ~parent()
  10. { cout<<"~1"<<endl; }
  11. };
  12.  
  13. class something
  14. {
  15. public:
  16. something()
  17. { cout<<""<<endl; }
  18. ~something()
  19. { cout<<"~2"<<endl; }
  20. };
  21.  
  22. class child : public parent
  23. {
  24. public:
  25. child()
  26. { cout<<""<<endl; }
  27. ~child()
  28. { cout<<"~3"<<endl; }
  29. protected:
  30. something Mysometing;
  31. };
  32.  
  33. void main()
  34. {
  35. parent * point = new child();
  36. delete point;
  37. }
  38. /*
  39. 1
  40. 2
  41. 3
  42. ~1
  43. */

嗨,好像哪里不对?怎么会这样呢?

我们构造对象的时候调用了三个构造函数,而且我们在堆上构造,

所以在回收资源的时候理所应当要将所有的资源回收,也便内存泄漏。

然后我们上面这个例子,却只收回了一块内存,这自然就会造成传说中的内存泄漏了。

如果用在大程序中,会带来严重的后果!!!

当然如果我们很严谨的在所有析构函数面前加上了virtual的话,输出就会正常。

那么现在大家是不是明白了virtual的重要性了呢?

现在我们再来看看另一种形式:

【6】程序6

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. { cout<<""<<endl; }
  9. void A()
  10. { cout<<"this is parent"<<endl; }
  11. ~parent()
  12. { cout<<"~1"<<endl; }
  13. };
  14.  
  15. class something
  16. {
  17. public:
  18. something()
  19. { cout<<""<<endl; }
  20. void B()
  21. { cout<<"this is something"<<endl; }
  22. ~something()
  23. { cout<<"~2"<<endl; }
  24. };
  25.  
  26. class child : public parent, public something
  27. {
  28. public:
  29. child()
  30. { cout<<""<<endl; }
  31. virtual ~child()
  32. { cout<<"~3"<<endl; }
  33. };
  34.  
  35. void main()
  36. {
  37. child * point = new child();
  38. point->A();
  39. point->B();
  40. delete point;
  41. }
  42. /*
  43. 1
  44. 2
  45. 3
  46. this is parent
  47. this is something
  48. ~3
  49. ~2
  50. ~1
  51. */

我们再来如下变换一下:

【7】程序7

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. { cout<<""<<endl; }
  9. void A()
  10. { cout<<"this is parent"<<endl; }
  11. ~parent()
  12. { cout<<""<<endl; }
  13. };
  14.  
  15. class something
  16. {
  17. public:
  18. something()
  19. { cout<<""<<endl; }
  20. void A()
  21. { cout<<"this is something"<<endl; }
  22. ~something()
  23. { cout<<""<<endl; }
  24. };
  25.  
  26. class child : public parent, public something
  27. {
  28. public:
  29. child()
  30. { cout<<""<<endl;}
  31. virtual ~child()
  32. { cout<<""<<endl;}
  33. };
  34.  
  35. void main()
  36. {
  37. child * point = new child();
  38. // point->A(); //二义性
  39. delete point;
  40. }

这段程序会编译不过,因为出现时方法名字二义性,为什么呢?

因为我们在parent里面定义了方法A,在something里面也定义了一个方法A,而这两个类都是child的父类,

当child调用A方法的时候编译器却蒙了,到底要使用那个呢?

或许我们继承something其实只是想要调用他的A方法,所以我们可以这样来解决:

【8】程序8

  1. void main()
  2. {
  3. child * point = new child();
  4. point->something::A();//调用something的
  5. point->parent::A();//调用parent的
  6. delete point;
  7. }

当然,如同上面我们所说的,我们继承something其实只想调用他重用A方法,所以我们不要这么麻烦,我们可以这样重写这个方法:

【9】程序9

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class parent
  5. {
  6. public:
  7. parent()
  8. { cout<<""<<endl; }
  9. void A()
  10. { cout<<"this is parent"<<endl; }
  11. ~parent()
  12. { cout<<"~1"<<endl; }
  13. };
  14.  
  15. class something
  16. {
  17. public:
  18. something()
  19. { cout<<""<<endl; }
  20. void A()
  21. { cout<<"this is something"<<endl; }
  22. ~something()
  23. { cout<<"~2"<<endl; }
  24. };
  25.  
  26. class child : public parent, public something
  27. {
  28. public:
  29. child()
  30. { cout<<""<<endl;}
  31. virtual void A()
  32. { something::A(); }
  33. virtual ~child()
  34. { cout<<"~3"<<endl;}
  35. };
  36.  
  37. void main()
  38. {
  39. child * point = new child();
  40. point->A();
  41. delete point;
  42. }
  43. /*
  44. 1
  45. 2
  46. 3
  47. this is something
  48. ~3
  49. ~2
  50. ~1
  51. */

这样就可以完美解决我们上面的问题了。那么大家是不是会想怎么会有这样现象呢?

就比如,小鸟,猫,猫头鹰 ,他们都同属于动物。

他们都吃东西,都睡觉,猫头鹰和猫一样都会吃老鼠,而小鸟不会 。

但是猫头鹰和小鸟又属于鸟一类,他们的作息方式应该差不多(我也只是猜测,反正这里只是给大家作为例子来说的)。

现在,我们看到,猫头鹰不但具有小鸟的属性还同时具有猫的一些特性,所以我们可以让他继承猫和小鸟。我们应该怎样来实现呢?

【10】程序10

  1. #include <iostream>
  2. using namespace std;
  3.  
  4. class 动物
  5. {
  6. public:
  7. virtual void 吃()= ;
  8. virtual void 睡()= ;
  9. };
  10.  
  11. class 小鸟 : public 动物
  12. {
  13. public:
  14. virtual void 吃()
  15. {
  16. // to do
  17. }
  18. virtual void 睡()
  19. {
  20. // to do
  21. }
  22. };
  23.  
  24. class : public 动物
  25. {
  26. public:
  27. virtual void 吃()
  28. {
  29. //to do
  30. }
  31. virtual void 睡()
  32. {
  33. // to do
  34. }
  35. };
  36.  
  37. class 猫头鹰 : public 猫, public 小鸟
  38. {
  39. public:
  40. virtual void 吃()
  41. {
  42. // 猫::吃()
  43. }
  44. virtual void 睡()
  45. {
  46. // 鸟::睡()
  47. }
  48. };

这样以来,就各取所需,什么都不用写,直接调用就好,不过,大家应该注意到了我们的动物这个class,里面全部是纯虚函数。

哦,对了,什么是纯虚函数呢?就是在声明虚函数的同时让他等于0,这样一来,拥有纯虚函数的类就成了抽象类,抽象类天生就是作为基类的。

就是为了解决名字二义性问题的,所以他不能像普通的类一样,他不能定义对象,他所定义的方法都是在派生类中实现,根据不同的要求来实现。

Good  Good  Study, Day  Day  Up.

顺序  选择  循环  总结

继承(引用~析构~virtual)的更多相关文章

  1. C++ 在继承中使用virtual

    使用virtual:如果方法是通过引用类型或指针而不是对象调用的,它将确定使用哪一种方法.如果没有使用关键字irtual,程序将根据引用类型或指针类型选择方法:如果使用了irtual,程序将根据引用或 ...

  2. C++ 学习笔记 (七)继承与多态 virtual关键字的使用场景

    在上一篇 C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量 中说了当父类子类有同名函数时在外部调用时如果不加父类名则会默认调用子类的函数.C++有函数重写的功能需要添加virtual关键字 ...

  3. c++继承构造析构调用原则以及特殊变量处理

    一.继承中的构造析构调用原则 1.子类对象在创建时会首先调用父类的构造函数 2.父类构造函数执行结束后,执行子类构造函数 3.当父类构造函数有参数时,需要在子类的初始化列表中显示调用: 4.析构函数调 ...

  4. C++ 虚析构(virtual destructor)原理

    注意:本文仅为个人理解,可能有误! 先看一段代码: #include <iostream> using namespace std; class CBase{ public: CBase( ...

  5. 【c++】一道关于继承和析构的笔试题

    题目如下,求输出结果 class A { public: A() { cout<<"A"<<endl; } ~A() { cout<<" ...

  6. C/C++ 随笔目录

    [1]基础部分 (1)宏定义 <assert> <offset宏> <#pragma once> <宏定义学习> <预处理语句> <# ...

  7. 阻止新的csproj工程的dll引用继承

    VisualStudio传统的csproj工程中,引用是没有继承功能的.例如,对于如下一个引用关系 App引用Assembly 1 Assembly 1引用Assembly 2 程序App在没有添加A ...

  8. C++类继承方式及实践

    直接上图: 以及: 实践如下: #include <iostream> using namespace std; class Father{ private: int father1; i ...

  9. c/c++: c++继承 内存分布 虚表 虚指针 (转)

    http://www.cnblogs.com/DylanWind/archive/2009/01/12/1373919.html 前部分原创,转载请注明出处,谢谢! class Base  {  pu ...

随机推荐

  1. imx6 mac地址设置

    imx6的mac地址总是固定的值,所以需要更改,采用的方法是在uboot中设置环境变量,之后在kernel中使用uboot中设置的mac地址的值.本文记录更改的过程. 参考链接: http://www ...

  2. Mybatis的分页插件PageHelper

    Mybatis的分页插件PageHelper 项目地址:http://git.oschina.net/free/Mybatis_PageHelper  文档地址:http://git.oschina. ...

  3. ie8兼容border-radius方法

    <!doctype html><html> <head>        <meta charset="utf-8" />    &l ...

  4. 通往成功的钥匙--Web前端开发技术

    互联网是一个服务性行业,用户对网站良好的体验度,直接影响到网站的效果.无论你做了多少广告推广,没有用户体验度等于零.Web前端技术是为了解决用户体验度而诞生的.无论是百度.新浪.阿里巴巴等大型网站,还 ...

  5. centos python 2.7 安装

    一开始有这个需求,是因为用 YaH3C 替代 iNode 进行校园网认证时,一直编译错误,提示找不到 Python 的某个模块,百度了一下,此模块是在 Python2.7 以上才有的,但是系统的自带的 ...

  6. OSG第一个Demo

    环境:Vs2010 OpenSceneGraph-3.0.1-VS10.0.30319-x86-debug-12741 OpenSceneGraph-3.0.1-VS10.0.30319-x86-re ...

  7. 学习"大众点评网的架构设计与实践"

    今天看了一篇"程序员"上的文章:"大众点评网的架构与实践",因为里面谈的架构演变之路中所经历的痛点对我的工作经验来说感同身受,所以觉得文章里的一些解决方案对我还 ...

  8. kfed (kernel file editor:内核文件编辑器)

    kfed是没有在文档中标出的asm工具,在oracle 11gR1中被引入.可以被用来读写asm元数据,特别是磁盘头和asm元数据的内容. kfed是一个单独的工具,不依赖与asm实例,所以可以对mo ...

  9. js 多选题选项内容显示在标题下

    <body><div class="page-container"> <div class="view-container"> ...

  10. Swift游戏实战-跑酷熊猫 06 创建平台类以及平台工厂类

    这节内容我们一起学习下随机长度的踩踏平台的原理是怎么样的. 要点: 平台类 我们的平台类继承于SKNode,这样就能被添加进其它节点进而显示在场景中. 它有一个方法来创建平台,这个方法接收一个包含SK ...