广告位联系
返回顶部
分享到

C++之RTTI和cast运算符的使用介绍

C语言 来源:互联网 作者:佚名 发布时间:2022-08-20 08:18:37 人浏览
摘要

1. RTTI RTTI是运行阶段类型识别(Running Type Identificarion)的简称。 如何知道指针指向的是哪种对象? 这是个很常见的问题,由于我们允许使用基类指针指向派生类,所以基类指针指向的对象可

1. RTTI

RTTI是运行阶段类型识别(Running Type Identificarion)的简称。

如何知道指针指向的是哪种对象?

这是个很常见的问题,由于我们允许使用基类指针指向派生类,所以基类指针指向的对象可能是基类对象,也可能是派生类对象。但是我们需要知道对象种类,因为我们需要使用正确的类方法。

RTTI能解决上述问题。

1.1 dynamic_cast运算符

dynamic_cast是最常用的RTTI组件,它不能回答"指针指向的是哪类对象",但是它能回答"是否可以安全的将对象的地址赋给特定类型指针"。

什么是安全?

例如:A是基类,B是A的派生类,C是B的派生类。那么将BC对象的地址赋给A指针是安全的,而反向就是不安全的。因为公有继承满足is-a关系,指针和引用进行向上转换是安全的,向下转换就是不安全的。

dynamic_cast的用法是:

dynamic_cast<Type*>(ptr)或dynamic_cast<Type&>(ref)

可以看到的是,dynamic_cast是一种类型转换符,它支持指针间的转换,他将ptr的类型转换成Type*,如果这种转换是安全的,那会返回转换后的指针;如果不安全那就会返回空指针。对于引用的话,他将ref的类型转换成Type&,如果这种转换是安全的,那就返回转换后的引用;如果不安全那就会引发bad_cast异常。

总之,dynamic_cast类型转换运算符比C风格的类型转换要安全的多,而且它能够判断转换是否安全。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

//RTTI1.cpp

#include<iostream>

#include<cstdlib>

#include<ctime>

using namespace std;

class Grand

{

protected:

    int hold;

public:

    Grand(int h=0):hold(h){};

    virtual void Speak() const {cout<<"I am Grand class!\n";}

};

class Superb:public Grand

{

public:

    Superb(int h=0):Grand(h){}

    virtual void Speak() const {cout<<"I am a superb class!\n";}

    virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}

};

class Magnificent:public Superb

{

private:

    char ch;

public:

    Magnificent(int h=0,char c='A'):Superb(h),ch(c){}

    virtual void Speak() const{cout<<"I am a Magnificent class!\n";}

    virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}

};

Grand *GetOne();

int main()

{

    srand(time(0));

    Grand *pg;

    Superb *ps;

    for(int i=0;i<5;i++)

    {

        pg=GetOne();

        pg->Speak();

        if(ps=dynamic_cast<Superb*>(pg))

        {

            ps->Say();

        }

    }

    delete pg;

    return 0;

}

Grand * GetOne()

{

    Grand *p;

    switch (rand()%3)

    {

        case 0:

            p=new Grand(rand()%100);

            break;

        case 1:

            p=new Superb(rand()%100);

            break;

        case 2:

            p=new Magnificent(rand()%100,'A'+rand()%26);

            break;

    }

    return p;

}

I am a Magnificent class!
the character: I
the value: 74
I am a superb class!
the value: 50
I am Grand class!
I am Grand class!
I am a Magnificent class!
the character: V
the value: 99

上面这个例子说明了重要的一点:尽可能使用虚函数,而只在必要时使用RTTI。

如果将dynamic_cast用于引用,当请求不安全的时候,会引发bad_cast异常,这种异常时从exception类派生而来的,它在头文件typeinfo中定义。

则我们可以使用如下方式处理异常:

1

2

3

4

5

6

7

8

9

10

#include<typeinfo>

...

try{

    Superb & rs= dynamic_cast<Superb &>(rg);

    ....

}

catch(bad_cast &)

{

    ...

};

1.2 typeid运算符

type_info类是在头文件typeinfo中定义的一个类。type_info类重载了==和!=运算符。

typeid是一个运算符,它返回一个对type_info的引用,它可以接受2种参数:类名和对象。typeid是真正回答了"指针指向的是哪类对象"。

(实际上,typeid也可以用于其他类型,例如结构体,函数指针,各种类型,只要sizeof能接受的,typeid都能接受)

用法:

typeid(Magnificent)==typeid(*pg)

