掘根宝典之C++RTTI和类型转换运算符

什么是RTTI

RTTI是运行阶段类型识别的简称。

哪些是RTTI?

C++有3个支持RTTI的元素。

1.dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,否则该运算符返回0——空指针。

2.typeid运算符返回一个指出对象类型的信息

3.type_info结构存储了特定类型的信息

注意:只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次,才应该将派生类对象的地址赋给基类指针。

警告:RTTI只适用于包含虚函数的类。

dynamic_cast运算符

在C++中,dynamic_cast是一种运行时类型检查和转换的操作符,用于在继承体系中进行安全的向下转型。它的语法如下:

dynamic_cast<目标类型>(表达式)

其中,目标类型是你想要将表达式转换为的类型,表达式是需要转换的对象。

dynamic_cast的使用有以下几个要点:

  1. dynamic_cast只能在具有多态性(即包含虚函数的类)的类之间进行类型转换。(is-a关系里可用)

  2. dynamic_cast会在运行时检查目标类型是否与表达式的类型兼容,如果不兼容则返回nullptr(对于指针类型)或抛出std::bad_cast异常(对于引用类型)。因此,在使用dynamic_cast时应该先判断转换结果是否为nullptr(空指针)或捕获异常。

  3. dynamic_cast只能用于指针或引用类型的转换。对于指针类型,如果转换失败,则返回nullptr;对于引用类型,如果转换失败,则抛出std::bad_cast异常。

下面是一个使用dynamic_cast的示例:

#include<iostream>
using namespace std;
class Base {
public:virtual void foo() {cout << "Base::foo()" << endl;}
};class Derived : public Base {
public:void foo() {cout << "Derived::foo()" << endl;}
};int main() {//用基类指针创建派生类对象Base* basePtr = new Derived();//将基类指针强制转换为派生类对象Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);//判断是否转换成功if (derivedPtr!=nullptr) {derivedPtr->foo();  // 输出 "Derived::foo()"}else {cout << "dynamic_cast failed!" << endl;}delete basePtr;  // 记得释放内存return 0;
}

运行结果

Derived::foo()

 在上面的示例中,由于Derived是Base的派生类,我们可以将Derived类型的对象的地址赋给Base指针。然后,通过使用dynamic_cast将Base指针转换为Derived指针,我们可以调用Derived类的成员函数。

我们看个反面例子

#include<iostream>
using namespace std;
class Base {
public:virtual void foo() {cout << "Base::foo()" << endl;}
};class Derived : public Base {
public:void foo() {cout << "Derived::foo()" << endl;}
};int main() {//用基类指针创建基类对象Base* basePtr = new Base();//将基类指针强制转换为派生类指针Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);//判断是否转换成功if (derivedPtr!=nullptr) {derivedPtr->foo();  // 输出 "Derived::foo()"}else {cout << "dynamic_cast failed!" << endl;}delete basePtr;  // 记得释放内存return 0;
}

 运行结果

dynamic_cast failed

 在上面的示例中,由于Derived是Base的派生类,发现不能安全的将指向基类地址的指针赋给派生类指针,所以derivedPtr被dynamic_cast赋予了空指针。

需要注意的是,如果Base类中没有声明为虚函数的成员函数,那么dynamic_cast无法进行类型转换。在这种情况下,可以考虑使用static_cast(我们等会会讲)进行类型转换,但这样做是不安全的,因为static_cast不会进行运行时类型检查。

typeid运算符和type_info类

在C++中,typeid运算符用于获取表达式的类型信息。它的语法如下:

typeid(表达式)

其中,表达式可以是任意类型的对象、类名、变量或者表达式。

注意包含头文件<typeinfo>

typeid运算符的使用有以下几个要点:

  1. typeid运算符返回一个std::type_info对象,它包含了类型的信息,可以用于比较两个类型是否相同。

  2. typeid运算符可以用于动态类型识别,即在运行时判断对象的实际类型。

  3. typeid运算符只能用于具有多态性(即包含虚函数的类)的类。

  4. typeid运算符对指针和引用类型的表达式进行操作时,返回的是指针或引用所指向的对象的类型信息。

下面是一个使用typeid的示例:

