c++基础——强制类型转换符

本文最后更新于:1 年前

(一)概念:

隐式类型转换是编译器自动隐式进行的,需要在代码中体现,而显式类型转换由程序员明确指定。

C++支持C风格的强制转换(将类型名作为强制类型转换运算符的做法是C语言的老式做法),但是C风格的强制转换可能带来一些隐患,让一些问题难以发现。

所以C++ 引入了四种功能不同的强制类型转换运算符以进行强制类型转换。

强制转换运算符是一种特殊的运算符,它把一种数据类型转换为另一种数据类型。强制转换运算符是一元运算符,它的优先级与其他一元运算符相同。

大多数的 C++ 编译器都支持大部分通用的强制转换运算符。

形式为:**强制类型转换运算符<type> (expression) **

type为转换后的数据类型,expression为待转换的表达式

c++的四种强制类型转换运算符分别为:static_cast、reinterpret_cast、const_cast和dynamic_cast。

(二)风险:

强制类型转换是有一定风险的,有的转换并不一定安全,如把整型数值转换成指针,把基类指针转换成派生类指针,把一种函数指针转换成另一种函数指针,把常量指针转换成非常量指针等。

C++ 引入新的强制类型转换机制,主要是为了克服C语言强制类型转换的以下三个缺点。

  • 没有从形式上体现转换功能和风险的不同。

例如,将int强制转换成double是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分。

  • 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。

  • 难以在程序中寻找到底什么地方进行了强制类型转换。

    强制类型转换是引发程序运行时错误的一个原因,因此在程序出错时,可能就会想到是不是有哪些强制类型转换出了问题。

如果采用C语言的老式做法,要在程序中找出所有进行了强制类型转换的地方,显然是很麻烦的,因为这些转换没有统一的格式。

而用 C++ 的方式,则只需要查找_cast字符串就可以了。甚至可以根据错误的类型,有针对性地专门查找某一种强制类型转换。例如,怀疑一个错误可能是由于使用了reinterpret_cast导致的,就可以只查找reinterpret_cast字符串。

(三)四种转换操作:

1. const_cast:

