【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. Java学习-024-获取当前类名或方法名二三文

    今天,看朋友编写程序,打印日志时,需要记录当前类的类名以及当前方法的方法名,我发现 TA 将类名或者方法名直接写死在了代码中...虽说这样可以实现记录类名和方法名,但是当有特殊情况需要修改类名或者方法 ...

  2. 使用 Redis 实现分布式系统轻量级协调技术

    http://www.ibm.com/developerworks/cn/opensource/os-cn-redis-coordinate/index.html 在分布式系统中,各个进程(本文使用进 ...

  3. Interview with BOA

    1. BFS 2. QuickSort 3. PCA, 1000 articles, so many factors, how to reduce factors. 4. newton's metho ...

  4. What algorithm to use to normalize someone's face on image

    http://stackoverflow.com/questions/7066104/what-algorithm-to-use-to-normalize-someones-face-on-image

  5. Android shape的使用(圆角矩形)

    <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http: ...

  6. python AES 双向对称加密解密

    高级加密标准(Advanced Encryption Standard,AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准.这个标准用来替代原先的DES,已经被多方分 ...

  7. php基础语法学习汇总

    常量学习: <?php # function demo function sum($x,$y){ $z=$x+$y; return $z; } echo sum(1,2); #define de ...

  8. Canvas中鼠标获取元素并拖动技术

    Silverlight拖动,需要Canvas. Canvas管网定义: 定义一个区域,在该区域中可以使用相对于该区域的坐标显式定位子元素. XAML <Canvas ...> oneOrM ...

  9. 从零开始攻略PHP(4)——数组的使用

    1.数组的概念 数组就是一个用来存储一系列变量值的命名区域. 每个数组元素有一个相关的索引(也成为关键字),它可以用来访问元素. PHP允许间隔性地使用数字或字符串作为数组的索引. 2.数字索引数组 ...

  10. Java最全文件操作实例汇总

    本文实例汇总了Java文件操作.分享给大家供大家参考,具体如下: 1.创建文件夹 ? 1 2 3 4 5 6 7 8 9 10 11 //import java.io.*; File myFolder ...