#include <typeinfo>
#include <iostream>class Base {
public:virtual void foo() {}
};class Derived : public Base {};int main() {Base* basePtr = new Derived();std::cout << typeid(*basePtr).name() << std::endl;  // 输出 "class Derived"Base& baseRef = *basePtr;std::cout << typeid(baseRef).name() << std::endl;  // 输出 "class Derived"delete basePtr;  // 记得释放内存return 0;
}

运行结果

class Derived
class Derived

在上面的示例中,我们创建了一个Base指针指向Derived对象,并通过typeid运算符获取了该对象的类型信息。可以看到,typeid运算符返回的是一个std::type_info对象,使用其name()方法可以获取类型的名称。

需要注意的是,typeid运算符返回的类型名称可能是编译器特定的名称,所以并不是在所有编译器中都能得到相同的结果。此外,typeid运算符还可以用于比较两个类型是否相同(使用==运算符进行比较),以及在异常处理中判断对象的类型。

typeid(a)==typeid(b)

判断a,b的类型是否相同

类型转换运算符

我们先看看之前的强制转换

int a=(int)3.0;
char b=char(888.0);

这些强制转换真的安全吗?

肯定不安全啊

为此,C++引入了4个类型转换运算符:dynamic_cast,const_cast,static_cast,reinterpret_cast

dynamic_cast

我们在上面已经讲了这个的用法,这个运算符的用途是能使得在类结构层次中进行向上转换(只能在is-a关系),而不允许其他转换

什么?你不知道is-a关系?看这里http://t.csdnimg.cn/JUgEI

const_cast

const_cast能将常量对象转换为非常量对象。

const_cast的语法如下:

const_cast<type>(expression)

其中,type表示要转换的类型,expression表示要转换的表达式。

使用const_cast需要注意以下几点:

性质一

在C++中,const_cast只能改变运算对象的底层const

情况1

如果expression是个常量,则使用const_cast来企图修改这个常量,这会产生未定义的后果

	const int num = 10;int* ptr = const_cast<int*>(&num);  // 将const int*转换为int**ptr = 20;  // 企图修改num的值cout << num << endl;

我们去运行一下就会发现

结果不是20!!因为企图修改常量,这是未定义的后果

我们再看一个例子

	const char* pc;char* p = const_cast<char*>(pc);char a = 'A';p = &a;cout << *pc << endl;

我们拿去运行,结果居然是

显然我们并没有成功修改常量

情况2

如果expression是个变量,则使用const_cast来获取修改权限的行为是允许的 

	int a = 1;int* ptr = &a;const int* const_ptr = ptr;int* non_const_ptr = const_cast<int*>(const_ptr);  // 将const int*转换为int**non_const_ptr = 30;  // 修改了a的值cout << *ptr << endl;

 

显然我们修改成功了

性质二

只有const_cast能改变表达式的常量属性,使用其他的命名强制类型转变表达式的常量属性都将引发编译器错误。同样的,也不能使用const_cast改变表达式的类型

const char* cp;
char* q = static_cast<char*>(cp);
//这是不允许的,只有const_cast能改变表达式的常量属性
string a=const_cast<string>(cp);
//这是不允许的,const_cast只改变表达式的类型

static_cast

这个可能是我们最常用的类型转换运算符

在C++中,static_cast类型转换的运算符。任何具有明确定义的类型转化,只要不包含底层const,都可以使用static_cast将一个类型转换为另一个相关的类型

如将整数类型转换为浮点类型,指针类型与整数类型之间的转换等

static_cast的语法如下:

static_cast<type>(expression)

其中,type表示要转换的目标类型,expression表示要转换的表达式。

使用

我们先看个例子

当需要把一个较大的算术类型赋值给较小的类型时,我们先来看看普通的做法

	double a = 9.00;int b = a;cout << b << endl;

这样子运行起来确实没什么问题

但编译器会提醒

 

我们可以通过static_cast来实现

    double a = 9.00;int b = static_cast<int>(a);cout << b << endl;

我们发现编译器不报错了 

这是因为,此时,强制类型转换告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。一般来说,如果编译器发现一个较大的算术类型试图赋值给较小的类型,就会给出警告信息;但是当我们执行了显式的类型转换后,警告信息就会被关闭了。


