C++之类型转换

C语言中的类型转换

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

隐式类型转换和显式类型转换

1. 隐式类型转化是关联度很强, 意义相近的类型之间的转换, 编译器在编译阶段自动进行, 能转就转, 不能转就编译失败, 
2. 显式类型转化是有一定关联的类型, 需要用户自己处理.

void Test ()
{
    int i = 1;
    // 隐式类型转换
    double d = i;
    printf("%d, %.2f\n" , i, d);

    int* p = &i;
    // 显示的强制类型转换
    int address = (int) p;
    printf("%x, %d\n" , p, address);
}

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


C++强制类型转换 

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

1. static_cast

2. reinterpret_cast

3. const_cast

4. dynamic_cast 

static_cast 

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

void Test2()
{
    int a = 12;
    double b = static_cast<double>(a);//对应double b = a;C语言的隐式类型转换
    cout << b << endl;
}

 一种错误写法是这样的:

void test2()
{    
    //错误写法
    int* pa = static_cast<int*>(a);
    cout << pa << endl;
}

 

这就不属于相关类型即(隐式类型转换)的范畴, 这就要用到reinterpret_cast转换了

 reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释, 适用于不相关类型之间的转换,即重新解释一种类型的含义, 用于将一种类型转换为另一种不同的类型, 对应C语言中的显示类型转换.

void Test2()
{
    int a = 12;

    //static_cast
    double b = static_cast<double>(a);//对应double b = a;C语言的隐式类型转换
    cout << b << endl;

    //reinterpret_cast
    int* pa = reinterpret_cast<int*>(a);
    cout << pa << endl;
}

const_cast

const_cast最常用的用途就是删除变量的const属性, 方便赋值.

常用方法: 利用const_cast 转换为同类型非 const 引用或者指针

  • <>内为转换的目标,()内为要转换的值
  • const_cast只针对指针、引用、this指针
void Test4()
{
    const int a = 2;
    int* p = const_cast<int*>(&a);
    *p = 3;

    cout << a << endl;
    cout << *p << endl;

    cout << &a << endl;
    cout << p << endl;
}

这里我们将 a 的地址通过 const_cast 转换之后赋值给指针变量 p, 取消了&a的底层const属性, 然后通过p将a的值修改为3, 通过输出可以看到a输出的值是2, *p的值是3, 而p和&a实际确实是同一块地址, 为什么呢?

因为变量a在定义时被 const 修饰, 而编译器认为const修饰值不会被修改, 所以编译器会进行一些优化, 比如将a的值放入一个寄存器中, 以后每次使用 a 都直接从该寄存器中读取, 而不再从内存中读取, 提高了效率, 这就导致我们虽然通过指针变量p修改了内存中a的值, 但寄存器中保存的仍然是a修改之前的值, 所以打印出来的是 2.

要解决这个问题很简单, 我们在定义常变量 a 时使用 volatile 关键字进行修饰即可, volatile 关键字的作用是保持内存可见性, 即取消编译器的优化, 每次都从内存中读取变量的值.

void Test4()
{
    volatile const int a = 2;
    int* p = const_cast<int*>(&a);
    //等价于int* p = (int*)&a;
    *p = 3;

    cout << a << endl;
    cout << *p << endl;

    cout << (void*) & a << endl;
    cout << p << endl;
}

 这里需要注意:

再举个例子: 

void Test5()
{
    char ch = 'x';
    cout << &ch << endl;
}

void Test5()
{
    char ch = 'x';
    cout << (void*)&ch << endl;
}

 

回到主题, 之前const_cast的例子其实可以反映出为什么 C++ 要重新专门去设计一系列的类型转换, 比如const_cast 强制类型转换操作符来用于 const 类型和非const类型之前的转换, 比如这里它就提醒了程序员这里有const属性的删除, 要考虑是否需要加volatile之类的注意事项. 虽然直接把 &a 强制转换为int*也可以, 但不容易看出来问题出在哪.

dynamic_cast

之前在继承中提到过, 基类对象不能赋值给派生类对象:

向上转型: 子类对象指针/引用->父类指针/引用(不需要转换, 赋值兼容规则)
向下转型: 父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的) 

C++为我们提供了更安全的父类与子类对象之间的转换: dynamic_cast

dynamic_cast用于将一个父类对象指针/引用转换为子类对象指针/引用(动态转换), 也就是说dynamic_cast应对的是向下转型.

注意:

1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功, 能成功则转换, 不能则返回0

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

class B : public A
{};

void fun(A* pa)
{
    // dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回
    B* pb1 = static_cast<B*>(pa);
    B* pb2 = dynamic_cast<B*>(pa);
    cout << "pb1:" << pb1 << endl;
    cout << "pb2:" << pb2 << endl;
}

int main()
{
    A a;
    B b;
    fun(&a);
    cout << "------------------------------" << endl;
    fun(&b);
    return 0;
}

当fun传入&a时, dynamic_cast会检查发现pa指向的是一个A类型的对象, 所以不能转换为B类型对象的指针, 所以给pb2返回了一个0, 而static_cast则只是相近类型的类型转换,  不会进行检查.

给A和B类添加成员变量, 并且我在fun内想通过pb2去修改_a和_b:

class A
{
public:
    virtual void f() {}
    int _a = 0;
};

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

void fun(A* pa)
{
    B* pb2 = (B*)pa;
    pb2->_a++;
    pb2->_b++;
}

int main()
{
    A a;
    B b;
    fun(&a);
    fun(&b);
    return 0;
}

程序崩溃了, 因为对原本就指向A类型对象的指针强转为B类型指针, 再去访问B类型的对象, 就是越界访问了, 是错误的行为. 

我们可以用dynamic_cast这样修改: 

void fun(A* pa)
{
    B* pb = dynamic_cast<B*>(pa);
    if (pb)
    {
        pb->_a++;
        pb->_b++;
        cout << "转换成功" << endl;
    }
    else
    {
        cout << "转换错误" << endl;
    }
}


注意

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


 RTTI

RTTI: Run-time Type identification的简称, 即运行时类型识别
C++通过以下方式来支持RTTI:

  • typeid: 在运行时识别出一个对象的类型.
  • decltype: 在运行时推演出一个表达式或函数返回值的类型.
  • dynamic_cast: 在运行时识别出一个父类的指针/引用指向的是父类对象还是子类对象.

注意: C++ 中的 auto 并不属于 RTTI, auto 是一种变量类型推导机制, 它能够根据变量的初始化表达式自动推导出变量的类型, 属于编译时识别, 而 RTTI 是一种运行时类型识别机制.

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值