【C++】-- 类型转换

news2025/3/12 10:55:51

目录

前言

C语言中的类型转换

C++强制类型转换

static_cast(static静止的)

reinterpret_cast(reinterpret重新解释)

const_cast(const常量)

总结

dynamic_cast(dynamic动态)

RTTI

常见面试题


前言

        C++继承了不少C语言的知识,因为C++最开始就是从C语言出来的,于是也就导致了C++将C语言的,好的与不好的,都继承下来了。

        有一个东西就是从C语言沿袭过来的,但是其又是不够好的 —— 隐式类型的转换

C语言中的类型转换

        在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型转换和显式类型转换
  • 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转化:需要用户自己处理。
void Test ()
{
     int i = 1;
     // 隐式类型转换
     double d = i;
     printf("%d, %.2f\n" , i, d);

     int* p = &i;
     // 显示的强制类型转换
     int address = (int) p;
     printf("%x, %d\n" , p, address);
}

#问:什么情况下允许隐式类型转换?什么情况下允许强制类型转换?

        (C++继承的C语言的 —— C语言那就当然是针对于内置类型啦)

C语言是意义相近的类型允许隐式类型的准换,如:整形家族和浮点数家族。

  • 整形之间转换是:小的可以转大的提升,大的也可以转小的截断
  • 整形和浮点之间转换是:通过补位的方式,整形转浮点补浮点就行因为精度,浮点转整形丢掉精度

        可以说:它们都是用来表示数据的大小,只是空间的大小不一样,表示的范围不一样。还有就是浮点数的存储机制也不一样,其还能表示小数点后的精度。(本质:意义相近)

C语言中对于显示强制类型转换是意义不相近的类型,值转换后有意义

Note:

        整体来说,隐式类型还好,但是显示类型是有巨大的坑的

在日常中最简单的insert函数实现(数据移动)中,就有问题。

void Insert(size_t pos, char ch)
{
    size_t _size = 5;
    // ……
    int end = _size - 1;
    while(end >= pos)
    {
        _str[end + 1] = _str[_end];
        --end;
    }
}

Insert(3, 'A'); // 没有问题
Insert(0, 'A'); // 有问题

        在其中,第二个Insert就存在问题,因为在判断的时候就会发生隐式类型的转换。

         在这个判断的时候应该就是end为-1,pos为0然后,退出while循环,但是此处它会悄悄地进入执行,因为在一个操作符的两端的操作数也会发生隐式类型的转换。这个时候会悄悄的产生一个变量将end进行了提升(从无符号提升成有符号),然后就会导致数据的越界。

        所以,C语言留下的这个东西也是一个坑。并且没有办法因为为了兼容前面的内容,所以没有办法移除隐式转换,所以我们需要注意隐式转换的坑。

        于是,便有了C++的规范化。

#问:为什么C++需要四种类型转换?

C风格的转换格式很简单,但是有不少缺点的:
  1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失。
  2. 显式类型转换将所有情况混合在一起,代码不够清晰。
        因此C++提出了自己的类型转化风格。
Note:
        因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。

C++强制类型转换

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:

  • static_cast
  • reinterpret_cast
  • const_cast
  • dynamic_cast

        这个时候C语言的隐式类型转换的一套还是可以使用的,只不过一般编译器会报警告,不影响运送,但是会告诉你,如:浮点数转换为整形精度可能会丢失。