如果pg是一个空指针,则会引发bad_typeid异常。

type_info类中包含一个name()接口,该接口返回字符串,一般情况下是类的名称。

cout<<typeid(*pg).name()<<".\n"

例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

//RTTI2.cpp

#include<iostream>

#include<cstdlib>

#include<ctime>

#include<typeinfo>

using namespace std;

class Grand

{

protected:

    int hold;

public:

    Grand(int h=0):hold(h){};

    virtual void Speak() const {cout<<"I am Grand class!\n";}

};

class Superb:public Grand

{

public:

    Superb(int h=0):Grand(h){}

    virtual void Speak() const {cout<<"I am a superb class!\n";}

    virtual void Say() const {cout<<"the value: "<<Grand::hold<<endl;}

};

class Magnificent:public Superb

{

private:

    char ch;

public:

    Magnificent(int h=0,char c='A'):Superb(h),ch(c){}

    virtual void Speak() const{cout<<"I am a Magnificent class!\n";}

    virtual void Say() const{cout<<"the character: "<<ch<<endl<<"the value: "<<Grand::hold<<endl;}

};

Grand *GetOne();

int main()

{

    srand(time(0));

    Grand *pg;

    Superb *ps;

    for(int i=0;i<5;i++)

    {

        pg=GetOne();

        cout<<"Now processing type "<<typeid(*pg).name()<<".\n";

        pg->Speak();

        if(ps=dynamic_cast<Superb*>(pg))

        {

            ps->Say();

        }

        if(typeid(Magnificent)==typeid(*pg))

        {

            cout<<"Yes,you are really magnificent.\n";

        }

    }

    delete pg;

    return 0;

}

Grand * GetOne()

{

    Grand *p;

    switch (rand()%3)

    {

        case 0:

            p=new Grand(rand()%100);

            break;

        case 1:

            p=new Superb(rand()%100);

            break;

        case 2:

            p=new Magnificent(rand()%100,'A'+rand()%26);

            break;

    }

    return p;

}

Now processing type 6Superb.      
I am a superb class!
the value: 64
Now processing type 11Magnificent.
I am a Magnificent class!
the character: Y
the value: 30
Yes,you are really magnificent.   
Now processing type 5Grand.       
I am Grand class!
Now processing type 11Magnificent.
I am a Magnificent class!
the character: C
the value: 3
Yes,you are really magnificent.   
Now processing type 5Grand.       
I am Grand class!

注意,你可能发现了typeid远比dynamic_cast优秀的多,typeid会直接告诉你类型是什么,而dynamic_cast只告诉你是否可以类型转换。但是typeid的效率比dynamic_cast低很多,所以,请优先考虑dynamic_cast和虚函数,如果不行才使用typeid。

2. cast运算符

C语言中的类型转换符太过随意,C++创始人认为,我们应该使用严格的类型转换,则C++提供了4个类型转换运算符:

  • dynamic_cast
  • const_cast
  • static_cast
  • reinterpret_cast

dynamic_cast运算符已经介绍过了,它的用途是,使得类层次结构中进行向上转换,而不允许其他转换。

const_cast运算符,用于除去或增加 类型的const或volatile属性。

它的语法是:

const_cast<type-name>(expression)

如果类型的其他方面也被修改,则上述类型转换就会出错,也就是说,除了cv限定符可以不同外,type_name和expression的类型必须相同。

提供该运算符的目的是:有时候我们需要一个值:它在大多数情况下是常量,而有时候我们需要更改它。

看一个有趣的例子:

1

2

3

4

5

6

7

8

9

10

11

12

13

//cast运算符1.cpp

//cast运算符1.cpp

#include <iostream>

int main()

{

    using std::cout;

    using std::endl;

    volatile const int a=100;

    volatile const int & ra=a;//无法通过ra修改a

    int &change_a=const_cast<int &>(ra);//可以通过change_a修改a;

    change_a=255;

    cout<<a<<endl;

}

上面最后这个a常量被修改成255,这是我们预期的结果。但是如果我把volatile关键词去掉,那么a的值还是100。其实是编译器在这里玩了个小聪明,const int a=100,编译器认为a就不会发生改变了,所以就把a放在了寄存器中,因为访问寄存器要比访问内存单元快的多,每次都从寄存器中取数据,但是后来a在内存中发生变化后,寄存器中的数据没有发生变化,所以打印的是寄存器中的数据。解决这一问题,就使用volatile关键词,那么对a的存取都是直接对内存做操作的。

static_cast运算符

