继承(引用~析构~virtual)
【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)的更多相关文章
- C++ 在继承中使用virtual
使用virtual:如果方法是通过引用类型或指针而不是对象调用的,它将确定使用哪一种方法.如果没有使用关键字irtual,程序将根据引用类型或指针类型选择方法:如果使用了irtual,程序将根据引用或 ...
- C++ 学习笔记 (七)继承与多态 virtual关键字的使用场景
在上一篇 C++ 学习笔记 (六) 继承- 子类与父类有同名函数,变量 中说了当父类子类有同名函数时在外部调用时如果不加父类名则会默认调用子类的函数.C++有函数重写的功能需要添加virtual关键字 ...
- c++继承构造析构调用原则以及特殊变量处理
一.继承中的构造析构调用原则 1.子类对象在创建时会首先调用父类的构造函数 2.父类构造函数执行结束后,执行子类构造函数 3.当父类构造函数有参数时,需要在子类的初始化列表中显示调用: 4.析构函数调 ...
- C++ 虚析构(virtual destructor)原理
注意:本文仅为个人理解,可能有误! 先看一段代码: #include <iostream> using namespace std; class CBase{ public: CBase( ...
- 【c++】一道关于继承和析构的笔试题
题目如下,求输出结果 class A { public: A() { cout<<"A"<<endl; } ~A() { cout<<" ...
- C/C++ 随笔目录
[1]基础部分 (1)宏定义 <assert> <offset宏> <#pragma once> <宏定义学习> <预处理语句> <# ...
- 阻止新的csproj工程的dll引用继承
VisualStudio传统的csproj工程中,引用是没有继承功能的.例如,对于如下一个引用关系 App引用Assembly 1 Assembly 1引用Assembly 2 程序App在没有添加A ...
- C++类继承方式及实践
直接上图: 以及: 实践如下: #include <iostream> using namespace std; class Father{ private: int father1; i ...
- c/c++: c++继承 内存分布 虚表 虚指针 (转)
http://www.cnblogs.com/DylanWind/archive/2009/01/12/1373919.html 前部分原创,转载请注明出处,谢谢! class Base { pu ...
随机推荐
- Selenium2学习-006-WebUI自动化实战实例-004-解决 Chrome 浏览器证书提示:--ignore-certificate-errors
此文主要讲述 Java 运行 Selenium 脚本时,如何消除 Chrome 浏览器启动后显示的证书错误报警提示,附带 Chrome 参数使浏览器最大化的参数. 希望能对初学 Selenium2 W ...
- attr属性
1.切换图片 $(".tip").hover( function () { $(this).attr("src"," ...
- perl常见符号
=> 键值对,左键右值 -> 引用,相当于java中的 [对象.方法名]中的点号 :: 表示调用类的一个方法 % 散列的标志,定义一个键值对类型的 @ 数组的标志 $ 标量的标志 =~ ...
- git 入门 2
进入d盘,新建project文件, 右键,git bash here cd project 初始化 $ git init 克隆项目 $ git clone http://192.168.1.188:3 ...
- JBOSS /invoker/JMXInvokerServlet 利用工具 .
链接: http://pan.baidu.com/s/1F8bMI 密码: 1h2r 工具使用说明 1. 查看系统名称 java -jar jboss_exploit_fat.jar -i http: ...
- 分布式消息系统Kafka初步
终于可以写kafka的文章了,Mina的相关文章我已经做了索引,在我的博客中置顶了,大家可以方便的找到.从这一篇开始分布式消息系统的入门. 在我们大量使用分布式数据库.分布式计算集群的时候,是否会遇到 ...
- 11月16日《奥威Power-BI基于SQL的存储过程及自定义SQL脚本制作报表》腾讯课堂开课啦
上周的课程<奥威Power-BI vs微软Power BI>带同学们全面认识了两个Power-BI的使用情况,同学们已经迫不及待想知道这周的学习内容了吧!这周的课程关键词—— ...
- SignalR实时聊天功能
使用vs2013新建一个空的asp.net 工程 添加SignalR集线器类MyHub.cs using System; using System.Collections.Generic; using ...
- HTML语言的一些元素(三)
本章节主要介绍:<div>和<span> 可以通过 <div> 和 <span> 将 HTML 元素组合起来. HTML <div> 元素是 ...
- CommonJS规范
CommonJS是一种规范,NodeJS是这种规范的实现.CommonJS是一 个不断发展的规范,计划将要包括如下部分: Modules Binary strings and buffers Char ...