【C++】C++提供类型转换的机制

目录

前言:

一,static_cast

二,reinterpret_cast

三,const_cast

四,dynamic_cast


前言:

        传统的不同类型转换有隐式类型转换(类型不匹配时编译器自动进行的转换,如:int a;float b; a = b;)和强制类型转换(强制类型转换也叫显示类型转换,如:a = (int)b)。

        隐式类型转换:基本数据类型之间的转换。内置类型之间的隐式转换系统内部自动完成,但必须是两者相近的类型,如int,char,double等。自定义类型之间的隐式转换借助的是构造函数来完成。内置类型隐式转换成自定义类型也是借助的构造函数完成。自定义类型隐式转成内置类型借助的是一个重载类型的函数,即:operator 类型。 

class B
{
public:
    int _b = 10;
};
class A
{
public:
    A(int a = 0) :_a(a) {    }
    A(const B& b) :_a(b._b) {    }
    //重载一个隐式转换成int型的函数
    operator int() 
    {
        return _a + 1;
    }
    int _a;
};

int main()
{
    int x = 5;
    double f = 3.14;
    A a;
    B b;
    a = x; //内置类型隐式转换成自定义类型
    cout << "A: " << a._a << "  " << "x: " << x << endl;
    a = b; //自定义类型隐式转换成自定义类型
    cout << "A: " << a._a << "  " << "B: " << b._b << endl;
    x = a; //自定义类型隐式转换成内置类型
    cout << "x: " << x << "  " << "A: " << a._a << endl;
    x = f; //内置类型之间的隐式类型转换,系统内部自动实现
    cout << "x: " << x << "  " << "f: " << f << endl;
    return 0;
}

        强制类型转换:指名要转换的类型后系统内部强制进行机制转换,比如指针、整数和函数指针之间,隐式转换很多情况下都无法完成。这种转化机制比较强大,即便相似度较小也能完成转换, 但若是几乎没有什么相似度,那么也无法完成。一般用于特殊说明类型或针对于指针的情况

int main()
{
    int a = 1;
    double f = 3.14;
    char c = 'a';

    const char* str = "aaa";

    //int* p = &f; 错误,不能隐式转换
    int* p = (int*)&f;
    //a = &c; 错误,不能隐式转换
    a = (int)&c;
    //p = a; 错误,不能隐式转换
    p = (int*)a;

 

    //a = (float)str; 错误,const char*与float基本没有相似度,无法强制转换
    return 0;
}

        隐式类型的转换主要是基于相似度较大的数据之间,通常不适用于指针。强制类型转换范围较广,可以实现有关指针转换的情况。但无论是哪种转换,这里面都存在不少的缺点。隐式类型转化有些情况下可能会出现数据精度丢失,可维护性和可读性差,而显式类型转换将所有情况混合在一起,代码不够清晰,难以发觉。因此C++提出了四种类型转换操作符:static_cast、reinterpret_cast、const_cast、dynamic_cast。它们分别拥有自己的风格。


一,static_cast

        static_cast类型转换运算符基本等价于以前的隐式类型转换,即主要用于基本数据类型之间的隐式类型转换,不能用于指针、整数和函数指针之间的强制转换。为了预防隐式类型转化可能出现数据丢失的情况,C++提供了关键字static_cast来明确这里正在进行隐式转换,使用于需要明确隐式转换的地方,即将隐式转换显式化表示出来。它是一个更安全的类型转换运算符。

int main()
{
    double d = 12.34;
    int a1 = static_cast<int>(d); //将d隐式转换成int型,然后赋值a1
    cout << a1 << endl;
    //上面a1的static_cast<int>(d)隐式转换与下面a2对应
    int a2 = d; 
    cout << a2 << endl;

    int* p = &a1;
    //int a3 = static_cast<int>(p); 错误,相当于a3 = p;

    //int* p1 = static_cast<int*>(a1); 错误,相当于int* p1 = a1;
    return 0;
}


