C语言中的类型转换

隐式类型转换

隐式类型转换(意义相近的类型)

1
2
3
4
int i = 1;
// 隐式类型转换(意义相近的类型)
double d = i;
printf("%d, %.2f\n", i, d);

显示类型转换

显示的强制类型转换(意义不相近的类型,值转换后有意义)

1
2
3
4
5
6
7
8
9
10
11
int main()
{
int a = 1;
int* p = &a;

//int address = p; //这样没法隐式类型转换会报错
int address = (int)p; //强制类型转换没问题

printf("%x, %d\n", p, address);
}

C++强制类型转换

c++也支持c的类型转换,但是c++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符

static_cast

这个就像c中的隐式类型转换,只不过显示的写了出来,static_cast用于非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast,但它不能用于两个不相关的类型进行转换

1
2
3
4
5
6
7
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}

reinterpret_cast

类似于c的显示强制类型的转换,一般用于将一种类型转换为另一种不同的类型

1
2
3
4
5
6
int main()
{
int a = 10;
int* p = reinterpret_cast<int*>(a); // 把int 强转为 int*
return 0;
}

const_cast

能够删除变量的const属性

1
2
3
4
5
6
7
8
9
10
11
int main()
{
const int a = 2;
//int* p = const_cast<int*>(&a);
int* p = (int*)&a; // c的那一套也可以
*p = 3;
cout << a << endl;
cout << *p << endl;

return 0;
}

但是上边的代码会出现一个问题,就是运行结果显示,a还是2,不过当我们打开监视窗口可以看到a其实已经被改成3了,这是什么原因呢?

a被const修饰,编译器以为不会被修改,所以加载到寄存器,虽然内存中已经被改了但是寄存器里面的还是2,cout的时候直接去寄存器读,所以打印出来是2

我们可以在定义a变量的时候加上 volatile关键字,这样编译器处理的时候就会去内存中读取数据,这样,运行结果就变成3 3了。

注:

  1. 兼容c隐式类型转换和强制类型转换
  2. 期望不要再用了,最好用规范的cpp显示强制类型转换
  3. static_cast(隐式类型转换)、reinterpret_cast、const_cast(强制类型转换)

dynamic_cast

用于将一个父类对象的指针或者引用转换为子类的指针或者引用(动态转换)

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

  1. dynamic_cast只能用于父类含有虚函数的类
  2. dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0
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
45
46
47
48
49
50
51
class A
{
public:
virtual void f(){}
public:
int _a = 0;
};

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

// A*指针pa有可能指向父类,有可能指向子类
void fun(A* pa)
{
// 如果pa是指向子类,那么可以转换,转换表达式返回正确的地址
// 如果pa是指向父类,那么不能转换,转换表达式返回nullptr
B* pb = dynamic_cast<B*>(pa); // 安全的
//B* pb = (B*)pa; // 不安全
if (pb)
{
cout << "转换成功" << endl;
pb->_a++;
pb->_b++;
cout << pb->_a << ":" << pb->_b << endl;
}
else
{
cout << "转换失败" << endl;
pa->_a++;
cout << pa->_a << endl;
}
}

int main()
{
A aa;
// 父类对象无论如何都是不允许转换成子类对象的
/*B bb = dynamic_cast<B>(aa);
B bb = (B)aa;*/
B bb;

fun(&aa);
fun(&bb);
//fun(nullptr); 转换失败并且报错

return 0;
}

细节部分:

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
class A1
{
public:
virtual void f(){}
public:
int _a1 = 0;
};

class A2
{
public:
virtual void f(){}
public:
int _a2 = 0;
};

class B : public A1, public A2
{
public:
int _b = 1;
};

int main()
{
B bb; //定义子类对象
A1* ptr1 = &bb;
A2* ptr2 = &bb;
cout << ptr1 << endl;
cout << ptr2 << endl << endl; //这两个地址是不同的

B* pb1 = (B*)ptr1;
B* pb2 = (B*)ptr2;
cout << pb1 << endl;
cout << pb2 << endl << endl;

B* pb3 = dynamic_cast<B*>(ptr1);
B* pb4 = dynamic_cast<B*>(ptr2);
cout << pb3 << endl;
cout << pb4 << endl << endl;

return 0;
}

可以看到,强转成子类指针,和dynamic_cast都可以将指针位置偏移到头上

总结:

  • 如果有个子类的对象,将地址传给一个函数,函数形参是父类的指针,那么函数内部可以将这个指针重新安全的转成子类的指针。
  • 普通的父类指针强转成子类可能有风险,如果父类的指针本来指向的对象就是父类的对象,那么将这个指针转换成子类,会有越界的风险。

RTTI

Run-time Type identification :运行时类型识别

c++通过以下方式支持RTTI

  1. typeid运算符 (获取对象类型的字符串)
  2. dynamic_cast运算符 (识别父类的指针是指向父类对象还是子类对象)
  3. decltype (推导一个对象类型,这个类型可以用来定义另一个对象)