C++:现代类型转换

简介: C++:现代类型转换

C/C++的旧式类型转换

在C语言中,类型转换被分为显式和隐式,常见的类型转换如下:

整型之间,intlong longlongshrotchar等互相转换

整型与浮点数之间,intlong等与floatdouble之间的转换

  1. 整型与布尔值,非0为true,0为false
  2. 指针与布尔值,空指针为false,非空指针为true
  3. 整型与指针之间
  4. 不同指针之间

C++后,增加了类的概念,类型就丰富了起来,于是又多出了以下与类相关的转换:

  1. 内置类型 → 自定义类型,依赖构造函数
  2. 自定义类型 → 自定义类型,依赖构造函数
  3. 自定义类型 → 内置类型,依赖操作符重载

前两个是类中常见的,不讲解了,而C++支持通过操作符重载来进行自定义类型 → 内置类型的转换

比如:

class A
{
public:
    operator int()
    {
        return _a + _b;
    }

    operator double()
    {
        return _a / _b;
    }
private:
    int _a = 3;
    int _b = 5;
};

A支持两种类型的转换:A -> intA -> double,这是通过操作符重载实现的,operator type就可以完成到type类型的转换。注意的是,类型转换的返回值是固定的,所以operator type左侧不用写函数返回类型。

那么类A就可以完成以下转换:

A aa;
int x = aa;
double y = aa;

以上所有类型转换,都是旧式的,是基础C语言的显式与隐式的规则,衍生出来的各种类型转换。

C语言风格的类型转换很简洁,但是也有缺点:

  1. 转换的分类过于笼统,只分显式和隐式
  2. 隐式转换非常坑,很容易出bug

比如以下代码:

size_t pos = 0;
int end = 10;

while (end >= pos)
{
    cout << end << endl;
    end--;
}

这个代码,乍一看没问题,输出10 - 0的所有数字,但是其实这是一个死循环代码。

由于pos的类型是无符号整型,当endpos放在一个表达式中比较,此时end会发生类型转化,从有符号变成无符号,那么end就永远>= 0,陷入死循环了。

这个问题就是隐式类型转换导致的。


C++的现代类型转换

C++提供了四种类型转化,以更加细致安全的方式来区分各种类型转换。

静态类型转换 static_cast

用途:

在类型之间进行安全的转换

格式:

static_cast<type>(expression)

任何能够明确的类型转换都可以使用static_cast(static_cast不能转换掉底层const和volatile),以上说明中,安全的转换不是指编译器帮你确认安全,而是说程序员自己确定了这个转换是安全的之后,再用static_cast。

特点在于:不提供运行时的检查,所以叫静态类型转换

主要在以下几种场合中使用:

  • 整型浮点型的转换(原先的隐式转换):
int x = 10;
double y = static_cast<double>(x);

只要是隐式转换可以做到的,都推荐改用static_cast

  • 派生类基类的转换(上行转换):
class Base
{
    //...
};
class Derived : public Base
{ 
    //...
};

int main()
{
    Derived d;
    Base* b = static_cast<Base*>(&d);

    return 0;
}
  • void*具体类型指针的转换(指针之间的转换):
void* p = malloc(sizeof(int));
int* x = static_cast<int*>(p);

重新解释转换 reinterpret_cast

用途:

进行不安全的类型转换

格式:

reinterpret_cast<type>(expression)


这是非常激进的指针类型转换,在编译期完成,可以转换任何类型的指针,所以极不安全。

仅仅重新解释内存中的二进制表示,而不执行任何实际的类型检查,因此非常危险,不建议使用。

  • 将一个指针转换为整型:
int x = 1;
int p = reinterpret_cast<int>(&x);
  • 在不同的指针类型之间进行转换:
int d;
double* p = reinterpret_cast<double*>(&d);

常量类型转换 const_cast

用途:

移除或添加对象的const属性和volatile属性

格式:

const_cast<type>(expression)
  • 移除const属性:
const int x = 10;
int* p = const_cast<int*>(&x);
*p = 20; // 修改常量对象的值

但是以上代码暗含一个小问题,我们尝试输出一下xp

cout << x << endl;
cout << *p << endl;

输出结果:

10
20

怎么回事?我们明明通过指针修改了x,为什么x是10,而p是20?