二,reinterpret_cast

        reinterpret_cast操作符类似于以前强制类型转换,但又不能单纯理解为以前的强制类型转换,适用于static_cast隐式转换的它就不适用,因为它提供了更多的类型安全性。总的来说,reinterpret_cast操作符适用于隐式转化以外的强制转化,即reinterpret_cast用于指针、整数和函数指针之间的转换,而不是基本数据类型之间的隐式转换。

int main()
{
    double f = 3.14;
    int i = 1;
    int* pi = &i;
    double* pf = &f;    
    //static_cast与reinterpret_cast的正确使用
    int a = static_cast<int>(f);
    float f1 = static_cast<float>(i);
    int a1 = reinterpret_cast<int>(pi);  //对应int a1 = (int)pi;
    int a2 = reinterpret_cast<int>(pf);  //对应int a2 = (int)pf;

    

    //下面都是错误的使用
    int a = reinterpret_cast<int>(f); //错误,两数据间可以进行隐式转换了, 但 a = (int)f 正确
    float f1 = reinterpret_cast<float>(i); //错误,同理,传统的 f1 = (float)i 正确
    int a1 = static_cast<int>(pi);        //错误,pi不能隐式转换成int型
    int a2 = static_cast<int>(pf);       //错误,同理
    return 0;
}

        reinterpret_cast也不是特别安全,因为它对应的是强制转换,这就导致它很容易被误用而引发错误或其它行为,比如子类指针强转为父类指针,这就可能导致父类指针指向子类对象出现越界访问的情况。因此,我们应特别小心使用reinterpret_cast,大多数情况下应该优先使用其它类型转换操作符,它们提供了更多的安全性。

        需要适用隐式类型转换的时候要用static_cast,隐式类型不能转换的且需要强制类型转换的要用reinterpret_cast,这里的强制转换不完全等同于传统的强制类型转换,可以理解为被限制的传统强制类型转换。


三,const_cast

        我们先来观察下面代码的问题。

int main()
{
    const int a1 = 2;
    int* p1 = (int*)&a1;
    *p1 = 3;
    cout << a1 << endl;
    cout << *p1 << endl;
    return 0;
}

输出窗口: 

 监视窗口:

        这里发现输出a1与p1所对应的数据不一样,但通过调试,监视窗口数据都正常。出现这种问题源于编译器对const数据的优化。由于const型数据不会改变,这里会把a1存入寄存器中,输出打印时把数据从寄存器中取出,但我们通过指针p1将内存中a1进行改变,监视窗口是从内存中读取的数据,这也就是输出打印时a1数据不变,监视窗口a1正常改变的原因。

        C++提供了关键字volatile 解决这块问题。volatile关键字告诉编译器某个变量的值可能在程序的执行过程中被外部因素修改。因此,volatile 处理变量后,数据不会往寄存器中存取,编译器总是从内存中读取实际值,而不是假设它可以在寄存器中缓存并重复使用,如:volatile const int a1 = 2,此时跟预料一样输出。

        const_cast转换也是类似于强制转换,与static_cast不同的是它常用来删除变量的const属性,不适用其它相关的强制转换。专门把const属性单独拿出来就是专门提醒去掉const是有一些内存可见优化的风险,要注意是否加了volatile,即上面那种情况。

int main()
{
    volatile const int a1 = 2;
    int* p1 = const_cast<int*>(&a1); //与int* p1 = (int*)&a1类似
    *p1 = 3;

    //若没有volatile修饰,这里输出的a1是2
    cout << a1 << endl;
    cout << *p1 << endl;

    //const_cast与reinterpret_cast对比
    int a2 = 1;
    //int p2 = const_cast<int>(p1); 错误,const_cast中的类型必须是指针、引用,
    int p2 = reinterpret_cast<int>(p1); //属于reinterpret_cast的强制类型转换,正确
    //int* p3 = const_cast<int*>(a2); 错误, const_cast只能去除const常性, 不能更改其类型
    int* p3 = reinterpret_cast<int*>(a2); //正确,与上同理
    return 0;
}


