C++ 类型转换

news2024/11/17 20:31:27

目录

C语言中的类型转换

为什么C++需要四种类型转换

C++:命名的强制类型转换

static_cast

reinterpret_cast

const_cast

dynamic_cast


C语言中的类型转换

在C语言中,如果赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与 接收返回值类型不一致时,就需要发生类型转化,C语言中总共有两种形式的类型转换:隐式类型 转换和显式类型转换。

1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
2. 显式类型转化:需要用户自己处理

    int i = 10.2;   // 隐式类型转换,会警告
    int* p = (int*)i;  // C显式强制类型转换

缺陷: 转换的可视性比较差,所有的转换形式都是以一种相同形式书写,难以跟踪错误的转换

为什么C++需要四种类型转换

C风格的转换格式很简单,但是有不少缺点:

1. 隐式类型转化有些情况下可能会出问题:比如数据精度丢失,或其他难以预见的错误。
2. 显式类型转换将所有情况混合在一起,代码不够清晰。

因此C++提出了自己的类型转化方式,注意因为C++要兼容C语言,所以C++中还可以使用C语言的 转化方式。

回顾std::string的模拟实现,编译器隐式类型转换带来的错误:

        string& insert(size_t pos, char c)
        {
            assert(pos <= _size);
            if(_size == _capacity)
            {
                reserve(_capacity == 0?4:2*_capacity);
            }
            int end = _size;
            while(end >= pos)
//            while(end >= (int)pos)
            {
                _str[end+1] = _str[end];
                end--;
            }
            // 比较推荐的写法,主要是  int和size_t比较,会出现比较错误,当int小于0时。
//            size_t end = _size+1;
//            while(end > pos)
//            {
//                _str[end] = _str[end-1];
//                end--;
//            }
            _str[pos] = c;
            ++_size;
            return *this;
        }

如上,while循环的判断部分,运算符的两个运算数的类型不同,int与size_t,此时会发生隐式类型转换,int -> size_t,若pos == 0,则会发生死循环,故产生了while(end >= (int)pos) // 显式类型转换的写法,或者下方更推荐的写法。

C++:命名的强制类型转换

一个命名的强制类型转换具有如下形式:

cast_name<type>(expression);

type是转换的目标类型,expression是要转换的值。

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

static_cast、reinterpret_cast、const_cast、dynamic_cast

static_cast

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

1. 当需要把一个较大算术类型赋值给较小类型或整型与浮点类型相互赋值时,编译器通常会因为潜在的精度丢失告警,而static_cast相当于告诉编译器,我们知道并且不在乎潜在的精度丢失。
这属于是编译器隐式执行的类型转换。

    int i2 = 10;
    short  s1 = static_cast<short>(i2);
    float f1 = static_cast<float>(i2);

2. static_cast并非只能处理任何隐式类型转换,某些编译器无法自动执行的类型转换也有用。
如void* -> int*   void* -> float*的转换,也就是使用static_cast找回存在于void*中的值。

    void* p2 = &i2;
    int* p3 = (int*)p2;
    int* p4 = static_cast<int*>(p2);

但是注意,其他的指针类型之间的转换,不能使用static_cast。因为int* double* char*等不属于相关的类型,故static_cast主要适用于C编译器可以执行的隐式类型转换。

reinterpret_cast

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释,用于将一种类型转换为另一种不同的类型。

reinterpret_cast通常适用于C语言编译器无法隐式类型转换的强制类型转换。

    // 1
    int* pi = new int(3);
    char* pc = (char*)pi;   // C style。必须强转
//    char* pc2 = static_cast<char*>(pi);  // Static_cast from 'int *' to 'char *' is not allowed
    char* pc3 = reinterpret_cast<char*>(pi); // must use reinterpret_cast

    // 5
    int i = 10;
    double* pd3 = (double*)i; // C style 强制转换
//    double* pd4 = static_cast<double*>(i); // Cannot cast from type 'int' to pointer type 'double *'
    double* pd5 = reinterpret_cast<double*>(i);


const_cast

const_cast只能改变运算对象的底层const,最常用的用途就是删除变量的const属性,方便赋值(顶层const指const修饰对象本身,底层const指const修饰指针所指向的对象)

void test_const_cast()
{
    const int a = 10;
    const int* pa = &a;
    int* p = const_cast<int*>(pa);
    *p = 20;
    cout << a << endl;  // 10
    cout << *p << endl << endl;  // 20

    volatile const int i = 10; // volatile 易变的 也就是不允许编译器进行优化(寄存器or宏式替换优化),每次都去内存中取值
    int* p2 = (int*)&i;
    *p2 = 20;
    cout << i << endl;
    cout << *p2 << endl;

    // 下方ptr指向的"aaa"其实是存储在常量区的,不同于上方
//    const char* ptr = "aaa";
//    char* p3 = const_cast<char*>(ptr);
//    *p3 = 'z';   // 错误,非编译错误,本质因为"aaa"存储在常量区,不能被修改!!!
//    cout << ptr << endl;
}

