【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种类型转化的应用场景。

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

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

相关文章

Simulink建立4WIS线性二自由度参考模型

4WIS线性二自由度参考模型 基于前轮转向做了小改动&#xff0c;难度不大&#xff0c;相当于两个微分方程加了两项 Simulink向CarSim中输入四个车轮的转角 有一点注意&#xff0c;四轮转向&#xff0c;前后轴车轮转角不应相等&#xff0c;否则动画会很滑稽 同侧车轮转向角的大小…

各种内部排序算法的比较及应用(插入排序、交换排序、选择排序、归并排序、基数排序)

目录 内部排序 前言 1.内部排序算法的比较 1.1各种排序算法的特点、比较和适用场景 1.2排序算法的稳定性判断及改进 1.3更适合采用顺序存储的排序算法 1.4根据排序的中间过程判断所采用的排序算法 1.5各种排序算法的性质 2.内部排序算法的应用 2.1选取排序算法时需要…

UE4_Ben_图形52_水下效果处理

学习笔记&#xff0c;不喜勿喷&#xff0c;欢迎指正&#xff0c;侵权立删&#xff01;祝愿生活越来越好&#xff01; 在这个后期处理的效果中&#xff0c;我们可以看到有很多不同的&#xff0c;这里有浓雾&#xff0c;波纹扭曲&#xff0c;镜头扭曲和边缘模糊&#xff0c;在第4…

pcb实验六-元件设计

目录 一&#xff0c;绘制28管脚PLCC封装ATF750C-10JC元件 二&#xff0c;绘制变压器原理图符号&#xff0c;并生成各种库文件输出报表 1&#xff0c;绘制变压器原理图 2&#xff0c;添加封装 3&#xff0c;输出报表文件 三&#xff0c;绘制音乐集成芯片及LCD元件 1&…

Apache漏洞复现:【CVE-2021-42013】【CVE_2021_41773】【CVE-2017-15715】

声明 严禁读者利用本文介绍知识点对网站进行非法操作 , 本文仅用于技术交流和学习 , 如果您利用文章中介绍的知识对他人造成损失 , 后果由您自行承担 , 如果您不能同意该约定 , 请您务必不要阅读该文章 , 感谢您的配合 ! 远程代码执行 CVE-2021-42013 描述 Apache HTTP Ser…

R语言数据探索和分析21-中国GDP及其影响因素多元线性回归分析

一、研究背景和意义 GDP 是宏观经济中最受关注的经济统计数字&#xff0c;目前我国国内生产总值年均增长率均明显高于同期美、日等发达经济体和巴 西、俄罗斯、南非、印度等其他金砖国家&#xff0c;成为世界经济增长的主力军&#xff0c;GDP 的增长对一个国家有着十分重要的意…

mysql中事务的简介

大家好。我们在日常开发过程中肯定都或多或少的用到过事务&#xff0c;而且在面试时&#xff0c;数据库的事务也是必问内容之一。今天我们就来说说mysql的事务。 为了方便我们下面内容的讲解&#xff0c;我们也先建立一个讲事务必用的表–account表&#xff0c;并在表中插入两…

基于centos7打包当前环境的系统为iso镜像文件

1. 准备工作 1.下载安装mondo 切换到root用户&#xff0c;进入yum下载库 # cd /etc/yum.repos.d # wget ftp://ftp.mondorescue.org/centos/7/x86_64/mondorescue.repo 打开文件mondorescue.repo&#xff0c;修改gpgcheck属性为0&#xff0c;指定mondorescue.repo安装 # 安…

【Python数据挖掘实战案例】机器学习LightGBM算法原理、特点、应用---基于鸢尾花iris数据集分类实战

一、引言 1、简要介绍数据挖掘的重要性和应用 在数字化时代&#xff0c;数据已经成为企业和社会决策的重要依据。数据挖掘作为一门交叉学科&#xff0c;结合了统计学、机器学习、数据库技术和可视化等多个领域的知识&#xff0c;旨在从海量数据中提取有价值的信息&#xff0c…

生命在于学习——Python人工智能原理(3.2)

三、深度学习 &#xff08;二&#xff09;人工神经网络 人工神经网络是模仿人类大脑神经系统工作原理所创建的数学模型&#xff0c;有并行的分布处理能力、高容错性和自我学习等特征。 1、感知器 感知器由Frank Roseblatt于1957年提出&#xff0c;是一种广泛使用的线性分类…

RPC框架原理(一)

RPC框架原理 网络和IO的关系&#xff0c;IO&#xff08;input和output&#xff09;面向的是谁&#xff1f;OSI 7层参考模型&#xff0c;TCP/IP协议为什么会出现一个会话层三次握手socket心跳keep alive四次挥手 网络IO&#xff08;IO模型&#xff09; IO框架底层 学习顺序&…

GaussDB技术解读——GaussDB架构介绍(一)

目录 1 GaussDB 关键架构目标 2 GaussDB分布式架构 2.1 GaussDB 分布式关键技术架构 3 数据计算路由层&#xff08;Coordinator&#xff09;关键技术方案 3.1 分布式优化器 3.2 分布式执行框架 GaussDB是华为自主创新研发的关系型数据库&#xff0c;基于华为在数据库领域…

api接口模块封装

1&#xff1a;前端封装接口 前端请求的统一封装也是为了方便前端项目的请求维护起来更加方便&#xff0c;将页面中的请求封装到js文件中&#xff0c;不同的页面需要用到相同的请求可以直接进行复用。 第一步创建一个api文件夹和js文件 第二步&#xff1a;在文件中导入axios&am…

为什么投资气膜网球馆是明智之选—轻空间

在现代体育产业快速发展的背景下&#xff0c;投资体育场馆成为许多投资者关注的焦点。其中&#xff0c;气膜网球馆以其独特的优势和广泛的市场需求&#xff0c;逐渐成为投资者的热门选择。那么&#xff0c;为什么投资气膜网球馆是一个明智之选呢&#xff1f; 1. 建设周期短&…

SpringBoot+Vue幼儿园管理系统(前后端分离)

技术栈 JavaSpringBootMavenMyBatisMySQLVueElement-UI 系统角色 教师用户管理员 功能截图

JAVA小案例-输出100-150中能被3整除的数,每5个换行

JAVA小案例-输出100-150中能被3整除的数&#xff0c;每5个换行 代码如下&#xff1a; public class Continue {/*** continue练习&#xff0c;输出100-150中能被3整除的数&#xff0c;每5个换行* param args*/public static void main(String[] args) {int count 0;//计数器…

【全开源】数据大屏系统源码(ThinkPHP+FastAdmin)

&#x1f4c8;数据大屏系统&#xff1a;可视化数据的魅力舞台&#x1f5a5; 基于ThinkPHPFastAdmin开发的数据大屏&#xff0c;可视化义拖拽快速制作数据大屏/科技大屏&#xff0c; 内置30组件、获取实时数据、使用简单易上手&#xff0c;轻松实现图形数据统计等。​ 一、引言…

03--nginx架构实战

前言&#xff1a;这应该是nginx梳理的最后一章&#xff0c;写一些关于网站架构和网站上线的知识内容&#xff0c;主要是感觉到运维并不是单一方向的行业&#xff0c;这一章概念会有一些广泛&#xff0c;但是非常重要&#xff0c;都是这几年工作中遇到的情况&#xff0c;整理一下…

【设计模式】JAVA Design Patterns——Private Class Data(私有类数据设计模式)

&#x1f50d;目的 私有类数据设计模式试图通过限制属性的可见性来减少属性的暴露。 通过将它们封装在单个Data对象中&#xff0c;可以减少类属性的数量。 &#x1f50d;解释 真实世界例子 想象一下你在为家人做晚餐炖汤。你想阻止家庭成员在你烹饪时偷偷品尝菜品&#xff0c;否…

C#WPF数字大屏项目实战08--生产量/良品统计

1、区域划分 生产量/良品统计这部分位于第二列的第二行 2、livechart拆线图 定义折线图,如下: <lvc:CartesianChart> <lvc:CartesianChart.Series> <!--设置Series的类型为 Line 类型, 该类型提供了一些折线图的实现--> <lvc:LineSeries/>…