四,dynamic_cast

        dynamic_cast主要用于继承体系,解决将一个父类对象的指针或引用转换为子类对象的指针或引用(向下转型)出现的问题。

        向上转型:子类对象指针或引用转换成父类指针或引用(不需要转换,赋值兼容规则),即子类指针或引用赋值给符父类指针或引用。

        向下转型:父类对象指针或引用转换成子类指针或引用(用dynamic_cast转型是安全的),即父类指针或引用赋值给子类指针或引用。

        继承体系中,父类对象无论如何都不可能赋值子类对象,但父类指针或引用可以指向子类对象,这时,倘若单纯访问父类数据是没有任何问题的,但若是访问子类对象中的数据,由于父类指针或引用指向的只是包含父类这块空间的数据,没有包含子类的数据,所以这里会出现越界访问,即向下转型出现的问题。向上转型中,子类指针或引用指向更大的空间,赋值给父类指针或引用后,父类指针或引用虽然只能访问父类数据,但是这里没有出现越界访问,不存在安全隐患。总的来说,向上转型是从大空间转到小空间,可以正常进行;向下转型是从小空间转到大空间,会出现越界访问。

        当发生向上转型或向下转型时,dynamic_cast操作符会进行检查,若是向下转型,他将返回空指针,转型失败;若是向上转型或其它正常情况,将正常返回,转型成功。

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

    int _a = 0;
};

class B : public A
{
public:
    int _b = 1;
};

void fun(A* pa)
{

    //若pa指向子类对象,转回子类,正常转换;若pa指向父类对象,转回子类,转换失败
    B* pb = dynamic_cast<B*>(pa);
    if (pb)
    {
        cout << pb << endl;
        cout << pb->_a << endl;
        cout << pb->_b << endl;
    }
    else
    {
        cout << "转换失败" << endl;
    }
}

int main()
{
    A a;
    B b;
    A* pa = &a;
    B* pb = &b;
    //向上转型(子类指针赋值给父类指针)
    A* ppa = dynamic_cast<A*>(pb);
    if (ppa)
    {
        cout << "转换成功" << endl;
    }
    else
    {
        cout << "转换失败" << endl;
    }


    //向下转型(父类指针赋值给子类指针),ppb访问_b时出现越界访问,pa指向的空间中没有_b那块空间
    B* ppb = dynamic_cast<B*>(pa);    
    if (ppb)
    {
        cout << "转换成功" << endl;
    }
    else
    {
        cout << "转换失败" << endl;
    }
    
    fun(&a);
    fun(&b);
    return 0;
}

        注意:dynamic_cast只能用于父类含有虚函数的类,没有虚函数会报错,因为它是借助虚表来识别父子类。


总结:

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

        最后要说明,static_cast、reinterpret_cast、const_cast、dynamic_cast都是编程中的规范,它们各自对类型转换的限制有助于减少因误用类型转换而导致的错误,虽然它们也存在一定的安全隐患,但用此操作符特意明确这里存在类型转换,使我们能够意识到这里在进行对应功能的类型转换,方便对其操作。

        编程规范没有强制规定,但建议平常运用时养成编程规范的习惯。求职的面试过程中,常问的这块内容是:1,C++中的4种类型转化分别是哪几种?     2,说说4种类型转化的应用场景。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【qt】MDI多文档界面开发

MDI多文档界面开发 一.应用场景二.界面设计三.界面类设计四.实现功能1.新建文档2.打开文件3.关闭所有4.编辑功能5.MDI页模式6.瀑布展开模式7.平铺模式 五.总结 一.应用场景 类似于vs的界面功能,工具栏的功能可以对每个文档使用! 二.界面设计 老规矩,边做项目边学! 目标图: 需…

2024年人力资源与社会治理国际会议(ICHRSG 2024)

2024年人力资源与社会治理国际会议 2024 International Conference on Human Resources and Social Governance 会议简介 2024年人力资源与社会治理国际会议是一个聚焦全球人力资源发展与社会治理创新的高端交流平台。本次会议汇集了全球顶尖的专家学者、企业高管和政策制定者&…

Transformer学习之SwinTransformer

1.算法简介 本文主要参考自以下链接&#xff0c;整理成线上的形式用于备忘&#xff0c;排版太麻烦了直接贴图&#xff0c;参考的朋友慎重&#xff0c;不如直接看参考链接&#xff0c;后期有了新的理解继续更正。 参考链接1&#xff1a;Swin-Transformer网络结构详解_swin tran…

智能售货机的商业潜力

智能售货机的商业潜力 1. 即时购物体验&#xff1a;在快节奏的生活中&#xff0c;人们往往缺乏闲暇去超市购物。智能售货机以其便捷性&#xff0c;提供了一种快速获取商品的方式&#xff0c;只需简单几步即可完成购买。 2. 全天候服务&#xff1a;智能售货机不受时间限制&…

鸿蒙轻内核M核源码分析系列九 互斥锁Mutex

多任务环境下会存在多个任务访问同一公共资源的场景&#xff0c;而有些公共资源是非共享的临界资源&#xff0c;只能被独占使用。鸿蒙轻内核使用互斥锁来避免这种冲突&#xff0c;互斥锁是一种特殊的二值性信号量&#xff0c;用于实现对临界资源的独占式处理。另外&#xff0c;…

使用JMeter软件压测接口配置说明

1、下载完该软件https://blog.csdn.net/wust_lh/article/details/86095924 2.点击bin文件中jmeter.bat脚本https://blog.csdn.net/wust_lh/article/details/86095924 3.官网地址https://jmeter.apache.org/download_jmeter.cgi 通过 【Options】->【Choose Language】变更为…

【调试笔记-20240606-Linux-为 OpenWrt 的 nginx 服务器添加Shell CGI 支持】

调试笔记-系列文章目录 调试笔记-20240606-Linux-为 OpenWrt 的 nginx 服务器添加Shell CGI 支持 文章目录 调试笔记-系列文章目录调试笔记-20240606-Linux-为 OpenWrt 的 nginx 服务器添加Shell CGI 支持 前言一、调试环境操作系统&#xff1a;Windows 10 专业版调试环境调试…

flutter日历范围选择器

1.传入日期跨度&#xff0c;选择上架日期时&#xff0c;自动显示下架日期 2.手动选择上架日期和下架日期(图中下架日期自动填了只需CalendarDateRangePicker在initState方法中使用_startDate widget.initialStartDate; _endDate widget.initialEndDate;&#xff0c;而不直接…

人工智能绘画的历史

人工智能绘画的起源可以追溯到20世纪50年代。当时&#xff0c;艺术家和科学家开始使用计算机生成图像和图形&#xff0c;将绘画艺术与技术领域相结合。计算机图像可以被视为人工智能绘画的一部分。下面&#xff0c;我们将按照时间顺序来了解人工智能绘画发展的一些关键时间节点…

26-LINUX--I/O复用-select

一.I/O复用概述 /O复用使得多个程序能够同时监听多个文件描述符&#xff0c;对提高程序的性能有很大帮助。以下情况适用于I/O复用技术&#xff1a; ◼ TCP 服务器同时要处理监听套接字和连接套接字。 ◼ 服务器要同时处理 TCP 请求和 UDP 请求。 ◼ 程序要同时处理多个套接…

如何使用SeaFile文件共享服务器结合内网穿透将家中电脑变成个人云盘

文章目录 1. 前言2. SeaFile云盘设置2.1 Owncould的安装环境设置2.2 SeaFile下载安装2.3 SeaFile的配置 3. cpolar内网穿透3.1 Cpolar下载安装3.2 Cpolar的注册3.3 Cpolar云端设置3.4 Cpolar本地设置 4.公网访问测试5.结语 1. 前言 本文主要为大家介绍&#xff0c;如何使用两个…