static_cast对于编译器无法自动执行的类型转换也非常有用。

例如,我们可以使用static cast 找回存在于void*指针中的值:
 

double d=9.0;
void* p= &d; // 正确:任何非常量对象的地址都能存入void* d为doable类
//正确:将void*转换回初始的指针类型
double *dp = static_cast<double*>(p);


当我们把指针存放在void*中,并且使用static_cast 将其强制转换回原来的类型时,应该确保指针的值保持不变。也就是说,强制转换的结果将与原始的地址值相等,因此我们必须确保转换后所得的类型就是指针所指的类型。类型一旦不符,将产生未定义的后果。

double d=9.0;
void* p= &d; 
int *dp = static_cast<int*>(p);
//这的结果是未定义的

下面是一些使用static_cast的例子:

int num = 10;
double d = static_cast<double>(num);  // 将int转换为doubleint a = 100;
void* void_ptr = static_cast<void*>(&a);  // 将int*转换为void*class Base {};
class Derived : public Base {};
Base* base_ptr = new Derived();
Derived* derived_ptr = static_cast<Derived*>(base_ptr);  // 将Base*转换为Derived*

在上面的例子中,我们分别使用了static_cast将整数类型、指针类型和引用类型进行了转换。注意最后一个例子中的指针类型转换,它只能在具有继承关系的类型之间进行转换,并且只能在相互兼容的类型之间进行转换。

reinterpret_cast

这个可能是最不常使用的类型转换运算符

reinterpret_cast可以执行任意类型之间的转换,即使类型之间没有任何关系

reinterpret_cast的语法如下:

reinterpret_cast<type>(expression)

其中,type表示要转换的目标类型,expression表示要转换的表达式。

使用reinterpret_cast需要注意以下几点:

  1. reinterpret_cast可以执行任意类型之间的转换,即使类型之间没有任何关系。

  2. reinterpret_cast不进行类型检查或安全检查,因此在使用时需要非常小心。

  3. reinterpret_cast通常用于需要底层二进制表示或将指针转换为整数类型的场景。

危险示例 

我们看个例子

int a=10;
int*b=&a;
char*c=reinterpret_cast<char*>(b);
string str(c);

使用reinterpret_cast是非常危险的,用c初始化str的例子很好地证明了这一点。

其中的关键问题是类型改变了,但编译器没有给出任何警告或者错误的提示信息。

当我们用一个int的地址初始化c时,由于显式地声称这种转换合法,所以编译器不会发出任何警告或错误信息。

接下来再使用c时就会认定它的值是char*类型,编译器没法知道它实际存放的是指向int的指针。

最终的结果就是,在上面的例子中虽然用c初始化str 没什么实际意义,甚至还可能引发更糟糕的后果,但仅从语法上而言这种操作无可指摘。

查找这类问题的原因非常困难,如果将b强制转换成c的语句和用c初始化string对象的语句分属不同文件就更是如此。 

下面是一些使用reinterpret_cast的例子:

int num = 42;
char* char_ptr = reinterpret_cast<char*>(&num);  // 将int*转换为char*int* num_ptr = reinterpret_cast<int*>(char_ptr);  // 将char*转换为int*int i = 10;
int* iptr = reinterpret_cast<int*>(&i);  // 将int转换为int*class A {};
class B {};
A* a_ptr = new A();
B* b_ptr = reinterpret_cast<B*>(a_ptr);  // 将A*转换为B*

上面的例子中,我们分别使用了reinterpret_cast将不同类型的指针进行了转换。注意在第一个例子中,我们将int指针转换为char指针,并且reinterpret_cast没有进行任何类型检查,因此需要非常小心使用。

旧式的强制类型转换

在早期版本的C++语言中,显式地进行强制类型转换包含两种形式:

type (expr); // 函数形式的强制类型转换
(type) expr; // C语言风格的强制类型转换

建议:避免强制类型转换

强制类型转换干扰了正常的类型检查,因此我们强烈建议程序员避免使用强制类型转换。

这个建议对于reinterpret_cast尤其适用,因为此类类型转换总是充满了风险。

在有重载函数的上下文中使用const_cast无可厚非;但是在其他情况下使用 const_cast也就意味着程序存在某种设计缺陷。

