C++中 OOP相关的类型转换
我们都知道,在C++中有很多类型转换。今天在这里,我们不讨论普通变量的类型转换(比如int转换成double等等)。本文主要讨论面向对象相关的类型转换:向上转换和向下转换。
首先,我们定义一个基类Base和继承类Derived,相关代码如下:
//Base.h
class Base
{
public:
int i;
Base();
void func1();
virtual void func2();
virtual ~Base();
}
//Derived.h
class Derived: public Base
{
public:
int i;
Derived();
void func1();
virtual void func2();
virtual ~Derived();
}
//Derived.cpp
Derived::Derived()
{
cout<<"Constructor Derived"<<endl;
}
void Derived::func1()
{
cout<<"Derived func1"<<endl;
}
void Derived::func2()
{
i = 3;
cout<<"Derived func2"<<endl;
}
//Base.cpp
Base::Base()
{
i = 1;
cout<<"Base Constructor"<<endl;
}
void Base::func1()
{
cout<<"Base func1"<<endl;
}
void Base::func2()
{
cout<<"Base func2"<<endl;
}
向上转换:指的是子类向基类的转换。即:Derived向Base的转换。
向下转换:指的是基类向子类的转换。即:Base向Derived的转换。
此时,我们需要注意的是:除了一种特殊情况外(我们会在下面讲),所有的向上、向下转换指的都是指针或是引用的转换,而不是普通对象的转换!
首先,在此处我们简要的介绍一下变量的静态类型和动态类型(运行时类型)。由于OOP的引入,使得指针或引用变量存在了这两种类型,在它们声明的时候所标明的类型就是该变量的静态类型,而在程序运行到某处时该变量的实际类型就是动态类型,而普通的变量则只存在静态类型。
正是因为对于指针或引用变量存在着两种变量类型,这种根据运行时的实际类型来调用对应的虚函数的方式,可以简单地理解为动态束定。这也就是OOP中多态机制能够实现的基础。而如果只有普通变量、对象,则就不存在多态(C++中)。
所以我们有如下代码片段,这种将子类转为父类的方式,将父类指针指向子类指针的方式,称为向上转换。向上转换是隐式转换。
Derived *_pD = new Derived();
Base *_pB = _pD;
而下面的代码片段,将父类转换为子类的方式,将子类指针指向父类的方式,称为向下转换。向下转换是必须强制转换的。而且,向下转换的方式是存在一定的危险的,使用时要极其小心的!
Base _oB;
Derived *_pD = (Derived*)&_oB;
下面,我们来通过一段代码来理解一下类型转换时的动态类型和静态类型,以及向下转换的危险性。
1 //Main.cpp
2 void main()
3 {
4
5 Derived _oD;
6 Base _oB;
7
8 //segment 1
9 Base *_pB1 = &_oD;
10 _pB1->func1();
11 _pB1->func2();
12 cout << _pB1->i << endl;
13 cout << endl;
14
15 //segment 2
16 Derived *_pD1 = (Derived*)&_oB;
17 _pD1->func1();
18 _pD1->func2();
19 cout << _pD1->i << endl;
20
21 }
在segment1中,我们进行向上转换,输出的结果如下:
由于func1是普通的函数,所以此处在编译时就已经确定,根据变量的静态类型调用,即调用Base的func1();而func2是虚函数,运行时绑定,根据运行时的实际动态类型来调用,运行时_pB1已经绑定到一个Derived类型对象上,所以此时调用Derived中的func2();最后的变量i也是根据静态类型决定。
在segement2中,我们进行向下转换,输出结果如下:
由于func1是普通的函数,所以此处在编译时就已经确定,根据变量的静态类型调用,即调用Derived的func1();而func2是虚函数,运行时绑定,根据运行时的实际动态类型来调用,运行时_pD1已经绑定到一个Base类型对象上,所以此时调用Base中的func2()。
此处的变量i的值需要讨论,由于变量i的值也是根据静态类型决定,所以应该是Derived中的变量i的值,由于最开始在Derived的构造函数中没有对i进行初始化,所以它的值是未定义的乱值。此时,细心的同学肯定主要到了,我们在Derived的func2中对变量i的值进行了修改,但是似乎没有起到效果。这是由于,func2是虚函数,此时_pD1调用的是Base中的func2函数,因此它修改的相应的是Base域中的i的值,而最后输出时的i,仍是由变量的静态类型决定,输出的仍是Derived域中的i,仍是未初始化的。这也是之前我们提到的,为什么强制进行向下转换存在一定的危险性。
dynamic_cast的引入
但是,我们都知道在实际工程中是要涉及到向下转换的,那么存在这样多的危险性怎么处理。C++中引入了dynamic_cast<type>()。它通过判断在执行到该语句的时候两个变量的运行时类型是否相同来判断是否能够进行向下转换。假设,我们有如下的三个类的关系。
class Geometry;
class Line: public Geometry;
class Curve: public Geometry;
那么,我们就可以在方法中这么写:
void doSomething(Geometry *_piGeom)
{
if(dynamic_cast<Line*>(_piGeom))
...
//do something for Line type else if(dynamic_cast<Curve*>(_piGeom))
...
//do something for Curve type
}
这样就可以避免未定义行为的产生,也可以实现相应的需求。
向上转换的切割(Slice)
最后,回到我们最开始的时候提出的,我们说除了一种特殊情况外(我们会在下面讲),所有的向上、向下转换指的都是指针或是引用的转换,而不是普通对象的转换。向上转换也可以应用于对象之间,如下:
Derived _oD;
Base _oB = _oD;
此时,相当于使用子类对象中的父类部分来对父类对象进行初始化赋值,实际是就是一种切割。
C++中 OOP相关的类型转换的更多相关文章
- [.net 面向对象编程基础] (4) 基础中的基础——数据类型转换
[.net面向对象编程基础] (4)基础中的基础——数据类型转换 1.为什么要进行数据转换? 首先,为什么要进行数据转换,拿值类型例子说明一下, 比如:我们要把23角零钱,换成2.30元,就需要把整形 ...
- 理解CSV文件以及ABAP中的相关操作
在很多ABAP开发中,我们使用CSV文件,有时候,关于CSV文件本身的一些问题使人迷惑.它仅仅是一种被逗号分割的文本文档吗? 让我们先来看看接下来可能要处理的几个相关组件的词汇的语义. Separat ...
- Spring Framework------>version4.3.5.RELAESE----->Reference Documentation学习心得----->Spring Framework中web相关的知识(概述)
Spring Framework中web相关的知识 1.概述: 参考资料:官网documentation中第22小节内容 关于spring web mvc: spring framework中拥有自 ...
- storm-kafka组件中KafkaOffsetMetric相关统计指标说明
storm-kafka组件中KafkaOffsetMetric相关统计指标说明 storm-kafka是storm提供的一个读取kakfa的组件,用于从kafka队列中消费数据.KafkaOffset ...
- Linux中mod相关的命令 内核模块化 mod相关命令都是用来动态加载内核模块/驱动程序模块
Linux中mod相关的命令 内核模块化 mod相关命令都是用来动态加载内核模块/驱动程序模块 http://baike.baidu.com/link?url=lxiKxFvYm-UfJIxMjz ...
- 补充:sql server 中的相关查询、case函数
相关查询(在同一个表中) 相关查询类似子查询,但是又不同于子查询:子查询中的子条件可以单独查出数据,但是相关查询的子条件不能查处数据.(可以理解成C#中for的穷举法,第一个for走一个,第二个for ...
- c语言中数组相关问题
c语言中数组相关问题: 1.数组基本定义: 相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标.组成数组 ...
- 【Socket编程】Java中网络相关API的应用
Java中网络相关API的应用 一.InetAddress类 InetAddress类用于标识网络上的硬件资源,表示互联网协议(IP)地址. InetAddress类没有构造方法,所以不能直接new出 ...
- sqlserver搜索中怎么把varchar类型转换成numeric类型
sqlserver搜索中怎么把varchar类型转换成numeric类型 可以用cast来转换 如:列名叫grade,表名为A select cast(grade as numeric(y,x)) f ...
随机推荐
- python学习道路(day3note)(元组,字典 ,集合,字符编码,文件操作)
1.元组()元组跟列表一样,但是不能增删改,能查.元组又叫只读列表2个方法 一个 count 一个 index2.字典{}字典是通过key来寻找value因为这里功能比较多,所以写入了一个Code里面 ...
- 研究Mysql优化得出一些建设性的方案
博客出自:http://blog.csdn.net/liuxian13183,转载注明出处! All Rights Reserved ! 熟悉网络请求路径,网址经过浏览器的URL验证,是否正确证书是否 ...
- Sublime Text的心得经验。 全面
Sublime Text的心得经验.jikeytang/sublime-text · GitHub
- iOS中的线程安全问题
为了保证线程安全,不会因为多个线程访问造成资源抢夺,出现的运行结果的偏差问题,我们需要使用到线程同步技术,最常用的就是 @synchronized互斥锁(同步锁).NSLock.dispatch_se ...
- 2017年8个UI设计流行趋势
设计趋势变化的理由需要考虑各种各样的因素.让我们来一起看看2017年的设计流行趋势吧. 应用界面的设计趋势是不断变化的.随着时间的推移他也在不断的成长,进化.虽然有些趋势还有待检验,但我们还是需要不断 ...
- libgdx 裁剪多边形(clip polygon、masking polygon)
直接放例子代码,代码中以任意四边形为例,如果需要做任意多边形,注意libgdx不能直接用ShapeRender填充多边形,需要先切割成三角形. public static void drawClip( ...
- magento后台paypal设置
如何在magento后台设置paypal呢? 这边把整理的简单跟大家分享一下. 1.system->config-paypel1.1 Merchant Country 设置国家1.2 Email ...
- 系统无法开始服务器进程。请检查用户名和密码。 (Exception from HRESULT: 0x8000401A)
开始-运行-cmd,输入aspnet_regiis.exe -i 重新注册iis 或者 出现以下错误:检索 COM 类工厂中 CLSID 为 {000209FF-0000-0000-C000-0000 ...
- CSS/HTML 改变鼠标指针形状
改变鼠标指针形状的方法有两种:第一种:用的来改变鼠标指针形状.另一种是:利用第三方控件的方法,而我自己最常用的是第一种:用css样式表来改变鼠标指针形状 我们先来看第一种:用来改变鼠标指针形状. 有些 ...
- FusionChart 数据的传入方式
已有案例,懒得写了,放个链接,大家看看吧.http://www.cnblogs.com/liujian21st/archive/2013/03/22/2975124.html