类型转换

C++ 类型转换

c++ 类型转换包括隐式类型转换和显式类型转换

一、隐式类型转换

自动执行,无需显式的操作符。比如函数参数到形参的类型转换、函数返回值类型的自动转换等等

1. 数值类型转换

小类型转成大类型不会有问题。比如从小整数类型(char、short)转换到 int,或者从 float 转换到 double,这种“提升型”的转换不会造成有任何问题。但是有些大类型转成小类型就会有可能出现问题。

  • 负数转换为无符号类型,会使数值表示的意义发生改变。通常会采用二进制补码表示(编译器不警告有符号和无符号整数类型之间的隐式转换)

    1
    2
    int a = -1;
    unsigned int b = 1; // b = 2^32 - 1 = 4294967295
  • 无论是转换到 bool 类型或者是有 bool 类型进行转换。false 等价于 0(数值类型)或者空指针(指针类型);true 等价于其他任何非 0 数值。

    1
    2
    int a = -2;
    bool b = a; // 则 b = true
  • 浮点数转换为整数会采取截断操作,即移除小数部分。如果转换时发生数值溢出,可能出现未定义行为

    1
    2
    float a = -1.5f;
    int b = a; // b = -1

2. 指针类型转换

指针通常会有以下转换:

  • 空指针可以转换到任意指针类型
  • 任意指针类型都可以转换到 void* 指针
  • 继承类指针可以转换到可访问的明确的基类指针,同时不改变 const 或者 volatile 属性

二、显式类型转换

1. explicit 关键字

C++ 中 explicit 关键字修饰的类构造函数不能在隐式转换中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

class A {};

class B {
public:
explicit B (const A& x) {}
B& operator= (const A& x) {return *this;}
operator A() {return A();}
};

void fn (B x) {} // 当我们希望x只能是B类型时,我们就需要禁止隐式类型转换

int main () {
A foo;
B bar (foo); // 必须显式类型转换,不再允许B bar = foo;
bar = foo;
foo = bar;

// fn (foo); // 不允许隐式类型转换
fn (bar);
return 0;
}

2. 强制类型转换

C 风格的强制类型转换在 C++ 中是不推荐的。

2.1 static_cast
1
static_cast <new_type> (expression)

static_cast 强制转换只会在编译时检查,但没有运行时类型检查来保证转换的安全性。同时,static_cast 也不能去掉 expression 中的 const、volatile 等属性

引用场景如下:

  • 用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类指针或引用)时,由于没有动态类型检查,所以是不安全的。
  • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
  • 把void指针转换成目标类型的指针(不安全!!)
  • 把任何类型的表达式转换成void类型
  • 将enum class值转化为整数或者浮点数
  • 转换为右值引用
2.2 dynamic_cast
1
dynamic_cast <new_type> (expression)

new_type 必须是一个指针、引用。

  • 如果 new_type 是指针,则 expression 的类型必须是指针;如果转换失败,返回 NULL
  • 如果 new_type 是引用,则 expression 的类型必须为左值;如果转换失败,抛出异常

dynamic_cast 会使用运行时信息(RTTI)来进行类型安全检查,因为存在一定的效率损失

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class BaseClass {
public:
virtual void fun() {}
};

class ExtendClass : public BaseClass {
public:
int val;
};

void test_dynamic() {
BaseClass* pa = new BaseClass();
BaseClass* pb = new ExtendClass();
ExtendClass* pExtend = nullptr;

// 这里转换会失败
pExtend = dynamic_cast<ExtendClass*>(pa);
if (pExtend == nullptr) {
std::cout << "convert pa error" << std::endl;
}
pExtend = dynamic_cast<ExtendClass*>(pb);
if (pExtend == nullptr) {
std::cout << "convert pb error" << std::endl;
}

BaseClass t_a;
ExtendClass t_b;
BaseClass& ga = t_a;
BaseClass& gb = t_b;

// 这里会转换失败
try {
ExtendClass& gExtend_01 = dynamic_cast<ExtendClass&>(ga);
} catch(...) {
std::cout << "convert quote 01 failed" << std::endl;
}

try {
ExtendClass& gExtend_02 = dynamic_cast<ExtendClass&>(gb);
} catch(...) {
std::cout << "convert quote 02 failed" << std::endl;
}
std::cout << "exit" << std::endl;
}

如上例子中,BaseClass 类相比 ExtendClass 类少了一个变量的定义,因此无法完整的由 BaseClass 转到 ExtendClass。此外,dynamic_cast 只有在基类存在虚函数(虚函数表)的情况下才有可能将基类指针转换为子类。

2.3 const_cast
1
const_cast <new_type> (expression)

new_type 必须是一个指针、引用或者指向对象类型成员的指针。const_cast 用于去除 expression 的 const 或者 volatile 属性

2.4 reinterpret_cast
1
reinterpret_cast <new_type> (expression)

用来处理无关类型之间的转换;他会产生一个新的值,这个值会有与原始参数(expression)有完全相同的比特位。一般来说,reinterpret_cast 用在任意指针(或引用)类型之间的转换;以及指针与足够大的整数类型之间的转换;从整数类型(包括枚举类型)到指针类型,无视大小。(足够大的整数类型 取决于操作系统的位数,如果是 32位,就需要整形(int)以上的;如果是 64 位,则至少需要长整形(long))。如下例子:

1
2
3
4
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}

参考: