C++强制类型转换运算符

C++强制类型转换运算符

C++强制类型转换运算符(static_cast、reinterpret_cast、const_cast和dynamic_cast)

https://en.cppreference.com/w/cpp/language/reinterpret_cast ; https://zh.cppreference.com/w/cpp/language/reinterpret_cast

https://blog.csdn.net/komtao520/article/details/79025562

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

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

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

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

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

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

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

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

C++ 强制类型转换运算符的用法如下:

1
强制类型转换运算符 <要转换到的类型> (待转换的表达式)

例如:

1
double d = static_cast <double> (3*5); //将 3*5 的值转换成实数

static_cast 静态转换

b站:shellmad-07_C++新特性 强制转换static_cast

static_cast 用于进行比较“自然”和低风险的转换,如整型和浮点型、字符型之间的互相转换。另外,如果对象所属的类重载了强制类型转换运算符 T(如 T 是 int、int* 或其他类型名),则 static_cast 也能用来进行对象到 T 类型的转换。

static_cast 不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。因为这些属于风险比较高的转换。

基本等价于隐式转换的一种类型转换运算符,可使用于需要明确隐式转换的地方。

可以用于低风险的转换。
  • 整型和浮点型
  • 字符与整型
  • 转换运算符
  • 空指针转换为任何目标类型的指针
不可以用与风险较高的转换
  • 不同类型的指针之间互相转换
  • 整型和指针之间的互相转换
  • 不同类型的引用之间的转换

举例1:

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
// int类型、float类型,都是用4个字节编码、表示的
// double类型用8个字节编码
int n = 5;
float f = 10.0f;
double dbl = 1.0;

// 本质上,发生了隐式转换
f = n;

// static_cast
f = static_cast<float>(n);

// 什么样的情况可以进行隐式转换:
// 低风险的转换:
// 整型与浮点型
n = static_cast<int>(dbl); // 8字节转4字节,精度损失


// 字符与整型
char ch = 'a'; // char类型用1个字节编码
n = static_cast<int>(ch); //精度的扩增


// void*指针的转换
void* p = nullptr;
int* pN = static_cast<int*>(p);


class CInt{
public:
operator int(){
return m_nInt;
}
int m_nInt;
}

// 转换运算符 类里面提供一种转换运算符给外界用
CInt nObj;
int k = static_cast<int>(nObj); // 把对象转换成int类型了;等价于 int k = nObj;


// 高风险的转换:不能转换的
int kk;
char* p;
// 整型与指针类型转换
p = kk; // 这种方式隐式转换不了
p = static_cast<char*>(kk); // 这样也是转换不了的,static_cast只能转换低风险

// 父类子类转换
CSon* pSon = nullptr;
CFather* pFather = nullptr;

// 父类转子类(不安全)
pSon = pFather; // 编译通过不了
pSon = static_cast<CSon*>(pFather); //不安全,没有提供运行时的检测。

// 子类转父类(安全)
pFather = pSon;
pFather = static_cast<CFather*>(pSon);

父类子类转换:

  • 父类转子类(不安全)(理解成子类内容比较多,父类转换可能会越界)隐式转换通过不了,static_cast静态转换可以通过,但是不安全,没有提供运行时的检测;
  • 子类转父类(安全)(因为子类包含父类)隐式转换可以通过。

举例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
class A
{
public:
operator int() { return 1; }
operator char*() { return NULL; }
};
int main()
{
A a;
int n;
char* p = "New Dragon Inn";
n = static_cast <int> (3.14); // n 的值变为 3
n = static_cast <int> (a); //调用 a.operator int,n 的值变为 1
p = static_cast <char*> (a); //调用 a.operator char*,p 的值变为 NULL
n = static_cast <int> (p); //编译错误,static_cast不能将指针转换成整型
p = static_cast <char*> (n); //编译错误,static_cast 不能将整型转换成指针
return 0;
}

reinterpret_cast

https://en.cppreference.com/w/cpp/language/reinterpret_cast

b站:shellmad-09_C++新特性 强制转换reinterpret_cast

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

这种转换提供了很强的灵活性,但转换的安全性只能由程序员的细心来保证了。例如,程序员执意要把一个 int* 指针、函数指针或其他类型的指针转换成 string* 类型的指针也是可以的,至于以后用转换后的指针调用 string 类的成员函数引发错误,程序员也只能自行承担查找错误的烦琐工作:(C++ 标准不允许将函数指针转换成对象指针,但有些编译器,如 Visual Studio 2010,则支持这种转换)。

比如:reinterpret_cast<void*>(decoder);reinterpret_cast<const int16_t*>(data)

  • 用于进行各种不同类型的转换
    • 不同类型指针之间
    • 不同类型引用之间
    • 指针和能容纳的整数类型之间的转换
  • 编译期处理,执行的是逐字节复制的操作
  • 类似于显式强转,后果自负

各种类型的指针转换

举例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 显示强转
int n = 1;

// C语言里的显式强转:
int* p = (int*)n;

// 整型转指针
int* p = reinterpret_cast<int*>(n);//这样可以通过,但是下面用到p时很可能会报错,因为地址1这块地址一般都是不可访问的

// 各种类型的指针转换
char*pCh = reinterpret_cast<char*>(p);

// 父类,子类指针的转换
class CFather {};
class CSon: public CFather {};

CSon* pSon;
CFather* pFather = nullptr;
pSon = reinterpret_cast<CSon*>(pFather); // 不存在检查

举例2:

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
#include <iostream>
using namespace std;
class A
{
public:
int i;
int j;
A(int n):i(n),j(n) { }
};
int main()
{
A a(100);
int &r = reinterpret_cast<int&>(a); //强行让 r 引用 a
r = 200; //把 a.i 变成了 200 ?
cout << a.i << "," << a.j << endl; // 输出 200,100
int n = 300;
A *pa = reinterpret_cast<A*> ( & n); //强行让 pa 指向 n
pa->i = 400; // n 变成 400
pa->j = 500; //此条语句不安全,很可能导致程序崩溃
cout << n << endl; // 输出 400
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 pf1; PF2 pf2;
pf2 = reinterpret_cast<PF2>(pf1); //两个不同类型的函数指针之间可以互相转换
}

//输出
200, 100
400
5678abed

第 19 行的代码不安全,因为在编译器看来,pa->j 的存放位置就是 n 后面的 4 个字节。 本条语句会向这 4 个字节中写入 500。但这 4 个字节不知道是用来存放什么的,贸然向其中写入可能会导致程序错误甚至崩溃。

上面程序中的各种转换都没有实际意义,只是为了演示 reinteipret_cast 的用法而已。在编写黑客程序、病毒或反病毒程序时,也许会用到这样怪异的转换。

reinterpret_cast体现了 C++ 语言的设计思想:用户可以做任何操作,但要为自己的行为负责。

const_cast

b站:shellmad-06_C++新特性 强制转换const_cast

const_cast 运算符仅用于进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。

const_cast中的类型必须是 指针、引用、指向对象类型成员的指针(this指针)

将 const 引用转换为同类型的非 const 引用,将 const 指针转换为同类型的非 const 指针时可以使用 const_cast 运算符。例如:

1
2
3
const string s = "Inception";
string& p = const_cast <string&> (s);
string* ps = const_cast <string*> (&s); // &s 的类型是 const string*

常量对象或是基本数据类型不允许转换为非常量对象,只能通过指针和引用来修改,可以通过const_cast转换成同类型的非const引用或指针:

1
2
3
4
5
6
7
8
9
10
const std::string s = "abc";
std::string t = const_cast<string>(s); // 错误
std::string &t = const_cast<string&>(s); // 转换成引用
t = "hello";

const int n = 5;
int k = const_cast<int>(n); // 错误
int *k = const_cast<int*>(n); // 转换成指针

*k = 6; // 转换后指针指向原来的变量

常成员函数(不能修改成员变量的值)中去除this指针的const属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CTest{
public:
CTest() : m_nTest(2) {}

// 常成员函数 // 内容不能改变
void foo(int nTest) const{
// void*p = this; // 错误 这个this类型是 const Ctest *const
// m_nTest = nTest; // 错误
const_cast<Ctest*const>(this)->m_nTest = nTest; //把this类型从const Ctest *const转换成Ctest *const
//前面的const没有了,可以改变它的内容了,不能改变地址
}

public:
int m_nTest;
}