其他强制类型转换,比如 static_cast和dynamic_cast,都不应该频繁使用。每次书写了一条强制类型转换语句,都应该反复斟酌能否以其他方式实现相同的目标。就算实在无法避免,也应该尽量限制类型转换制的作用域,并且记录对相关类型的所有假定,这样可以减少错误发生的机会。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/754151.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

后端配置拦截器的一个问题【问题】

后端配置拦截器的一个问题【问题】 前言版权后端配置拦截器的一个问题问题解决 最后 前言 2024-3-14 00:07:28 以下内容源自《【问题】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://jsss-1.blog…

el-form 的表单校验,如何验证某一项或者多项;validateField 的使用

通常对form表单的校验都是整体校验&#xff1a; this.$refs.form.validate( valid > {if (valid) {// 校验通过&#xff0c;业务逻辑代码...} }); 如果需要对表单里的特定一项或几项进行校验&#xff0c;应该如何实现&#xff1f; 业务场景&#xff1a;下图点探测按钮时…

Python 井字棋游戏

井字棋是一种在3 * 3格子上进行的连珠游戏&#xff0c;又称井字游戏。井字棋的游戏有两名玩家&#xff0c;其中一个玩家画圈&#xff0c;另一个玩家画叉&#xff0c;轮流在3 * 3格子上画上自己的符号&#xff0c;最先在横向、纵向、或斜线方向连成一条线的人为胜利方。如图1所示…

【数据可信流通,从运维信任到技术信任】

1. 数据可信流通体系 信任的基石&#xff1a; 身份的可确认利益可依赖能力有预期行为有后果 2.内循环——>外循环 内循环&#xff1a;数据持有方在自己的运维安全域内队自己的数据使用和安全拥有全责。 外循环&#xff1a;数据要素在离开持有方安全域后&#xff0c;持有方…

蓝桥杯刷题(六)

[蓝桥杯 2022 省 A] 求和 题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示代码题目描述输入格式输出格式样例 #1样例输入 #1样例输出 #1 提示代码 题目描述 给定 n n n 个整数 a 1 , a 2 , ⋯ , a n a_{1}, a_{2}, \cdots, a_{n} a1​,a2​,⋯,an​, 求它们两两…

Linux内存管理笔记----TLB

1. TLB介绍 TLB是Translation Lookaside Buffer的简称&#xff0c;可翻译为“地址转换后援缓冲器”&#xff0c;也可简称为“快表”。 简单地说&#xff0c;TLB就是页表的Cache&#xff0c;属于MMU的一部分&#xff0c;其中存储了当前最可能被访问到的页表项&#xff0c;其内…

Java 文件处理完全指南:创建、读取、写入和删除文件详细解析

Java 文件操作 文件处理简介 文件处理是任何应用程序的重要部分。Java 提供了许多用于创建、读取、更新和删除文件的方法。 Java 文件处理 Java 中的文件处理主要通过 java.io 包中的 File 类完成。该类允许我们处理文件&#xff0c;包括创建、读取、写入和删除文件。 创建…

总是很困怎么办

总是感到困倦可能是由多种原因导致的&#xff0c;如睡眠不足、营养不足、健康问题或压力过大等。以下是一些建议&#xff0c;帮助你缓解困倦感&#xff1a; 保证充足睡眠&#xff1a; 确保每晚获得足够的睡眠时间&#xff0c;成年人通常需要7至9小时的睡眠。建立一个规律的睡眠…

Answer.AI开启家用70亿参数模型训练新篇章

Answer.AI开启家用70亿参数模型训练新篇章 摘要 2024年3月6日&#xff0c;Answer.AI推出首个项目&#xff1a;一套开源系统&#xff0c;首次实现在家用台式机上&#xff0c;仅需两块标准游戏GPU&#xff08;RTX 3090或4090&#xff09;&#xff0c;高效训练70亿大型语言模型。…

[leetcode ~dp ]279. 完全平方数

给你一个整数 n &#xff0c;返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数&#xff0c;其值等于另一个整数的平方&#xff1b;换句话说&#xff0c;其值等于一个整数自乘的积。例如&#xff0c;1、4、9 和 16 都是完全平方数&#xff0c;而 3 和 11 不是。 …