解析: 

可以使用const_cast或强转将const int* 赋值给 int*,并使用这个int*修改常变量的值的根本原因是const int i这样的常变量没有存储在内存中的常量区,而是在栈区中,这样,如果得到一个int*指针指向常变量存储的地址,就可以修改它了。

而下方打印出a == 10是因为编译器的优化行为,编译器认为常变量不会改变所以将其存储在寄存器中一份,利用int*修改常变量是修改内存中的常变量,打印时打印出的是寄存器中的,故还是原来的值10。而后方*p从内存中取时,就是修改后的值了。

注意编译器对常变量的另一种优化方式是编译时进行类似于宏式的替换,直接将代码中的常变量换为常变量的初始值。事实上VS的编译器就是这样做的。(可通过反汇编看出)(YDYBJ)(注意,VS下的监视窗口实际上是调试器得出的数据信息,是从内存中获取的,所以在VS监视窗口查看上方代码中a的值就是修改后的值)


当然,const_cast的作用并不是让你去修改常变量的值。通常用于删除变量的const属性,方便赋值(变量本身没有const属性)

dynamic_cast

dynamic_cast用于将基类的指针或引用安全地转换成派生类的指针或引用。

dynamic_cast支持运行时类型识别! RTTI - run-time type identification,C++RTTI功能由两个运算符实现:typeid dynamic_cast (decltype)


回顾继承

向上转型:子类指针/引用 -> 父类指针/引用。这里并不属于类型转换,因为这是语法原生支持的,发生切片/切割。
向下转型:父类指针/引用 -> 子类指针/引用(用dynamic_cast转型是安全的)

(注意,子类对象赋值 或 拷贝构造父类对象,本质还是调用拷贝构造 or operator=,而参数为父类引用,故,本质还是父类引用引用子类对象。而父类对象无论如何都不能赋值给子类对象,同理子类引用无法直接引用父类对象)(说实话,其实我更喜欢称为基类和派生类妈的)


父类指针/引用可以强转为子类指针/引用,但这是危险的行为,因为父类指针/引用的指向不一定,若原本指向子类对象,则转为子类指针是合理的,但若原本指向父类对象,则强转后产生一个指向父类对象的子类指针,此时这个指针可以访问子类数据成员,但是是非法访问,因为对应数据不存在。

dynamic_cast可以检测父类指针/引用的指向,若指向子类对象,则安全,返回对应转换结果,若指向父类对象,则转换失败,指针转换失败则返回nullptr,引用转换失败则抛出一个bad_cast异常

示例代码一:dynamic_cast进行指针/引用转换 

class Base
{
public:
    virtual void func()
    {}
public:
    int _base = 10;
};

class Derived: public Base
{
public:
    int _derived = 20;
};

void test_dynamic_cast()
{
    Base b;
    Derived d;

    // 向上转型:从子类到父类,从派生类到基类
    Base b2 = d; // 调用 Base(const Base& b); 父类引用引用子类对象
    Base* pb = &d;
    Base& fb = d;

    // 向下转型
    Base* pb2 = &b;
    Derived* pd = (Derived*)pb2;  // 这是不安全的
    Derived* pd2 = static_cast<Derived*>(pb2);  // 这是不安全的
    // Clang-Tidy: Do not use static_cast to downcast from a base to a derived class; use dynamic_cast instead

    // dynamic_cast的正确使用
    if(Derived* pd3 = dynamic_cast<Derived*>(pb2))
    {
        // 转换成功,使用pd3指向的Derived对象
        cout << "dynamic cast successful " << pd3->_derived << endl;
    }
    else
    {
        // 转换失败,pd3 == nullptr, 使用pb2指向的Base对象
        cout << "dynamic cast failed " << pb2->_base << endl;
    }

    // 向下转型:引用
    Base& rb = b;
    Derived& rd = (Derived&)rb; // 这是不安全的

    try
    {
        Derived &rd2 = dynamic_cast<Derived &>(rb); // 转换失败则抛出bad_cast异常
        // 使用rd2所引用的Derived对象
        cout << rd2._derived << endl;
    }
    catch(const bad_cast& bc)
    {
        cout << bc.what() << endl;
    }

    // 向下转型的错误示例,这些都不可以。
//    Derived d2 = b; // error
//    Derived d2 = reinterpret_cast<Derived>(b); // error
//    Derived& pd = b; // error
}

