类型转换(2)

news2024/11/25 16:38:23

类型转换

  • 知识回顾
    • static_cast
    • const_cast
    • reinterpret_cast
  • 类型转换
    • dynamic_cast
    • 动态转换和静态转换区别
    • 动态转换的使用

知识回顾

static_cast

静态转换应用范围:

  • 基本数据类型的转换,但不能实现基本数据类型指针的转化,但是可以将无类型转成其他类型
  • 也可以将整型转换成枚举类型
  • 可以将左值转成右值
  • 可以实现上行转换,也就是将派生类指针转换成基类指针
class PtrInt
{
private:
    int* pval; // new .heap ; stack . data;
public:
    PtrInt(int* p = nullptr) :pval(p)
    {
    }
    ~PtrInt()
    {
        delete pval;
        pval = nullptr;
    }
    PtrInt(const PtrInt& it) :pval(new int(0))
    {
        *pval = *it.pval;
        //*pval = it.*pval;
    }
    PtrInt& operator=(const PtrInt& it)
    {
        if (this != &it)
        {
            delete pval;
            pval = new int(*it.pval);
            //pval = it.pval;
        }
        return *this;
    }
    PtrInt (PtrInt&& it) :pval(it.pval)
    {
        it.pval = nullptr;
    }
    PtrInt& operator=(PtrInt&& it)
    {
        if (this != &it)
        {
            delete[]pval;
            pval = it.pval;
            it.pval = nullptr;
        }
        return *this;
    }
};

PtrInt func(int x) {
    PtrInt tmp(new int(x));
    return tmp;
}

int main() {
    PtrInt a(new int(10));
    a = func(100);
    return 0;
}

首先创建a对象,然后进入func函数,在函数中创建了一个tmp对象,我们知道函数内部创建的局部对象会在函数结束时进行释放。所以我们创建的tmp对象最终会被释放掉,所以呢在return结束前会生成一个将亡值,将亡值的指针指向我们tmp对象的堆内存,然后释放tmp对象时就不会释放堆内存,最后func函数返回的将亡值调用移动赋值来对a对象进行赋值。如果在tmp对象前加上const,编译器就不会移动他的资源,如果加上static关键字,其就会在数据区,不会进行移动资源。
在这里插入图片描述

const_cast

去常性转换,强制转换的目标类型必须是指针或引用。

int main() {
    const int a = 10;
    int& b = const_cast<int&>(a);
    b = 100;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
}

思考上面代码输出结果是什么?输出结果a为10,b为100,有的同学可能回想b是a的引用,b的值改成了100,为什么a的值不是100呢?事实上,在运行时a的值的确被赋值成了100,也就是说a的值在运行时同b一样都是一百,但是常变量a在编译时就会进行替换,将a的值替换成10,所以输出为10。

reinterpret_cast

重新解释,其类似于强转,但是只适应于指针或引用的转换,不适用于值的转换。

int main() {
    int a = 0x61626364;
    char* cp = reinterpret_cast<char*>(&a);
    cout << *cp << endl;
    cout << *(cp+1) << endl;
    cout << *(cp + 2) << endl;
    cout << *(cp + 3) << endl;
    return 0;
}

同样观察上面代码思考其输出结果?
其输出结果是的d,c,b,a,0x61626364,我们的计算机是小端存储,所以地址存放低位,高地址存放高位,cp为低地址,存放16进制的64,而16进制的64转换成10进制就是100,正好是字母d的ASK码值,然后依次移动1字节,也就是63,62,61对应cba,对应输出结果是dcba。

类型转换

dynamic_cast

用法:dynamic_cast<type_name>(expression)
动态转换允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全的转换类型,把基类指针转换成派生类指针,或者把指向基类的左值转化成派生类的引用。
必须是公有继承,基类要有虚函数
特点:

  • C++其他三种类型转换均在编译时,而dynamic_cast在运行时执行的类型转换。
  • 如果对指针类型dynamic_cast转换失败,返回结果为nullptr。
  • 如果针对引用类型的dynamic_cast失败,则会抛出异常。
  • 在类的层次结构进行上行转换时,dynamic_cast和static_cast的效果是一样的。
  • 进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
class Object
{
private:
    int value;
public:
    Object(int x = 0) :value(x) {}
    virtual void func() { cout << "value: " << value << endl; }
};
class Base : public Object
{
private:
    int num;
public:
    Base(int x = 0) :Object(x + 10), num(x) {}
    virtual void func() { cout << "num: " << num << endl; }
};
int main() {
    Base base(100);
    Object obj(10);

    Object* obja = (Object*)&base;
    Object* objb = static_cast<Object*>(&base);
    Object* objc = dynamic_cast<Object*>(&base);

    Object& oba = (Object&)base;
    Object& obb = static_cast<Object&>(base);
    Object& obc = dynamic_cast<Object&>(base);

    Object oca = (Object)base;
    Object ocb = static_cast<Object>(base);
    Object occ = dynamic_cast<Object>(base);//err
    Object&& oc = dynamic_cast<Object&&>(base);

}

运行上面一段代码就可以应正我们的结论,在指针和引用转换的前提下进行上行转换,动态转换和静态转换都是一样的,但是在值转换的时候,动态转换就会报错,但可以将派生类对象转换为基类右值引用。(前提是继承关系,有虚函数)这也就是编译时转换。

int main() {
    Base base(10);
    void* vp = dynamic_cast<void*>(&base);
    Base* bp = dynamic_cast<Base*>(&vp);
    return 0;
}

通过这段代码我们可以发现动态转化可以将有类型指着转换成无类型指针,不可以将无类型指针转换成有类型指针。

动态转换和静态转换区别

仍然是上面的类和函数,执行下面一段代码

int main() {
    Base base(10);
    Object obj(20);
    Object* op = &obj;
    //Object* op=&base;
    Base* bp = dynamic_cast<Base*>(op);
    bp->func();
    Base* dp = static_cast<Base*>(op);
    dp->func();
}

我们可以分析上面代码,当基类指针指向派生类对象时,然后将其强转成派生类指针,然后调用func虚函数很显然其调用的是派生类的虚函数,因为其查找虚表时的虚表指针指向Base的虚表。静态转换和静态转换在此时结果是一样的,但是将op指向obj对象时,将op强转成Base指针类型就会出现强转错误导致bp指针为nullptr,不能执行func函数,静态转换可以运行并且输出基类的func函数。
这是什么原因呢?是这样的,因为静态转换是在编译时就确定了绑定关系,所以dp不管指针指向基类对象还是派生类对象都会调用op指向的的对象的虚表来查找调用函数。而动态转换会根据虚表指针来动态查找虚表的RTTI,找其基类类型,如果其基类没有找到也就是说不能把基类指针赋值给派生类指针,就会返回nullptr。所以也就不能调用func函数。在这里插入图片描述
动态转换通过虚表指针查看虚表,通过RTTI(运行时识别,存在一个指针)来查找信息,信息中存在其类型名,基类指针,派生类指针。
我们使用动态转换在基类的虚表中查找其Base类型是查不到的所以返回nullptr,而把派生类指针转换成基类指针就可以通过基类指针找到Obj类型名从而实现动态转换。

动态转换的使用


class Goods
{
    float _weight;  // 重量
public:
    Goods(float wt) : _weight(wt) {}
    virtual ~Goods() { std::cout << "~Trash()" << std::endl; }
    float GetWeight() const { return _weight; }
    virtual float GetPrice() const = 0;  // 价格
};

// 铜
class Copper : public Goods
{
    static float price;
public:
    Copper(float wt) : Goods(wt) {}
    float GetPrice() const { return price; }
    static void SetPrice(float newprice) {
        price = newprice;
    }
};
float Copper::price = 2.80;

// 纸张
class Paper : public Goods
{
    static float price;
public:
    Paper(float wt) : Goods(wt) {}
    float GetPrice() const { return price; }
    static void SetPrice(float newprice) {
        price = newprice;
    }
};
float Paper::price = 0.20;

// 玻璃
class Glass : public Goods
{
    static float price;
public:
    Glass(float wt) : Goods(wt) {}
    float GetPrice() const { return price; }
    static void SetPrice(float newprice) {
        price = newprice;
    }
};
float Glass::price = 0.32;

template<class Container >
void sumPrice(Container& bin)
{
    float total = 0;
    for (auto p : bin)
    {
        //cout << typeid(x).name() << endl;
        total += p->GetPrice() * p->GetWeight();
        cout << "wight of : " << typeid(*p).name() << " = " << p->GetWeight() << endl;
    }
    cout << "Total price = " << total << endl;
}

int main()
{
    srand(time(0)); // Seed the random number generator
    vector<Goods*> bin;
    int n = rand() % 100;  // 0 99
    for (int i = 0; i < n; i++)
    {
        switch (rand() % 3)
        {
        case 0:
            bin.push_back(new Copper((rand() % 1000) / 10.0));
            break;
        case 1:
            bin.push_back(new Paper((rand() % 1000) / 10.0));
            break;
        case 2:
            bin.push_back(new Glass((rand() % 1000) / 10.0));
            break;
        }
    }
    // Note: bins hold exact type of object, not base type:
    vector<Glass*> glassBin;
    vector<Paper*> paperBin;
    vector<Copper*> coppBin;
    // Sort the Trash
    for(auto xp:bin){
        Glass* gp = dynamic_cast<Glass*>(xp);
        Paper* pp = dynamic_cast<Paper*>(xp);
        Copper* cp = dynamic_cast<Copper*>(xp);
        if (gp != nullptr) {
            glassBin.push_back(gp);
        }
        else if (pp != nullptr) {
            paperBin.push_back(pp);
        }
        else if (cp != nullptr) {
            coppBin.push_back(cp);
        }
    }
    sumPrice(coppBin);
    sumPrice(paperBin);
    sumPrice(glassBin);
    sumPrice(bin);
    for (auto& x : bin)
    {
        delete x;
        x = nullptr;
    }
}