const_cast 运算符用于修改类型的const /volatile属性。除了const或volatile属性之外,目标类型必须与源类型相同(即type和expression结果类型相同)。这种类型的转换主要是用来操作所传对象的const属性,可以加上const属性,也可以去掉const属性(常用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;
int main() {
const string s = "Inception";
string& p = const_cast <string&> (s);
string* ps = const_cast <string*> (&s);
p += "1";
*ps += "2";
cout << typeid(p).name() << endl;
cout << typeid(ps).name() << endl;
cout << p << endl;
return 0;
}
//运行结果:
//class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >
//class std::basic_string<char, struct std::char_traits<char>, class std::allocator<char> >* __ptr64
//Inception12

2. reinterpret_cast:

reinterpret_cast 运算符用于进行各种不同类型的指针之间、不同类型的引用之间以及指针和能容纳指针的整数类型之间的转换。转换时,执行的是逐个比特复制的操作。

reinterpret_cast 功能十分强大,可以实现任意类型之间的转换。

这个转换是“最不安全”的。不推荐使用。

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
52
53
#include <iostream>
using namespace std;
class A
{
public:
int i;
int j;
A(int n1,int n2) :i(n1), j(n2) { }
};
void show1(int) { cout << "show1" << endl; return; }
int show2(int, char*) { cout << "show2" << endl; return 1; }
int main()
{
A a(100,200);

//强行让r引用a
int& r = reinterpret_cast<int&>(a);
r = 300; //把a.i变成了300
std::cout << a.i << "," << a.j << std::endl; // 输出 300,200

//强行让pa指向n
int n = 400;
A* pa = reinterpret_cast<A*> (&n);
pa->i = 500; // n变成400
//pa->j = 600; //此条语句越界操作内存,不安全,会导致程序崩溃
cout << n << endl; // 输出 500

long long la = 0x12345678abcdLL;
pa = reinterpret_cast<A*>(la); //la太长,只取低32位0x5678abcd拷贝给pa
unsigned int u = reinterpret_cast<unsigned int>(pa);//pa逐个比特拷贝到u
cout << hex << u << endl; //输出 5678abcd

typedef void (*PF1) (int);
typedef int (*PF2) (int, char*);
//两个不同类型的函数指针之间可以互相转换,但是pf1和pf2必须初始化
PF1 pf1=show1; PF2 pf2=show2;
pf1(1); //show1
cout << pf2(1, (char*)("1")) << endl; //show2,返回1
pf2 = reinterpret_cast<PF2>(pf1);
pf1(1); //show1
cout << pf2(1, (char*)("1")) << endl; //show1,返回1,(表现非常奇怪,不安全)
return 0;
}
//运行结果:
//300, 200
//500
//5678abcd
//show1
//show2
//1
//show1
//show1
//b0e74080

3. static_cast:

编译器隐式执行的任何类型转换都可以由static_cast显式完成。

static_cast主要用于基本数据类型之间的转换,如把char转换为 int,把int转换为double等。

使用static_cast可以明确告诉编译器,损失精度的转换是在知情的情况下进行的,也可以让阅读程序的其他程序员明确转换的目的而不是由于疏忽。

把精度大的类型转换为精度小的类型,static_cast使用位截断进行处理。

static_cast执行的是非动态转换,没有运行时的类型检查来保证转换的安全性。

例如,对于类层次的转换:

  • static_cast 进行上行转换是安全的,即把派生类的指针转换为基类的;

  • static_cast 进行下行转换是不安全的,即把基类的指针转换为派生类的。

使用static_cast可以找回存放在void*指针中的值。

static_cast可以把任何类型的表达式转换成void*类型。

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
#include <iostream>
using namespace std;
class A
{
public:
int i;
int j;
operator int() { return 1; }
operator char* () { return NULL; }
};
class B :public A
{
public:
int z;
};
int main()
{
A a;
int n;
char* p = (char*)"New Dragon Inn";
n = static_cast <int> (a); //调用 a.operator int,n的值变为1
cout << n << endl;
p = static_cast <char*> (a); //调用 a.operator char*,p的值变为NULL
cout << &p << endl;
n = static_cast <int> (3.14); //转换丢失精度,n的值变为3
cout << n << endl;
int x = -1;
unsigned int y = static_cast<unsigned int>(x); // 转换为无符号整数最大值,此处为4294967295
cout << y << endl;
//n = static_cast <int> (p); //编译错误,static_cast不能将指针转换成整型
//p = static_cast <char*> (n); //编译错误,static_cast 不能将整型转换成指针

double da = 2.991;
void* vp = static_cast<void*>(&da); //double转void*类型,void*指向double类型值
double* dp = static_cast<double*>(vp); //将void*类型的double值找回来
cout << *dp << endl; //输出2.991
A* pa = new A;
B* pb = new B;
pa->i = 1;
pa->j = 2;
pb->i = 10;
pb->j = 20;
pb->z = 30;
A* pab = static_cast<B*>(pb); //上行转换,派生类指针->基类指针,pab的i和j的值与pb的相同,z的值丢弃
cout << pab->i << "," << pab->j << endl;
B* pba = static_cast<B*>(pa); //下行转换,基类指针->派生类指针,不进行类型检查,不安全,i和j的值与pa的相同,但是z的值相当于未初始化
cout << pba->z << "," << pba->i << "," << pba->j << endl;
return 0;
}

4. dynamic_cast:

dynamic_cast 主要用于类层次间的上行转换或安全的下行转换(实际就是专门用于将多态基类指针或引用转换为派生类指针或引用)。在进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的,但在下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全。

对于“向下转型”有两种情况:

  • 基类指针所指对象是派生类类型的,这种情况下将其转换为派生类指针转换是安全的;

  • 基类指针所指对象为基类类型,在这种情况下dynamic_cast在运行时做检查,转换失败,返回结果为NULL指针;

dynamic_cast最特殊的地方在于它支持运行时识别指针或引用。

dynamic_cast 是在运行时执行转换,进行类型检查的。如果转换未执行,则转换失败,表达式 expr 被判定为 null。dynamic_cast 执行动态转换时,type 必须是类的指针、类的引用或者 void*,如果 type 是类指针类型,那么 expr 也必须是一个指针,如果 type 是一个引用,那么 expr 也必须是一个引用。

dynamic_cast检查的来源是虚函数表。

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <iostream>
using namespace std;

class Base {
public:
int i;
virtual void Show() {
cout << "I am Base." << endl;
}
};

class Derive : public Base {
public:
int j;
virtual void Show() {
cout << "I am Derive." << endl;
}
};

int main()
{
// 上行转换
Derive* d1 = new Derive();
cout << "d1: " << d1 << endl;
Base* b1 = dynamic_cast<Base*>(d1); //转换安全,且无影响。
cout << "b1: " << b1 << endl;

// 下行转换
//基类指针指向派生类类型进行转换
Base* b2 = new Derive;
cout << "b2: " << b2 << endl;
if (Derive* d2 = dynamic_cast<Derive*>(b2))
{
cout << "第一种情况转换成功" << endl;
cout << "d2: " << d2 << endl;
d2->Show();
}
else cout << "第一种情况转换失败" << endl;
//基类指针指向基类类型进行转换
Base* b3 = new Base;
cout << "b3: " << b3 << endl;
if (Derive* d3 = dynamic_cast<Derive*>(b3))
{
cout << "第二种情况转换成功" << endl;
d3->Show();
}
else
{
cout << "第二种情况转换失败" << endl;
cout << "d3: " << d3 << endl;
}
delete b1;
delete b2;
delete b3;

//引用在其他方面表现和指针一样,但因为没有NULL类型,不能用dynamic_cast强制转换时抛出错误。
Base bbb;
Base& bbbb = bbb;
//Derive& dddd = dynamic_cast<Derive&>(bbbb);
return 0;
}
//运行结果:
//d1 : 000002531E58F8C0
//b1 : 000002531E58F8C0
//b2 : 000002531E58F740
//第一种情况转换成功
//d2 : 000002531E58F740
//I am Derive.
//b3 : 000002531E592440
//第二种情况转换失败
//d3 : 0000000000000000

c++基础——强制类型转换符
https://github.com/xiaohei07/xiaohei07.github.io/2023/04/14/c++基础——强制类型转换符/
作者
07xiaohei
发布于
2023年4月14日
许可协议