冯喜运:6.7今日外汇黄金原油走势分析及日内操作策略

【黄金消息面分析】&#xff1a;美国初请失业金人数超预期&#xff0c;市场对美联储9月降息预期升温&#xff0c;全球降息潮起&#xff0c;黄金市场受支撑。北京时间本周四&#xff0c;美国劳工部公布的数据显示&#xff0c;截至6月1日当周初请失业金人数增加至22.9万人&#x…

StableDiffusion Windows本地部署

检查电脑环境 启动CMD命令窗。 如上图&#xff0c;在CMD窗口输入python命令&#xff0c;可查看本地安装的python版本信息等。输入exit()退出python命令行 执行where命令&#xff0c;可查看python安装目录。 必须安装Python3.10.x&#xff0c;因为stable-diffusion-webui的一…

卫星通信频段有哪些

卫星通信使用到的频段涵盖L, S, C, Ku, Ka等&#xff0c;而最常用的频段是C(4~8GHz)和Ku(12~18GHz)频段&#xff0c;而Ka(27-40GHz)频段是后起之秀。目前地球赤道上空有限的地球同步卫星轨位几乎已被各国占满&#xff0c;C和Ku频段内的频率资源被大量使用&#xff0c;而Ka频段的…

Java学习中,如何理解注解的概念及常用注解的使用方法

一、简介 Java注解&#xff08;Annotation&#xff09;是一种元数据&#xff0c;提供了一种将数据与程序元素&#xff08;类、方法、字段等&#xff09;关联的方法。注解本身不改变程序的执行逻辑&#xff0c;但可以通过工具或框架进行处理&#xff0c;从而影响编译、运行时的…

新品发布 | 飞凌嵌入式RK3576核心板,为AIoT应用赋能

为了充分满足AIoT市场对高性能、高算力和低功耗主控日益增长的需求&#xff0c;飞凌嵌入式全新推出基于Rockchip RK3576处理器开发设计的FET3576-C核心板&#xff01; 集成4个ARM Cortex-A72和4个ARM Cortex-A53高性能核&#xff0c;内置6TOPS超强算力NPU&#xff0c;为您的AI…

ComfyUI 完全入门:必备插件

ComfyUI 是一个基于 Stable Diffusion 的AI绘画创作工具&#xff0c;最近发展势头特别迅猛&#xff0c;但是 ComfyUI 的上手门槛有点高&#xff0c;用户需要对 Stable Diffusion 以及各种数字技术的原理有一定的了解才行。这个系列将会介绍 ComfyUI 的一些基础概念和使用方法&a…

1+x(Java)中级题库易混淆理论题(三)

SQL 语句中进行 group by 分组时&#xff0c;可以不写 where 子句 分组时可以多层分组&#xff0c;比如&#xff1a;先按照省、再按照市来分组。 File 类不能获取文件的内容 在使用 select 语句进行查询分组时&#xff0c;如果希望去掉不满足条件的分组&#xff0c;使用 hav…

ES 8的向量检索性能调优实践

前言 ES的官方实验室曾发布过一篇博客,介绍了使ES向量检索性能获得显著提升的技术要点与展望: 多线程搜索能力的利用:Lucene 的分段架构允许实现多线程搜索能力。Elasticsearch 通过同时搜索多个段来提高性能,使用所有可用的 CPU 核心的计算能力显著减少了单个搜索的延迟。…

Laravel框架进阶:掌握队列系统,优化应用性能

Laravel使用队列处理 本文主要讲述如何利用 Laravel 框架的队列系统来管理异步任务和设置周期性执行的任务&#xff0c;从而增强应用程序的效能和可靠性。 Laravel队列的优势 异步执行&#xff1a;将任务添加到队列中后&#xff0c;可以立即返回响应给用户&#xff0c;而任务…