看上面代码,可以看出设置了一个抽象类,还有三个派生类,铜,玻璃,纸张。最后存在一个模板类型的函数来计算总价格。
主函数中设置了随机数材料总数,其三类材料也用了随机值,有一个Goods指针类型的容器,每创建一个类就将其放入容器中,然后设置三个材料类型的指针容器,将不同材料放入不同容器,最后计算价格。这段代码重点在于

for(auto xp:bin){
        Glass* gp = dynamic_cast<Glass*>(xp);
        Paper* pp = dynamic_cast<Paper*>(xp);
        Copper* cp = dynamic_cast<Copper*>(xp);
        if (gp != nullptr) {
            glassBin.push_back(gp);
        }
        else if (pp != nullptr) {
            paperBin.push_back(pp);
        }
        else if (cp != nullptr) {
            coppBin.push_back(cp);
        }
    }

很显然我们用了动态转换来查找虚表判断其类型,如果类型相匹配便返回派生类的指针类型,不匹配则返回nullptr,通过判断返回值来判断其类型放入不同容器进行材料分类。

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

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

相关文章

Linux 下进行权限修改

查看权限 ls -l 文件名该命令可以查看文件的详细属性&#xff0c;包括文件的权限 权限含义 -rwxrwxrwx在文件系统中&#xff0c;user、group、others的权限是分开的&#xff0c;第一个rwx代表user的权限、第二个rwx代表group的文件、第三个rwx代表others的权限 字符含义值…

真无线蓝牙耳机什么品牌比较好?五大高性价比真无线耳机推荐

与有线耳机相比&#xff0c;无线蓝牙耳机重量轻&#xff0c;便于携带。最重要的是避免了耳机线的麻烦&#xff0c;所以很受当代人的欢迎。什么牌子的蓝牙耳机好&#xff1f;哪个好用&#xff1f;本文中整理了五款市场上高性价比的无线蓝牙耳机&#xff0c;为您提供参考。 第一…

每天一个面试题之final在java中有什么作用?

final在java中有什么作用&#xff1f; final关键字表示最终的含义 当它用来修饰一个引用时&#xff1a; <1>:如果引用为基本数据类型&#xff0c;则该引用为常量&#xff0c;该值无法被修改。<2>:如果引用为引用数据类型&#xff0c;例如&#xff0c;对象/数组等…

使用Nodejs搭建简单的HTTP服务器 - 内网穿透公网远程访问

文章目录 前言1.安装Node.js环境2.创建node.js服务3. 访问node.js 服务4.内网穿透4.1 安装配置cpolar内网穿透4.2 创建隧道映射本地端口 5.固定公网地址 转载自cpolar内网穿透的文章&#xff1a;使用Nodejs搭建HTTP服务&#xff0c;并实现公网远程访问「内网穿透」 前言 Node.…

SSM 如何使用 Saga 机制实现分布式事务?

SSM 如何使用 Saga 机制实现分布式事务&#xff1f; 在分布式系统中&#xff0c;事务管理一直是一个复杂的问题。传统的 ACID 事务只适用于单体应用&#xff0c;随着微服务架构的兴起&#xff0c;分布式事务成为了必须解决的问题。Saga 是一种解决分布式事务问题的机制&#x…

聊聊API 安全

API 安全的现状 随着互联网的高速发展和技术的日趋成熟&#xff0c;人们在享受技术所带来的便利之时&#xff0c;也开始关注技术层面的安全问题。 近年来&#xff0c;应用市场成为各大互联网平台企业的最爱&#xff0c;Facebook、Twitter、新浪微博、微信公众号、抖音等均使用了…

5月面试太难,吃透这份软件测试面试笔记后,成功跳槽涨薪30K

5月开始&#xff0c;生活工作渐渐步入正轨&#xff0c;但金三银四却没有往年顺利。昨天跟一位高级架构师的前辈聊天时&#xff0c;聊到今年的面试。有两个感受&#xff0c;一个是今年面邀的次数比往年要低不少&#xff0c;再一个就是很多面试者准备明显不足。不少候选人能力其实…

ThreadLocal的应用

1. ThreadLocal 是什么 JDK 对ThreadLocal的描述为&#xff1a; 此类提供线程局部变量。这些变量与普通变量的不同之处在于&#xff0c;每个访问一个变量的线程&#xff08;通过其get或set方法&#xff09;都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有…