2023年度VSCode主题推荐(个人常用主题存档)

前言 早在2018年的时候发了一篇关于VSCode主题风格推荐——VS Code 主题风格设置&#xff0c;时过境迁&#xff0c;如今常用的主题皮肤早已更替。 今天下午在整理VSCode插件的时候&#xff0c;不小心把常用的那款&#xff08;亮色&#xff09;主题插件给删除了&#xff0c;无…

202444读书笔记|《作家榜名著:菜根谭》——大聪明的人,小事必朦胧,大懵懂的人,小事必伺察

202444读书笔记|《作家榜名著&#xff1a;菜根谭》——大聪明的人&#xff0c;小事必朦胧&#xff0c;大懵懂的人&#xff0c;小事必伺察 修省篇&#xff08;三八则&#xff09;应酬篇&#xff08;五一则&#xff09;评议篇&#xff08;四八则&#xff09;闲适篇&#xff08;四…

添可、希亦、追觅洗地机好不好用?实物终极PK测评分享!

随着人们对家庭生活品质的追求不断地提高&#xff0c;对智能清洁家电的需求也不断地上涨。作为在智能清洁家电领域的资深研究员&#xff0c;结合我八年的使用经验以及专业的眼光&#xff0c;今天就给大家一起来聊聊希亦、追觅、添可洗地机好不好用&#xff0c;以及三款洗地机产…

科技成果鉴定测试如何进行?第三方检测机构进行鉴定测试的好处

科技成果鉴定测试&#xff0c;作为科技领域中一项重要的质量检验手段&#xff0c;具有广泛的应用范围。旨在为科技成果的研发者和使用者提供客观、科学、权威的鉴定结果&#xff0c;从而评估科技成果的技术水平和市场竞争力。   科技成果鉴定测试是对科技成果进行系统、全面的…

Java项目:62 基于ssm的校园驿站管理系统+jsp

作者主页&#xff1a;源码空间codegym 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 管理员管理快递仓库信息&#xff0c;管理待发货信息&#xff0c;管理已收快递&#xff0c;管理物流以及留言信息&#xff0c;管理员工和用户资…

2.7 ROC曲线相比P-R曲线有什么特点?

2.7 ROC曲线相比P-R曲线有什么特点&#xff1f; 前情提要&#xff1a; P-R曲线详见&#xff1a;2.2 什么是精确率&#xff08;Precision&#xff09;与召回率&#xff08;Recall&#xff09;&#xff1f;二者如何权衡&#xff1f;&#xff09; 2.4 ROC曲线是什么&#xff1f; 2…

小鹏MONA将至:10 - 15万级,用性价比打新势力,用智驾打比亚迪

‍ 作者 |老缅 编辑 |德新 小鹏的全新品牌即将发布&#xff0c;10-15万级也能有高等级智能驾驶。 3月16日在中国电动汽车百人会论坛2024上&#xff0c;小鹏汽车董事长、CEO何小鹏提出&#xff1a;“下一个十年将是智能化的十年。未来18个月内高阶智驾的拐点将到来”。 所谓…

Excel之数据透视表

数据透视&#xff1a;逻辑理解与制作步骤 一、创建数据透视表 1、创建数据透视表&#xff1a;每列必须有表头 &#xff08;1&#xff09;选择要创建数据透视表的数据------插入----选择数据透视表 &#xff08;2&#xff09;选择现有工作表然后点击目标表选择合适的位置插入…

第1关:在字符串相邻字符间再插入一个字符

任务描述 本关任务&#xff1a;编写一个能在字符串相邻字符间再插入一个字符的小程序。 编程要求 用户输入一个字符a&#xff0c;再输入一个字符串s&#xff0c;在字符串s相邻的每两个字符之间插入一个字符a输出 根据提示&#xff0c;在右侧编辑器补充代码。 测试说明 平台…

面向对象深度解析---webpack 项目搭建

安装依赖 "devDependencies": {"babel-core": "^6.26.3","babel-loader": "^8.0.6","babel-preset-env": "^1.7.0","html-webpack-plugin": "^3.2.0","webpack": "…