【1】程序1

 #include <iostream>
using namespace std; class Base
{
private:
int m_nBase;
public:
Base(int nValue = );
virtual ~Base();
virtual void print();
};
Base::Base(int nValue):m_nBase(nValue)
{
}
Base::~Base()
{
cout << "Base: " << "~Base" << endl;
}
void Base::print()
{
cout << "Base: " << m_nBase << endl;
}
class BA : public Base
{
private:
int m_nBA;
public:
BA(int nValue = );
~BA();
void print();
};
BA::BA(int nValue):m_nBA(nValue)
{
}
BA::~BA()
{
cout << "BA: " << "~BA" << endl;
}
void BA::print()
{
cout << "BA: " << m_nBA << endl;
} void main()
{
BA ba;
Base base = ba;
base.print();
Base& refBase = ba;
refBase.print();
}
/*输出结果:
Base: 100
BA: 200
Base: ~Base
BA: ~BA
Base: ~Base
*/

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

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

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

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

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

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

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

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

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

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

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

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

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

【2】程序2

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{
cout<<""<<endl;
}
}; class something
{
public:
something()
{
cout<<""<<endl;
}
}; class child : public parent
{
public:
child()
{
cout<<""<<endl;
}
protected:
something Mysomething;
}; void main()
{
child MyChild;
}
/*
1
2
3
*/

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

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

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

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

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

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

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

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

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

【3】程序3

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{ cout<<""<<endl; }
~parent()
{ cout<<"~1"<<endl; }
}; class something
{
public:
something()
{ cout<<""<<endl; }
~something()
{ cout<<"~2"<<endl; }
}; class child : public parent
{
public:
child()
{ cout<<""<<endl; }
virtual ~child()
{ cout<<"~3"<<endl; }
protected:
something Mysometing;
}; void main()
{
child * point = new child();
delete point;
}
/*
1
2
3
~3
~2
~1
*/

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

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

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

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

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

【4】程序4

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{ cout<<""<<endl; }
~parent()
{ cout<<"~1"<<endl; }
}; class something
{
public:
something()
{ cout<<""<<endl; }
~something()
{ cout<<"~2"<<endl; }
}; class child : public parent
{
public:
child()
{ cout<<""<<endl; }
~child()
{ cout<<"~3"<<endl; }
protected:
something Mysometing;
}; void main()
{
child * point = new child();
delete point;
}
/*
1
2
3
~3
~2
~1
*/

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

【5】程序5

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{ cout<<""<<endl; }
~parent()
{ cout<<"~1"<<endl; }
}; class something
{
public:
something()
{ cout<<""<<endl; }
~something()
{ cout<<"~2"<<endl; }
}; class child : public parent
{
public:
child()
{ cout<<""<<endl; }
~child()
{ cout<<"~3"<<endl; }
protected:
something Mysometing;
}; void main()
{
parent * point = new child();
delete point;
}
/*
1
2
3
~1
*/

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

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

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

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

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

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

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

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

【6】程序6

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{ cout<<""<<endl; }
void A()
{ cout<<"this is parent"<<endl; }
~parent()
{ cout<<"~1"<<endl; }
}; class something
{
public:
something()
{ cout<<""<<endl; }
void B()
{ cout<<"this is something"<<endl; }
~something()
{ cout<<"~2"<<endl; }
}; class child : public parent, public something
{
public:
child()
{ cout<<""<<endl; }
virtual ~child()
{ cout<<"~3"<<endl; }
}; void main()
{
child * point = new child();
point->A();
point->B();
delete point;
}
/*
1
2
3
this is parent
this is something
~3
~2
~1
*/

我们再来如下变换一下:

【7】程序7

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{ cout<<""<<endl; }
void A()
{ cout<<"this is parent"<<endl; }
~parent()
{ cout<<""<<endl; }
}; class something
{
public:
something()
{ cout<<""<<endl; }
void A()
{ cout<<"this is something"<<endl; }
~something()
{ cout<<""<<endl; }
}; class child : public parent, public something
{
public:
child()
{ cout<<""<<endl;}
virtual ~child()
{ cout<<""<<endl;}
}; void main()
{
child * point = new child();
// point->A(); //二义性
delete point;
}

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

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

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

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