ReentrantLock 和 synchronized 关键字有什么区别?

ReentrantLock 和 synchronized 关键字有什么区别&#xff1f; 在 Java 中&#xff0c;有两种常用的锁机制&#xff1a;ReentrantLock 和 synchronized 关键字。它们都可以用来实现线程同步&#xff0c;但在具体的使用上有一些区别。本文将介绍 ReentrantLock 和 synchronized…

如何用Thanos 和 Prometheus 打造一个高可用的K8S监控系统

概 述 对于弹性伸缩和高可用的系统来说&#xff0c;一般有大量的指标数据需要收集和存储&#xff0c;如何为这样的系统打造一个监控方案呢&#xff1f;本文介绍了如何使用 ThanosPrometheusGrafana 构建监控系统。 集群容量概览 用户故事 直到今年 1 月&#xff0c;我一直在…

C#小项目之记事本

C#小项目之记事本 子窗体设计 frmChild.cs using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; …

第 2 章 Servlet 编程

文章目录 第 2 章 Servlet 编程2.1 Servlet 简介2.2 Servlet 基础2.2.1 用记事本写一个 Servlet2.2.2 Servlet 体系结构2.2.3 Servlet 接口2.2.4 Servlet 生命周期2.2.5 Servlet 生命周期示例 2.3 Servlet API编程常用接口和类2.3.1 GenericServlet 类2.3.2 HttpServlet类2.3.3…

使用object.defineProperty来更新数据示例

Object.defineProperty() 方法会直接在一个对象上定义一个新属性&#xff0c;或者修改一个对象的现有属性&#xff0c;并返回此对象。 Object.defineProperty&#xff08;&#xff09;可以为对象的属性添加特性&#xff0c;每一个被添加过的属性&#xff0c;都会拥有属于自己的…

公司来了个00后,真是卷死了呀,辞职信已经写好了·····

人们都说00后躺平了&#xff0c;但是有一说一&#xff0c;该卷的还是卷。这不&#xff0c;三月份春招我们公司来了个00后&#xff0c;工作没两年&#xff0c;跳槽到我们公司起薪20K&#xff0c;都快接近我了。 后来才知道人家是个卷王&#xff0c;从早干到晚就差搬张床到工位睡…

chatgpt赋能python:Python单行命令:while

Python单行命令&#xff1a;while 介绍 在Python中&#xff0c;while循环是一种重复执行代码块的结构&#xff0c;只要满足循环条件&#xff0c;就会一直循环下去。而单行命令则是指一行代码就完成了某个任务。在Python语言中&#xff0c;我们可以使用单行命令在 while循环中…

springcloud高频面试题

springcloud的组件有哪些 注册中心&#xff1a;euraka、nacos、zookeeper 注册中心及配置中心&#xff1a;nacos 远程调用&#xff1a;feign、dubbo 负载均衡&#xff1a;ribbon 服务熔断&#xff1a;hystrix、sentinel 网关&#xff1a;zuul、gateway eureka注册中心的作用 …

JAVA面试八股整理——基础部分

JAVA 基础 JVM JDK JRE JVM java虚拟机&#xff0c;针对不同的系统&#xff0c;使用相同的字节码会给出相同结果。一次编译&#xff0c;随处可运行 JDK Java SDK 提供给开发者使用&#xff0c;创建和编译Java程序。包含了JRE&#xff0c;同时包含了其它工具&#xff08;jav…

Deepin 23的最佳新功能和亮点

Deepin是一个基于Linux的操作系统&#xff0c;以其美观、简洁和易用的用户界面而闻名。Deepin 23是Deepin操作系统的最新版本&#xff0c;引入了许多令人兴奋的新功能和亮点&#xff0c;为用户提供了更好的体验和更多的功能。 本文将详细介绍Deepin 23的最佳新功能和亮点。 1…

教你彻底卸载MySQL 并重装(保姆级教程 )

前言&#xff1a;都是自己踩过的坑&#xff08;其他博主也有&#xff0c;不过我的特殊&#xff0c;按步骤走完重新安装仍要输入原密码&#xff0c;本篇主要解决和我问题类似的情况&#xff09;&#xff0c;跟着以下步骤走就行。 步骤一&#xff1a;关闭MySQL服务 右击【计算机】…

@Transactional @Aysnc 循环依赖 事务同步问题

文章目录 学习链接场景最初版本TestControllerTestService问题 Lazy版本 事务同步报错版本&#xff1a;TestServiceLazy正常启动版本&#xff08;有问题&#xff09;Lazy 注册事务同步 学习链接 Async学习及循环依赖 场景 我们要做的事情很简单&#xff1a; 现在我们需要…