【C++】类型转换

【C++】类型转换

目录

C语言中的类型转换

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

classA{public:A()=default;};classB{ A _a;public:B(const A& a):_a(a){}};intmain(){int a =1;double b = a;float c = a;int* d =(int*)a;// 没法隐式转换,但是两者之间还是有点关系,所以可以强转 std::string str;// std::vector arr = str; 如果两者之间没有一点关系,那么就不能转换 A ca; B ca = ca;// 但是如果构成隐式类型转换,则可以转换return0;}

其实我们也能明白C语言的这种转换方式可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换。尤其是隐式类型转换,有时不注意还会引发错误,比如

int pos =0; size_t sz =0;while(pos >= sz)// 死循环,pos整形提升成size_t,此时没有负数{ std::cout <<"hello world"<< std::endl;--pos;}

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

C++强制类型转换

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

static_cast

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

int a =1;double b =static_cast<double>(a);

reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。比如int转指针,指针转int,不同类型指针转换,不同引用转换。reinterpret_cast不改变原有内存,只是改变内存数据的解释方式,比如我们可以这样

float a =3.1415926;int b =reinterpret_cast<int&>(a);// 打印结果: 1078530010int c =static_cast<int>(a);// 打印结果: 3 std::cout << b << std::endl; std::cout << c << std::endl;

这里我们使用引用转换将float 类型转换成int&,注意这里不能写int,因为reinterpret_cast不能进行值类型之间的直接转换,这是static_cast干的事,我们转换成int&,意思就是float底层存在内存中的数据没有改,只是将这段数据解释成了int,这里我们也能用int&来接收,这样就少一次拷贝。我们也能看到打印结果不会像static_cast那样,因为float的存储原理和int不一样。

const_cast

我们先来看下面这段代码,

constint a =1;int* b =(int*)&a;(*b)++; std::cout << a << std::endl;// 打印结果: 1 std::cout <<*b << std::endl;// 打印结果: 2

这是什么原因呢?a的类型是const int,是常变量,有存储空间,但是因为const不允许修改,所以编译器优化,将其视为常量,转成汇编之后直接就是常量数字,优化进了指令或者寄存器中了,所以当其数值被修改时内存中的数值已经被改了,但是打印时却无法显示,

 std::cout << a << std::endl;// 打印结果: 100007FF6AB9B196B mov edx,100007FF6AB9B1970 mov rcx,qword ptr [__imp_std::cout(07FF6AB9C1188h)]00007FF6AB9B1977 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char>>::operator<<(07FF6AB9C1190h)]00007FF6AB9B197D mov qword ptr [rbp+0F8h],rax 00007FF6AB9B1984 lea rdx,[std::endl<char,std::char_traits<char>>(07FF6AB9B1442h)]00007FF6AB9B198B mov rcx,qword ptr [rbp+0F8h]00007FF6AB9B1992 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char>>::operator<<(07FF6AB9C1118h)]00007FF6AB9B1998 nop 

可以看到移到寄存器的操作直接用的常量数字,所以这时出现了这样诡异的现象。想要避免的话也很简单,我们使用volatile关键字,

volatileconstint a =1;int* b =(int*)&a;(*b)++; std::cout << a << std::endl;// 打印结果: 2 std::cout <<*b << std::endl;// 打印结果: 2
 std::cout << a << std::endl;// 打印结果: 200007FF68B9B196B mov edx,dword ptr [a]00007FF68B9B196E mov rcx,qword ptr [__imp_std::cout(07FF68B9C1188h)]00007FF68B9B1975 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char>>::operator<<(07FF68B9C1190h)]00007FF68B9B197B mov qword ptr [rbp+0F8h],rax 00007FF68B9B1982 lea rdx,[std::endl<char,std::char_traits<char>>(07FF68B9B1442h)]00007FF68B9B1989 mov rcx,qword ptr [rbp+0F8h]00007FF68B9B1990 call qword ptr [__imp_std::basic_ostream<char,std::char_traits<char>>::operator<<(07FF68B9C1118h)]00007FF68B9B1996 nop 

它的作用就是让编译器编译时不要优化,每次都去内存中取数据。

我们也能发现,这种const类型指针转非const类型指针的操作虽然合法,但是是比较危险的,所以我们使用reinterpret_cast是无法对这种情况进行转换的,如果我们想要转换,我们就要使用const_cast。C++这么做的本意就是当我们要转换时,使用const_cast也算是一种提醒,告诉我们这样的操作是危险的,做好准备,比如加上volatile关键字。

volatileconstint a =1;//int* b = reinterpret_cast<int*>(&a);int* b =const_cast<int*>(&a);(*b)++; std::cout << a << std::endl;// 打印结果: 2 std::cout <<*b << std::endl;// 打印结果: 2

dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)。
向上转型:子类对象指针/引用->父类指针/引用(不需要转换,赋值兼容规则)。
向下转型:父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的)。
注意:
(1)dynamic_cast只能用于父类含有虚函数的类。
(2)dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。
我们在对父子类指针 / 引用对象的进行转换时,可以使用dynamic_cast、或者static_cast,也能使用reinterpret_cast(可以但是不推荐,可能出问题),但是更提倡使用dynamic_cast。在进行向上转换时,其实由于继承的切片规则,怎么写都行,static_castreinterpret_cast,甚至直接赋值也行。但是如果是向下转换的话,可能就会有问题,因为上述的几种方法都不能很好的分辨这个父类指针 / 引用到底指向的父类还是子类,如果是父类,那就出错了,可能会有越界访问的情况,如果是子类就没事。这时我们使用dynamic_cast,它可以检查这个指针 / 引用指向的到底是父类还是子类,如果是父类,那么转换的是指针的话就会返回nullptr,如果是引用就会抛出异常,这样我们就能防止不当转换的错误发生。