【8】程序8

 void main()
{
child * point = new child();
point->something::A();//调用something的
point->parent::A();//调用parent的
delete point;
}

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

【9】程序9

 #include <iostream>
using namespace std; class parent
{
public:
parent()
{ cout<<""<<endl; }
void A()
{ cout<<"this is parent"<<endl; }
~parent()
{ cout<<"~1"<<endl; }
}; class something
{
public:
something()
{ cout<<""<<endl; }
void A()
{ cout<<"this is something"<<endl; }
~something()
{ cout<<"~2"<<endl; }
}; class child : public parent, public something
{
public:
child()
{ cout<<""<<endl;}
virtual void A()
{ something::A(); }
virtual ~child()
{ cout<<"~3"<<endl;}
}; void main()
{
child * point = new child();
point->A();
delete point;
}
/*
1
2
3
this is something
~3
~2
~1
*/

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

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

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

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

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

【10】程序10

 #include <iostream>
using namespace std; class 动物
{
public:
virtual void 吃()= ;
virtual void 睡()= ;
}; class 小鸟 : public 动物
{
public:
virtual void 吃()
{
// to do
}
virtual void 睡()
{
// to do
}
}; class 猫 : public 动物
{
public:
virtual void 吃()
{
//to do
}
virtual void 睡()
{
// to do
}
}; class 猫头鹰 : public 猫, public 小鸟
{
public:
virtual void 吃()
{
// 猫::吃()
}
virtual void 睡()
{
// 鸟::睡()
}
};

这样以来,就各取所需,什么都不用写,直接调用就好,不过,大家应该注意到了我们的动物这个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. logback详细配置(三)

    转自:http://blog.csdn.net/haidage/article/details/6794540 <filter>: 过滤器,执行一个过滤器会有返回个枚举值,即DENY,NE ...

  2. python_条件、循环语句

    1. python中语句块如何定义: 在Python中,冒号(:)用来标识语句块的开始,块中的每一个语句都是缩进的.当回退到和已经闭合的块一样的缩进量时,就表示当前块已经结束.      默认推荐缩进 ...

  3. H3C S3600-28TP-SI配置命令

    模式分类为:<H3C> 用户视图       [H3C] 系统视图    [H3C-Ethernet1/0/1] 以太网端口视图 [H3C-vlan10] VLAN视图   [H3C-Vl ...

  4. MvvmLight 绑定

    添加MvvmLight引用,通过Nuget: 加载nuget以后会有ViewModelLocator.cs: 新建自己的ViewModel,继承ViewModelBase: View 通过资源引用Vi ...

  5. iOS网络协议 HTTP/TCP/IP浅析

    一.TCP/IP协议       话说两台电脑要通讯就必须遵守共同的规则,就好比两个人要沟通就必须使用共同的语言一样.一个只懂英语的人,和一个只懂中文的人由于没有共同的语言(规则)就没办法沟通.两台电 ...

  6. iOS block在两个页面间的简单传值

    #import <UIKit/UIKit.h> @interface AppDelegate : UIResponder <UIApplicationDelegate> @pr ...

  7. mysql在线改表结构 pt-online-schema-change

    https://www.percona.com/doc/percona-toolkit/2.1/pt-online-schema-change.html 不锁表更改数据库表结构 pt-online-s ...

  8. linux:档案与档案系统的压缩、打包与备份

    压缩比:压缩后与压缩的档案锁占用的磁碟空间大小,就称之为压缩比 压缩技术: a.将没有使用到的空间丢出去,以让档案资料占用的空间变小 b.将重复的资料统计记录(比如100个1,不是真正的用100个元位 ...

  9. IntelliJ IDEA 常用设置讲解2

    IntelliJ IDEA 有很多人性化的设置我们必须单独拿出来讲解,也因为这些人性化的设置让我们这些 IntelliJ IDEA 死忠粉更加死心塌地使用它和分享它. 常用设置 如上图 Gif 所示, ...

  10. 使用Mac的AppleScritp调用控制台的方式

    使用Mac的AppleScritp调用 控制台的方法 tell application "Terminal" activate do script "cd Documen ...