static_cast(static静止的

        static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换 - 意义相近的类型

int main()
{
    double d = 12.34;
    int a = static_cast<int>(d);
    std::cout << a << std::endl;
    return 0;
}

        不是相近的类型不可以运用其来转换。

int main()
{
	int* p = &a;

	// 不支持的
	int address = static_cast<int>(p);
	return 0;
}

        其是会报错为,如:"static_cast":无法从"int*"转换为"int",的错误(无效的)

reinterpret_cast(reinterpret重新解释

        reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型 - 意义不相关的类型

int main()
{
	double d = 12.34;
	int a = static_cast<int>(d);

	int* p = &a;
	// 不支持的
	//int address = static_cast<int>(p);
	int address = reinterpret_cast<int>(p);

	return 0;
}

         对于const修饰的变量,不能转化,因为C++的规范会进行检查。

int main()
{
	const int a = 2;
    // 不支持
	int* p = reinterpret_cast<int*>(&a)

	*p = 3;
	return 0;
}

补充:

        甚至有一些地方还可以支持一些比较bug的转化。因为其相当于重新解释了,转换成完全另外一个类型。如:其实是一个指针,转换成为了一个数据大小,或者是一个数据大小转换成为了指针。

const_cast(const常量

        const_cast最常用的用途就是删除变量的const属性,方便赋值。

int main()
{
	const int a = 2;
	int* p = const_cast<int*>(&a)

	*p = 3;
	return 0;
}

        一个奇葩的操作。

int main()
{
	const int a = 2;
	int* p = const_cast<int*>(&a);

	*p = 3;
	cout << a << endl;
	cout << *p << endl;
	return 0;
}

#问:上面的奇葩的操作的输出结果是什么?

简单的来看是不是以为是:3、3?那就错了。

        p是a的地址,那*p就是a,这么想上面没错(const对象虽然不能改,但是在C++里面const修饰的变量叫做常变量,是不能被直接的修改,但是可以被间接接的修改)

修改const变量:

        就像上述,就是改const的方法。这种方式很bug,我们取到const修饰的变量的地址,然后通过地址间接的强制去改。本质的原因就是C++的const修饰的变量,并没有存储到常量区当中去,const修饰的变量与其他的变量一样,都存在栈上的,所以使用指针的方式是可以被修改的。

其实运行结果是:2、3

        而且我回很奇特的发现,我们对于数据进行监视数据是:3、3,然而实际打印出的数据却是2,3

        这个的原因是由来于编译器的优化,编译器对于const类型的变量是有优化的。比如说有一些编译器的优化是会将数据放在寄存器的,因为对于编译器来说,这个const修饰的对象,是只会读而并不会进行修改的,于是为了提高运行的效率,于是直接将数存储到了寄存器当中,你要就取。所以虽然内存中的数据被进行了更改,但是在寄存器看来就是没变。

        有一些编译器的处理方式,不是放到寄存器,而是直接搞死,如同一个宏。不取直接就给初始化时的值。

        所以也就是为什么 const_cast 也可以作为毫不相关的转换(重新解释的转换)。 const_cast 单独形成一类,也就是为了告诉我们这个地方很危险。还有一个方法可以解决这个问题:volatile(关键字),就是为了告诉编译器这个地方不要进行优化,直接去内存中取这个值。

总结

  1. 兼容C隐式类型转换和强制类型转换。
  2. 期望我们需要使用C语言的,期望我们用规范的C++显示的强制类型转换。
  3. static_cast (隐式类型转换) 、reinterpret_cast、const_cast (强制类型转换)
        C++的一套更规范一些,使用它的一看就知道是相近类型还是没有关联类型的准换,看见static_cast就知道很危险要小心。

dynamic_cast(dynamic动态

        是C语言没有的,是C++自己增加的,适用于向下转化。dynamic_cast用于将一个父类对象的指针 / 引用转换为子类对象的指针或引用(动态转换)。

  • 向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则) ——  天然支持

        严格的来说:向上转型是不属于隐式类型转换,也不属于显示类型转换。它可以说是C++的一个特例,其不需要进行转换,它是赋值兼容的。

  • 向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)
注意:
  • dynamic_cast只能用于父类含有虚函数的类 —— 否者编译直接报错。
  • dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。 
        我们日常中所用的继承关系的转换就是 天然支持 向上转型的,只有在继承的向上转里面是支持的。
class A
{
public :
    virtual void f(){}
};

class B : public A
{};

int main ()
{
    B bb;
    A aa = bb;
    A& ra = bb;
    return 0;
}

        dynamic_cast是适用于向下转型,父转子(用dynamic_cast转型是安全的)。因为按道理父类对象是无论如何都不能转子类的,就是使用dynamic_cast也是不行的。

class A
{
public :
    virtual void f(){}
};

class B : public A
{};

int main ()
{
    A aa;
    // 父类对象无论如何都是不允许转换成子类对象的
    // B bb = dynamic_cast<B>(aa);
    // B bb = (B)aa;
    return 0;
}

        对象是怎么样都不允许转的,但是指针与引用是要允许转的。因为涉及到一个指针,是有可能指向父类,也有可能指向子类。而如果是父类指针指向是父类的对象,那么就会导致出现越界问题。而如果是父类指针指向是子类的对象,那就没有问题。于是C++提供了一个dynamic_cast,让我们的行为是安全的,即:

  1. 如果父类指针指向的是子类的对象,那么可以转换,转换表达式返回正确的地址。
  2. 如果父类指针指向的是父类的对象,那么不能转换,转换表达式返回nullptr。
class A
{
public :
    virtual void f(){}
};

class B : public A
{};

int main ()
{
	// 如果pa是指向子类,那么可以转换,转换表达式返回正确的地址
	// 如果pa是指向父类,那么不能转换,转换表达式返回nullptr
    
    B bb;
    A* pa = bb;
	B* pb = dynamic_cast<B*>(pa); // 安全的
	//B* pb = (B*)pa;             // 不安全

	if (pb)
	{
		cout << "转换成功" << endl;
		pb->_a++;
		pb->_b++;
		cout << pb->_a << ":" << pb->_b << endl;
	}
	else
	{
		cout << "转换失败" << endl;
		pa->_a++;
		cout << pa->_a << endl;
    }
}

总结:

        其中,向上转型就是所说的切割/切片,是语法天然支持的,不需要进行转换,而向下转型是语法不支持的,需要进行强制类型转换。

#include <iostream>

class A1
{
public:
	virtual void f(){}
public:
	int _a1 = 0;
};

class A2
{
public:
	virtual void f(){}
public:
	int _a2 = 0;
};

class B : public A1, public A2
{
public:
	int _b = 1;
};

int main()
{
	B bb;
	A1* ptr1 = &bb;
	A2* ptr2 = &bb;
    std::cout << ptr1 << std::endl;
    std::cout << ptr2 << std::endl << std::endl;

	B* pb1 = (B*)ptr1;
	B* pb2 = (B*)ptr2; // C语言的强制类型转换是回得到开头的
    std::cout << pb1 << std::endl;
    std::cout << pb2 << std::endl << std::endl;

	B* pb3 = dynamic_cast<B*>(ptr1);
	B* pb4 = dynamic_cast<B*>(ptr2);

    std::cout << pb3 << std::endl;
    std::cout << pb4 << std::endl << std::endl;

	return 0;
}

Note:
        强制类型转换关闭或挂起了正常的类型检查,每次使用强制类型转换前,程序员应该仔细考虑是否还有其他不同的方法达到同一目的,如果非强制类型转换不可,则应限制强制转换值的作用域,以减少发生错误的机会。强烈建议:避免使用强制类型转换。

RTTI

        RTTI (Run-time Type identifification的简称) ,即:运行时类型识别。
C++通过以下方式来支持RTTI:
  1. typeid运算符 —— 拿到变量的类型的字符串。
  2. dynamic_cast运算符 —— 父类的指针指向父类对象,还是子类对象,只能用于父类含有虚函数的类(多态)
  3. decltype —— 推导一个对象的类型,这个类型可以用来定义另一个对象。

常见面试题

#问:C++中的4种类型转换分别是:____ 、____ 、____ 、____。

        static_cast、reinterpret_cast、const_cast和dynamic_cast。

#问:说说4种类型转换的应用场景。

  • static_cast用于相近类型的类型之间的转换,编译器隐式执行的任何类型转换都可用static_cast。
  • reinterpret_cast用于两个不相关类型之间的转换。
  • const_cast用于删除变量的const属性,方便赋值。
  • dynamic_cast用于安全的将父类的指针(或引用)转换成子类的指针(或引用)。

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

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

相关文章

JavaWeb开发(三)3.5——Java的反射机制

一、反射机制的概念 指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法&#xff0c;对于任意一个对象&#xff0c;都能调用它的任意一个方法。这种动态获取信息&#xff0c;及动态调用对象方法的功能叫java语言的反射机制。 Java反射机制的核心是在程序运行时动…

Vue3电商项目实战-购物车模块4【11-购物车页面-确认框组件】

文章目录11-购物车页面-确认框组件11-购物车页面-确认框组件 目的&#xff1a;通过vue实例调用$confirm函数弹出确认框。import导入函数使用也需要支持。 大致步骤&#xff1a; 实现组件基础结构和样式。实现函数式调用组件方式和完成交互。加上打开时动画效果。给购物车删除加…

飞鹤奶粉营销杀手级动作,让对手郁闷

飞鹤奶粉杀手级动作&#xff0c;让对手绝望 不是“更适合中国宝宝体质”一句话的事 而是杀手级资源匹配&#xff1a; 飞鹤奶粉一年50万场线下活动 趣讲大白话&#xff1a;让别人无路可走 【安志强趣讲信息科技94期】 ********************************** 战略定位后&#xff0…

多重背包问题中的二进制状态压缩

1.多重背包问题 经典的多重背包问题和01背包问题的相似之处在于二者的一维遍历顺序都是从右侧往左侧遍历。 同时多重背包的一维写法不比二维写法降低时间复杂度。 2.多重背包标准写法:(平铺展开形式&#xff09; class Solution {public int maxValue(int N, int C, int[] s…

Raspbian镜像无头烧录

Raspbian镜像无头烧录1. 源由2. 需求3. 分析4. 步骤4.1 删除tf卡分区内容4.2 balena烧录镜像4.3 配置USB直接登录4.4 配置WiFi 2.4G网络登录4.5 修改登录账号密码4.6 数据同步和弹出tf卡5. 登录5.1 登录异常处理5.2 WiFi 2.4G网络登录5.3 USB直接登录6. 参考资料7. 补充资料这里…

HCIP-5OSPF域内域间外部路由学习笔记

1、OSPF区域 每个区域都维护一个独立的LSDB。 Area 0是骨干区域&#xff0c;其他区域都必须与此区域相连。 划分OSPF区域可以缩小路由器的LSDB规模&#xff0c;减少网络流量。 区域内的详细拓扑信息不向其他区域发送&#xff0c;区域间传递的是抽象的路由信息&#xff0c;而不…

【Flutter·学习实践·UI篇】基础且重要的UI知识

前言 参考学习官网&#xff1a;《Flutter实战第二版》 学习前先记住&#xff1a;Flutter 中万物皆为Widget&#xff0c;心中默念3次以上铭记于心。 这一点和开发语言Dart的变量一切皆是对象的概念&#xff0c;相互对应。 Widget 在前面的介绍中&#xff0c;我们知道在Flutt…

CSAPP第八章 异常控制流

目录 异常 异常处理 异常的类别 中断 陷阱和系统调用 故障 终止 ​编辑 Linux/x86-64 系统中的异常 进程 ​编辑 逻辑控制流 并发流 私有地址空间 用户模式和内核模式 上下文切换 ​编辑系统调用错误处理 进程控制 获取进程ID 创建和终止进程 回收子进程 …

汇编系列03-不借助操作系统输出Hello World

每天进步一点点&#xff0c;加油&#xff01; 上一节&#xff0c;我们通过汇编指令&#xff0c;借助操作系统的系统调用实现了向标准输出打印Hello world。这一节我们打算绕过操作系统&#xff0c;直接在显示屏幕上打印Hello world。 计算机的启动过程 当我们给计算机加电启…

AcWing1049.大盗阿福题解

前言如果想看状态机的详解&#xff0c;点机这里:dp模型——状态机模型C详解1049. 大盗阿福阿福是一名经验丰富的大盗。趁着月黑风高&#xff0c;阿福打算今晚洗劫一条街上的店铺。这条街上一共有 N家店铺&#xff0c;每家店中都有一些现金。阿福事先调查得知&#xff0c;只有当…

《算法分析与设计》笔记总结

《算法分析与设计》笔记总结第一章 算法引论1.1 算法与程序1.2 表达算法的抽象机制1.3 描述算法1.4 算法复杂性分析第二章 递归与分治策略2.1 递归的概念2.2 分治法的基本思想2.3 二分搜索技术2.4 大整数乘法2.5 Strassen矩阵乘法2.7 合并排序2.8 快速排序2.9 线性时间选择2.10…

深度学习算法训练和部署流程介绍--让初学者一篇文章彻底理解算法训练和部署流程

目录 1 什么是深度学习算法 2 算法训练 2.1 训练的原理 2.2 名词解释 3 算法C部署 3.1 嵌入式终端板子部署 3.3.1 tpu npu推理 3.3.2 cpu推理 3.2 服务器部署 3.2.1 智能推理 3.2.2 CPU推理 1 什么是深度学习算法 这里不去写复杂的概念&#xff0c;就用通俗的话说…

无头盔PICO-unity开发日记1(抓取、传送)

目录 可传送的地面 锚点传送 修改射线颜色&#xff08;可交互/不可交互&#xff09; 球、抓手组件 ||刚体&#xff08;重力&#xff09;组件 可传送的地面 1.地面添加组件 2.XR交互管理器添加传送提供者 3.地面设置传送提供者 4.XR交互管理器添加locomotion system 5.拖拽 完…

2020蓝桥杯真题日期格式 C语言/C++

问题描述 小蓝要处理非常多的数据, 其中有一些数据是日期。 在小蓝处理的日期中有两种常用的形式: 英文形式和数字形式。 英文形式采用每个月的英文的前三个宁母作为月份标识, 后面跟两位数字 表示日期, 月份标识第一个字母大写, 后两个字母小写, 日期小于 10 时要补 前导 0s…

汇编基础语法和指令总结+案例(用32位汇编实现插入排序)

目录 前提知识 案例 c的插入排序 32位汇编代码 代码分析 效果展示 前提知识 常用指令add指令 sub指令 mul乘法指令 div除法指令 inc&#xff08;自增&#xff09;&#xff08;即&#xff09; dec&#xff08;自减&#xff09;&#xff08;即--&#xff09; cmp&#xf…

二叉树的最近公共祖先【Java实现】

题目描述 现有一棵n个结点的二叉树&#xff08;结点编号为从0到n-1&#xff0c;根结点为0号结点&#xff09;&#xff0c;求两个指定编号结点的最近公共祖先。 注&#xff1a;二叉树上两个结点A、B的最近公共祖先是指&#xff1a;二叉树上存在的一个结点P&#xff0c;使得P既是…

4万字数字政府建设总体规划方案WORD

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用。部分资料内容&#xff1a; 我省“数字政府”架构 &#xff08;一&#xff09; 总体架构。 “数字政府”总体架构包括管理架构、业务架构、技术架构。其中&#xff0c;管理架构体现“管运分离”的建设运营模式…

面试必须要知道的常见排序算法

以下排序均为升序 1.直接插入排序 具体思想 把待排序的数据按大小比较插入到一个已经排序好的有序序列中,直到所有的待排序数据全部插入到有序序列中为止.实际生活中,我们平常斗地主摸牌时,就用到了插入排序的思想. 当插入第n个数据时,前面n-1个数据已经有序;第n个数据依次与前…

WebStorm安装教程【2023年最新版图解】一文教会你安装

文章目录引言一、下载WebStorm三、WebStorm激活配置及创建项目Active Code安装完成尝试新建一个项目引言 今天发现了一个专注前端开发的软件&#xff0c;相比VSCode的话&#xff0c;这个好像也不错&#xff0c;为了后续做个API接口项目做准备。 对于入门JavaScript 开发的者&am…

Linux操作系统学习(信号处理)

文章目录进程信号信号的产生方式&#xff08;信号产生前&#xff09;1. 硬件产生2.调用系统函数向进程发信号3.软件产生4.定位进程崩溃的代码&#xff08;进程异常退出产生信号&#xff09;信号保存的方式&#xff08;信号产生中&#xff09;获取pending表&&修改block表…