实际上,C++的常量不存储在常量区,而是存储在栈区,而编译器会对const变量优化,把const变量放到寄存器中,后续只要访问变量a,都去寄存器中查找。我们通过指针p修改的变量,其实修改的是栈区中的数据,没有修改寄存器的数据,因此访问a还是从寄存器中读出了10。

为了解决这个问题,可以用volatile关键字修饰const变量,此时就不会把常量存在寄存器中了。

volatile const int x = 10;
int* p = const_cast<int*>(&x);
*p = 20;

cout << x << endl;
cout << *p << endl;

此时的输出结果为:

20
20

由于volatile的出现,const的种类也变多了,比如以下代码:

volatile const int x = 10;
const int* p = &x;

这是一段错误的代码,&x的类型是volatile const int*,与const int*不同,此时编译器会报错。const_cast不止可以消除const属性,也可以消除volatile属性

  • 消除volatile属性
volatile const int x = 10;
const int* p = const_cast<const int*>(&x);

动态类型转换 dynamic_cast

用途:

在继承层次结构中进行安全的向下转换,检查转换是否成功。

格式:

dynamic_cast<type>(expression)

相比static_cast,dynamic_cast会在运行时检查类型转换是否合法,具有一定的安全性。由于运行时的检查,所以会额外消耗一些性能。

dynamic_cast常用于基类与派生类之间的转换,分为两种情况:

上行转换:从 派生类指针/引用 向 基类指针/引用 的转换,本质上是赋值兼容,一般来说是安全的

下行转换:从 基类指针/引用 向 派生类指针/引用 的转换,安全性不确定

一般来说,上行转换使用static_cast即可,因为这是一个比较安全的行为。而下行转换用dynamic_cast来检测安全性。

有如下特性:

  1. 使用dynamic_cast要求基类中必须有虚函数
  2. 如果转换成功,dynamic_cast返回转换后的指针,如果转换失败,dynamic_cast返回空指针
  • 向下转换,并检查转换是否成功:
class Base
{
    virtual void foo() {}
};

class Derived : public Base
{
    /* ... */
};

int main()
{
    Base* b = new Base();
    Derived* d = new Derived();

    Base* p1 = static_cast<Base*>(d);//上行转换

    Derived* p2 = dynamic_cast<Derived*>(p1);
    Derived* p3 = dynamic_cast<Derived*>(b);

    cout << p2 << endl;
    cout << p3 << endl;

    return 0;
}

以上代码中,完成了两次下行转换,分别是p1 -> p2和d -> p3的转换。而p1是通过b转化而来的,所以p1是指向派生类的基类指针,而d是指向基类的基类指针。


因此p1转回派生类指针,是合理的,而d转为派生类指针,是危险的,于是dynamic_cast会阻止d转为派生类指针。

输出结果:

0000015220F64100
0000000000000000
相关文章
|
6月前
|
安全 编译器 程序员
【C++】C++的类型转换
【C++】C++的类型转换
|
6月前
|
设计模式 安全 算法
【C/C++ 类型转换 】深入理解C++向上转型:从基础到应用
【C/C++ 类型转换 】深入理解C++向上转型:从基础到应用
218 0
|
7天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
30 5
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
20 1
|
1月前
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
31 3
|
1月前
|
编译器 C++
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
47 3
|
1月前
|
C++
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
C++入门4——类与对象3-2(构造函数的类型转换和友元详解)
21 0
|
4月前
|
存储 安全 编译器
【C++11】类型转换
【C++11】类型转换
29 0
|
4月前
|
安全 程序员 编译器
C++一分钟之-C++中的类型转换
【7月更文挑战第8天】C++中的类型转换涉及隐式和显式操作,隐式转换如从`int`到`double`是自动的,但可能导致数据丢失。显式转换包括`static_cast`, `dynamic_cast`, `const_cast`, `reinterpret_cast`,以及转换构造函数。要避免数据丢失、类型不匹配和运行时错误,需谨慎使用显式转换并检查结果。过度使用`reinterpret_cast`应避免。理解这些转换有助于编写更安全的代码。
43 0
|
6月前
|
安全 程序员 C语言
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(下)
从C语言到C++_37(特殊类设计和C++类型转换)单例模式
56 5