RTTI

RTTI:Run-time Type identification的简称,即:运行时类型识别。
C++通过以下方式来支持RTTI:
(1)typeid运算符
(2)dynamic_cast运算符
(3)decltype
我们上面的dynamic_cast是怎么实现RTTI的呢?这其实就和它规定必须的是有虚函数才能进行转换有关系,我我们都知道,父类定义虚函数实现多态那么就会在存储时生成一张虚函数表,我们可以理解为这个虚函数标准额外有一个标记,标注了其是父类还是子类,这样我们在使用dynamic_cast进行转换时,就会进行运行时类型检查,看这个标记位,这样就能知道到底是父类还是子类了。

Read more

在 Ubuntu 上安装 Docker 的完整指南(使用国内镜像源)

在 Ubuntu 上安装 Docker 的完整指南(使用国内镜像源)

概述 Docker 是一个开源的应用容器引擎,允许开发者将应用及其依赖打包到一个可移植的容器中,从而实现快速部署和跨平台运行。本指南将详细介绍在 Ubuntu 系统上安装 Docker 的完整步骤,特别使用了清华大学镜像源以加速国内用户的下载过程。 准备工作 在开始安装前,请确保: * 使用的是 Ubuntu 系统(本教程适用于 Ubuntu 16.04 及以上版本) * 拥有 sudo 权限或 root 用户权限 * 能够访问互联网 完整安装步骤 1. 卸载旧版本(如有) 为避免版本冲突,建议先卸载系统中可能存在的旧版本 Docker: sudoapt-get remove docker docker-engine docker.io containerd runc 注意:如果这是首次安装 Docker,系统中可能没有这些旧版本,但执行此命令可以确保环境的清洁。 2. 设置

By Ne0inhk
Flutter for OpenHarmony: Flutter 三方库 fake_async 掌控时间的魔法,让鸿蒙异步单测快如闪电(单元测试加速神器)

Flutter for OpenHarmony: Flutter 三方库 fake_async 掌控时间的魔法,让鸿蒙异步单测快如闪电(单元测试加速神器)

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net 前言 在 OpenHarmony 应用的单元测试中,异步逻辑是一个避不开的难点。如果你的代码中有 Future.delayed(Duration(minutes: 5)),难道你在跑测试时真的要等上 5 分钟吗?或者如果你在测试一个复杂的动画状态流转,如何精确地模拟时间流逝了 125 毫秒? fake_async 是 Dart 测试工具链中的“时间胶囊”。它能在一个受控的环境中虚拟化时钟。你可以瞬间“拨快”时间,让那些原本需要漫长等待的异步操作立即执行,从而让你的鸿蒙单测运行速度提升千倍。 一、核心虚拟时间原理 它通过接管全局的 Zone,拦截了所有基于时间的调度任务。 elapse(5 mins) 测试用例 fakeAsync 闭包环境 挂起的延迟任务 (Future/Stream) 瞬间拨快虚拟时钟

By Ne0inhk
鸿蒙常见问题分析三:视频关键帧提取与智能体图像分析

鸿蒙常见问题分析三:视频关键帧提取与智能体图像分析

引言:视频封面生成的技术挑战 在HarmonyOS应用开发中,视频内容处理是一个常见但充满挑战的领域。特别是当需要从视频中自动提取最佳封面时,开发者常常面临以下痛点: 1. 性能瓶颈:长视频全帧分析计算成本高 2. 准确性不足:均匀抽帧容易错过关键画面 3. 智能化缺失:传统算法难以理解画面内容价值 4. 用户体验差:手动选择封面增加用户操作成本 本文将从实际开发角度,深入分析HarmonyOS视频关键帧提取与智能体图像分析的常见问题及解决方案。 常见问题一:HarmonyOS中如何高效提取视频帧? 问题描述 开发者在使用AVImageGenerator提取视频帧时,常遇到性能问题:提取速度慢、内存占用高、处理长视频时应用卡顿。 解决方案分析 1. 正确的AVImageGenerator使用方式 // 错误示例:频繁创建和销毁实例 async extractMultipleFramesWrong(timestamps: number[]) { const frames = []; for (const timestamp of timestamps) { /

By Ne0inhk
使用Linux命名管道(FIFO)实现无血缘关系进程间通信

使用Linux命名管道(FIFO)实现无血缘关系进程间通信

使用Linux命名管道实现无血缘关系进程间通信 * 1. 引言 * 2. 命名管道(FIFO)概述 * 3. 创建和使用命名管道 * 3.1 使用命令行创建命名管道 * 3.2 使用C语言创建命名管道 * 4. 实现无血缘关系进程间通信 * 4.1 写进程示例 * 4.2 读进程示例 * 4.3 运行示例 * 5. 命名管道的高级特性 * 5.1 非阻塞打开命名管道 * 5.2 使用select或poll监控命名管道 * 6. 命名管道的注意事项 * 7. 实际应用场景 * 8. 总结 1. 引言 在Linux系统中,进程间通信(IPC)是编程中常见的需求。传统上,有血缘关系的进程(如父子进程)可以通过管道进行通信,

By Ne0inhk