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;

此时,相当于使用子类对象中的父类部分来对父类对象进行初始化赋值,实际是就是一种切割

时间: 2024-10-10 05:08:58

C++中 OOP相关的类型转换的相关文章

C语言中随机数相关问题

用C语言产生随机数重要用到rand函数.srand函数.及宏RAND_MAX(32767),它们均在stdlib.h中进行了声明. int rand(void);//生成一个随机数 voidsrand(unsigned int seed); //为rand设置"种子"的值 srand()就是给rand()提供种子seed,如果srand每次输入的数值是一样的,那么每次运行产生的随机数也是一样的.通常的做法是以这样一句代码: srand((unsigned)time(NULL)); 来取

补充:sql server 中的相关查询、case函数

相关查询(在同一个表中) 相关查询类似子查询,但是又不同于子查询:子查询中的子条件可以单独查出数据,但是相关查询的子条件不能查处数据.(可以理解成C#中for的穷举法,第一个for走一个,第二个for走一圈,在相关查询中,括号内的数据只有几个,外面的查询有全部的数据,每个数据到括号中去比较是否合适) 格式: select * from 表名1 as a where a.列名 关系表达式或逻辑运算符 ( select * from 表名1 as b where a.相关列名1 = b.相关列名1

理解CSV文件以及ABAP中的相关操作

在很多ABAP开发中,我们使用CSV文件,有时候,关于CSV文件本身的一些问题使人迷惑.它仅仅是一种被逗号分割的文本文档吗? 让我们先来看看接下来可能要处理的几个相关组件的词汇的语义. Separator:两个字段之间的界线,在CSV文件中即是“,”. Delimiter:这种符号的开端和结束,代表了某种东西的界限.举个例子“测试字符串”有两个delimiters,即两个双引号.在很多逗号需要成为文本的情况下,这些CSV文件会使用双引号作为Delimiter. Terminator : 代表片段

(笔记)Linux内核中内存相关的操作函数

linux内核中内存相关的操作函数 1.kmalloc()/kfree() static __always_inline void *kmalloc(size_t size, gfp_t flags) 内核空间申请指定大小的内存区域,返回内核空间虚拟地址.在函数实现中,如果申请的内存空间较大的话,会从buddy系统申请若干内存页面,如果申请的内存空间大小较小的话,会从slab系统中申请内存空间.有关buddy和slab,请参见<linux内核之内存管理.doc> gfp_t flags 的选项

HTML中CSS相关选择器

1.选择器的分类: 1 h1,h2,h3,h4 { 2 color:red; 3 } 2.派生选择器: 1 li strong{ 2 color:red; 3 } 3.id选择器: 1 1 #red{ 2 color:red; 3 } 4 #green{ 5 color:green; 6 } 8 9 HTML: 10 <p id="red">I am red</p> 11 <p id="green">I am green</

Java中Map相关的快速查找算法与唯一性(转载)

原文地址:http://blog.csdn.net/chuyuqing/article/details/19629229 在对<Set和hashCode()>的一篇原创文章写完后,由于对自己的一些论断产生了模糊和怀疑,因此又对Set进行了一些研究,形成本篇. 在Set的使用场景中,我们不外乎看中了她存储数据的唯一性,即不能存储重复值,这在某些应用场合下是很必要的一个特性.那么从更深一层来考虑,Set究竟如何使数据不重复的呢?从另一个层面来考虑,她又如何确保在验证数据是否重复过程中的快速性呢?假

原生 JS 中对象相关 API 合集

https://juejin.im/entry/58f8a705a0bb9f0065a4cb20 原文链接:https://microzz.com/2017/04/20/jsobject/ 原生 JavaScript 中对象相关 API 合集 - 对象篇.现在 jQuery 已经没有那么有优势了,原生 JS 赶紧学起来... -- 由microzz分享 Microzz [email protected] 主页 文章 标签 GitHub 关于我 掘金专栏 SegmentFault Vue音乐播放器

【Socket编程】Java中网络相关API的应用

Java中网络相关API的应用 一.InetAddress类 InetAddress类用于标识网络上的硬件资源,表示互联网协议(IP)地址. InetAddress类没有构造方法,所以不能直接new出一个对象: InetAddress类可以通过InetAddress类的静态方法获得InetAddress的对象: 1 InetAddress.getLocalHost();//获取本地对象 2 InetAddress.getByName("");//获取指定名称对象 主要方法使用: 1 /

c语言中条件编译相关的预编译指令

一. 内容概述 本文主要介绍c语言中条件编译相关的预编译指令,包括#define.#undef.#ifdef.#ifndef.#if.#elif.#else.#endif.defined. 二.条件编译 条件编译是根据实际定义宏(某类条件)进行代码静态编译的手段.可根据表达式的值或某个特定宏是否被定义来确定编译条件. 最常见的条件编译是防止重复包含头文件的宏,形式跟下面代码类似: 1 #ifndef ABCD_H 2 #define ABCD_H 3 4 // ... some declarat