它的语法是:

static_cast<type-name>(expression)

仅当type-name可被隐式转换成expression所属的类型或者expression可以隐式转换成type-name类型时,上述转换才合法,否则出现bad_cast异常。

例如,枚举值可以隐式转换成整型,那么整型就可以通过static_cast转换成枚举型。

总之,static_cast只允许"相似"的类型间的转换,而不允许"差异很大"的类间的转换。

reinterpret_cast运算符:重新诠释类型,进行疯狂的类型转换

它的语法时:

reinterpret_cast<type-name>(expression)

它可以进行类型间"不可思议"的互换,但是它不允许删除const,也不允许进行丧失数据的转换

例如下面这两个例子:

1

2

3

4

5

6

7

8

9

#include<iostream>

int main()

{

    using namespace std;

    struct dat {short a; short b;};

    long value =0xA224B118;

    dat *pd=reinterpret_cast<dat*>(&value);

    cout<<hex<<pd->a;

}

1

2

3

4

5

6

7

8

9

10

11

12

#include<iostream>

void fun()

{

    std::cout<<"hello world!\n";

}

int main()

{

    using namespace std;

    void (*foo)();

    foo=fun;

    int* p=reinterpret_cast<int*>(foo);

}

通常,reinterpret_cast转换符适用于底层编程技术,是不可移植的,因为不同系统可能使用不同大小,不同顺序来存储同一类型的数据。

总之,C语言类型转换比reinterpret_cast还要"疯狂",非常不安全,所以推荐使用cast运算符来实现类型转换


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 : https://blog.csdn.net/m0_71009069/article/details/126392622
相关文章
  • C++中类的六大默认成员函数的介绍

    C++中类的六大默认成员函数的介绍
    一、类的默认成员函数 二、构造函数Date(形参列表) 构造函数主要完成初始化对象,相当于C语言阶段写的Init函数。 默认构造函数:无参的构
  • C/C++实现遍历文件夹最全方法总结介绍

    C/C++实现遍历文件夹最全方法总结介绍
    一、filesystem(推荐) 在c++17中,引入了文件系统,使用起来非常方便 在VS中,可以直接在项目属性中调整: 只要是C++17即以上都可 然后头文件
  • C语言实现手写Map(数组+链表+红黑树)的代码

    C语言实现手写Map(数组+链表+红黑树)的代码
    要求 需要准备数组集合(List) 数据结构 需要准备单向链表(Linked) 数据结构 需要准备红黑树(Rbtree)数据结构 需要准备红黑树和链表适配策略
  • MySQL系列教程之使用C语言来连接数据库

    MySQL系列教程之使用C语言来连接数据库
    写在前面 知道了 Java中使用 JDBC编程 来连接数据库了,但是使用 C语言 来连接数据库却总是连接不上去~ 立即安排一波使用 C语言连接 MySQL数
  • 基于C语言实现简单学生成绩管理系统

    基于C语言实现简单学生成绩管理系统
    一、系统主要功能 1、密码登录 2、输入数据 3、查询成绩 4、修改成绩 5、输出所有学生成绩 6、退出系统 二、代码实现 1 2 3 4 5 6 7 8 9 10 11
  • C语言实现共享单车管理系统

    C语言实现共享单车管理系统
    1.功能模块图; 2.各个模块详细的功能描述。 1.登陆:登陆分为用户登陆,管理员登陆以及维修员登录,登陆后不同的用户所执行的操作
  • C++继承与菱形继承的介绍

    C++继承与菱形继承的介绍
    继承的概念和定义 继承机制是面向对象程序设计的一种实现代码复用的重要手段,它允许程序员在保持原有类特性的基础上进行拓展,增加
  • C/C++指针介绍与使用介绍

    C/C++指针介绍与使用介绍
    什么是指针 C/C++语言拥有在程序运行时获得变量的地址和操作地址的能力,这种用来操作地址的特殊类型变量被称作指针。 翻译翻译什么
  • C++进程的创建和进程ID标识介绍
    进程的ID 进程的ID,可称为PID。它是进程的唯一标识,类似于我们的身份证号是唯一标识,因为名字可能会和其他人相同,生日可能会与其他
  • C++分析如何用虚析构与纯虚析构处理内存泄漏

    C++分析如何用虚析构与纯虚析构处理内存泄漏
    一、问题引入 使用多态时,如果有一些子类的成员开辟在堆区,那么在父类执行完毕释放后,没有办法去释放子类的内存,这样会导致内存
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计