dynamic_cast

b站:shellmad-08_C++新特性 强制转换dynamic_cast

用 reinterpret_cast 可以将多态基类(包含虚函数的基类)的指针强制转换为派生类的指针,但是这种转换不检查安全性,即不检查转换后的指针是否确实指向一个派生类对象。dynamic_cast专门用于将多态基类的指针或引用强制转换为派生类的指针或引用,而且能够检查转换的安全性。对于不安全的指针转换,转换结果返回 NULL 指针。

dynamic_cast 是通过“运行时类型检查”来保证安全性的。dynamic_cast 不能用于将非多态基类的指针或引用强制转换为派生类的指针或引用——这种转换没法保证安全性,只好用 reinterpret_cast 来完成。

用于具有虚函数的基类派生类之间的的转换。

  • 基类必须具备虚函数

    原因:dynamic_cast是运行时类型检查,需要运行时类型信息(RTTI),而这个信息是存储与类的虚函数表关系紧密,只有一个类定义了虚函数,才会有虚函数表。

  • 运行时检查,转型不成功则返回一个空指针

  • 非必要不要使用dynamic_cast,有额外的函数开销

常见的转换方式:

  • 基类指针或引用转派生类指针(必须使用dynamic_cast)
  • 派生类指针或引用转基类指针(可以使用dynamic_cast,但是更推荐使用static_cast

举例1:

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
#include <iostream>
class CFather{
public:
virtual void foo(){
std::cout << "cfather" << std::endl;
}
int m_nFather;
};

class CSon : public CFather{
public:
virtual void foo(){
std::cout << "cson" << std::endl;
}
int m_nSon;
};

int main(){
CFather f;
CSon s;
CFather* pFather = &f;
CSon* pSon = &s;

// 向下转换, 父类转子类 不安全
pSon = static_cast<CSon*>(pFather);
pSon->m_nSon = 123; //理论上是越界了
std::cout << pSon->m_nSon << std::endl; // 输出 123

// 有一种语法能检测出这种语言是不安全的 : dynamic_cast
// 在运行时检测转换是否安全,检测出被转换的指针的类型(依赖RTTI 运行时类型识别)
// 有额外的开销,一般而言只有在向下转换时才必须使用
pSon = dynamic_cast<CSon*>(pFather); // 检测到父类不可以转换成子类,变成null
pSon->m_nSon = 123; //理论上是越界了
std::cout << pSon->m_nSon << std::endl; // 输出 Segmentation fault
// 修改为:
if(pSon != nullptr){
pSon->m_nSon = 123;
}


// 向上转换, 子类转父类 安全
pFather = static_cast<CFather*>(pSon);

}

具有多态类型的向下转换时使用,其余情况可以不用。

举例2:

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
#include <iostream>
#include <string>
using namespace std;
class Base
{ //有虚函数,因此是多态基类
public:
virtual ~Base() {}
};
class Derived : public Base { };
int main()
{
Base b;
Derived d;
Derived* pd;
pd = reinterpret_cast <Derived*> (&b);
if (pd == NULL)
//此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
cout << "unsafe reinterpret_cast" << endl; //不会执行
pd = dynamic_cast <Derived*> (&b);
if (pd == NULL) //结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
cout << "unsafe dynamic_cast1" << endl; //会执行
pd = dynamic_cast <Derived*> (&d); //安全的转换
if (pd == NULL) //此处 pd 不会为 NULL
cout << "unsafe dynamic_cast2" << endl; //不会执行
return 0;
}

程序的输出结果是:
unsafe dynamic_cast1

第 20 行,通过判断 pd 的值是否为 NULL,就能知道第 19 行进行的转换是否是安全的。第 23 行同理。

如果上面的程序中出现了下面的语句:

1
Derived & r = dynamic_cast <Derived &> (b);

那该如何判断该转换是否安全呢?不存在空引用,因此不能通过返回值来判断转换是否安全。C++ 的解决办法是:dynamic_cast 在进行引用的强制转换时,如果发现转换不安全,就会拋出一个异常,通过处理异常,就能发现不安全的转换。

在visual stdio 菜单栏 项目 -> 属性 -> 左边菜单栏 C/C++ 语言 -> 启用运行时类型信息 改为 “是”