引用类型转换失败时dynamic_cast抛bad_cast异常,而不是像指针类型转换失败时返回nullptr的原因是因为没有空引用...

示例代码二

void test_dynamic_cast2()
{
    A1 a1;
    A2 a2;
    B b;

    A1* p1 = &b;
    A2* p2 = &b;
    B* pb = &b;
    cout << p1 << endl;
    cout << p2 << endl;   // 不同于p1 pb,切片现象
    cout << pb << endl << endl;

    B* pb1 = (B*)p1;   // 不安全
    B* pb2 = static_cast<B*>(p2);   // 不安全,同于上方 Clang-Tidy: Do not use static_cast to downcast from a base to a derived class; use dynamic_cast instead
    cout << pb1 << endl;
    cout << pb2 << endl << endl;

    B* pb3 = reinterpret_cast<B*>(p1);
    B* pb4 = reinterpret_cast<B*>(p2); // 'reinterpret_cast' to class 'B *' from its base at non-zero offset 'A2 *' behaves differently from 'static_cast'
    cout << pb3 << endl;
    cout << pb4 << endl << endl;

    B* pb5 = dynamic_cast<B*>(p1);
    B* pb6 = dynamic_cast<B*>(p2);
    cout << pb5 << endl;
    cout << pb6 << endl << endl;
}

事实证明,reinterpret_cast就像它的名字一样:重新解释,通常为操作数的位模式提供较低层次的重新解释,而不会处理多继承的指针偏移问题。 

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

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

相关文章

信息学奥赛一本通——1163:阿克曼(Ackmann)函数

文章目录1163&#xff1a;阿克曼(Ackmann)函数【题目描述】【输入】【输出】【输入样例】【输出样例】分析代码1163&#xff1a;阿克曼(Ackmann)函数 时间限制:1000ms内存限制:65536KB提交数:24804通过数:20247时间限制: 1000 ms 内存限制: 65536 KB 提交数: 24804 通过数: 202…

第三十章 linux-模块的文件格式与EXPORT_SYMBOL的实现

第三十章 linux-模块的文件格式与EXPORT_SYMBOL的实现 文章目录第三十章 linux-模块的文件格式与EXPORT_SYMBOL的实现模块的文件格式EXPORT_SYMBOL的实现模块的文件格式 以内核模块形式存在的驱动程序&#xff0c;比如demodev.ko&#xff0c;其在文件的数据组织形式上是ELF&am…

数据结构---快速排序

快速排序分治法思想基准元素的选择元素交换双边循环法JAVA实现单边循环法JAVA实现快速排序也是从冒泡排序演化而来使用了 分治法&#xff08;快的原因&#xff09;快速排序和冒泡排序共同点&#xff1a;通过元素之间的比较和交换位置来达到排序的目的。 快速排序和冒泡排序不同…

JavaWeb核心:HTTPTomcatServlet

HTTP 概念: Hyper Text Transfer Protocol&#xff0c;超文本传输协议&#xff0c;规定了浏览器和服务器之间数据传输的规则。 HTTP-请求数据格式 HTTP-响应数据格式 响应状态码的大的分类 常见的响应状态码 Tomcat 简介 概念: Tomcat是Apache 软件基金会一个核心项目&#…

【云原生】Prometheus 自定义告警规则

文章目录一、概述二、告警实现流程三、告警规则1&#xff09;告警规则配置1&#xff09;监控服务器是否在线3&#xff09;告警数据的状态四、实战操作1&#xff09;下载 node_exporter2&#xff09;启动 node_exporter3&#xff09;配置Prometheus加载node_exporter4&#xff0…

这样也可以让图像正向扩散

🍿*★,*:.☆欢迎您/$:*.★* 🍿 怎样的扩散取决于b是不是随机噪声 是随机噪声 则是扩散模型 如stable diffision 如果是非噪声则是方向模型 方向模型是指 在已知几个连续的输入 后可以通过模型的辅助预测扩散的方向 而 stable diffision 是通过预测反扩散方向 本质就…

VS2017中OpenCV编程插件Image Watch安装和使用介绍

安装 下载适合vs2017最新版本的Image Watch(ImageWatch.vsix)&#xff0c;下载地址 安装ImageWatch&#xff0c;双击ImageWatch.vsix进行安装即可&#xff1b; 使用 打开一个OpenCV工程&#xff0c;在Debug下设置断点&#xff0c;通过view -> other windows -> Image W…

基于51单片机宠物自动投料喂食器控制系统仿真设计( proteus仿真+程序+讲解视频)

基于51单片机宠物自动投料喂食器控制系统仿真设计( proteus仿真程序讲解视频&#xff09; 仿真图proteus 7.8及以上 程序编译器&#xff1a;keil 4/keil 5 编程语言&#xff1a;C语言 设计编号&#xff1a;S0029 视频讲解 基于51单片机的宠物自动投料喂食器控制系统proteu…

数据结构—最小生成树

目录 一、生成树 二、最小生成树&#xff08;代价最小树&#xff09; 三、求最小生成树 1、Prim算法&#xff08;普里姆&#xff09; 2.Kruskal 算法&#xff08;克鲁斯卡尔&#xff09; 3.Prim算法和Kruskal算法对比 一、生成树 连通图的生成树是包含图中全部顶点的一个…

[附源码]Nodejs计算机毕业设计基于框架的秧苗以及农产品交易网站Express(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分…

算法分析专业工具——大O记法

本文内容借鉴一本我非常喜欢的书——《数据结构与算法图解》。学习之余&#xff0c;我决定把这本书精彩的部分摘录出来与大家分享。 写在前面 从之前的章节中我们了解到&#xff0c;影响算法性能的主要因素是其所需的步数。 然而&#xff0c;我们不能简单地把一个算法记为“…

Postman下载,安装,汉化,注册及登录教程

目录 一、Postman简介 二、Postman的注册 1、首先下载Postman&#xff0c;进入官网&#xff1a;Download Postman | Get Started for Free 2、安装Postman 3、下载汉化包 4、找到所下载的app.zip文件&#xff0c;将文件进行解压&#xff0c;放置到此路径下 Postman\app9…

代码随想录Day52|300.最长递增子序列、674.最长连续递增序列、718.最长重复子数组

文章目录300.最长递增子序列674.最长连续递增序列718.最长重复子数组300.最长递增子序列 文章讲解&#xff1a;代码随想录 (programmercarl.com) 题目链接&#xff1a;300. 最长递增子序列 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 给你一个整数数组 nums…

Eclipse常用开发配置

Eclipse常用开发配置1. 编码配置1.1 输出中文乱码问题1.2 Java文件中文乱码2. 切换JDK、修改JRE3. 错误&#xff1a;找不到或无法加载主类4. 修改字体大小4.1 修改编辑窗口字体大小4.2 修改编译器字体大小5. 新建Java项目6. 导入项目6.1 导入git6.2 导入已有Java项目7. 运行中文…

.net core AutoMapper的简单使用。

AutoMapper主要处理对象与对象之间的映射&#xff0c;减少程序员自己编写代码的工作量&#xff0c;提高开发效率。 应用场景&#xff1a; 假如你想对原始数据&#xff0c;进行部分字段展示&#xff0c;那么你需要创建一个对应的DTO类&#xff0c;进行手动映射&#xff0c;这样…

在电网上使用的储能系统模拟(simulink)

目录 1 概述 2 配电系统 3 动态负载模型 4 光伏电场和TMY3数据 5 储能系统 (ESS) 6 案例 7 仿真结果 8 Simulink&Matlab代码实现 1 概述 (1)目标展示了SimPowerSystems在不到一分钟的模拟时间内&#xff0c;以相量模式模拟电路和控制系统的能力。 (2)说明与能量存储…

对话顶立欧雅纳特丨传统制造企业的“人货场”重构该从何入手?

链条长、客单价高、标准化程度低、交付周期长......作为传统制造行业中颇具代表性的领域&#xff0c;家居建材一直被视为“距离互联网最远”行业之一&#xff0c;平均仅有10%的数字化率&#xff0c;行业整体的数字化转型相对滞后。随着外部环境的变动与行业生态的发展&#xff…

RK3588平台开发系列讲解(AUDIO篇)Android音频调试--tiny-alsa 工具

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、工具介绍二、工具的使用2.1 tinyplay2.1 tinycap2.3 tinymix2.4 tinypcminfo沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇将介绍Android下audio调试工具tiny-alsa的使用方法。 一、工具介绍 RK平台…

FFT学习笔记(快速傅里叶变换)

用途 快速傅里叶变换&#xff08;Fast Fourier Transformation&#xff0c;简称FFT&#xff09; 一般用来加速多项式乘法。求两个nnn次多项式相乘&#xff0c;朴素算法需要O(n2)O(n^2)O(n2)&#xff0c;但FFT只需要O(nlog⁡n)O(n\log n)O(nlogn)就能解决。 多项式 系数表示法…

基于java的扫雷游戏的设计-计算机毕业设计

项目介绍 扫雷游戏的基本功能&#xff1a;点击鼠标左键于未知区域,如果未知区域有雷,游戏停止,显示所有的地雷。如果没雷,则显示周围雷数,如果周围没雷,则再查看周围八个区域是否有雷直到有雷为止并显示,玩家需要尽快找出雷区中的所有不是地雷的方块&#xff0c;而不许踩到地雷…