C++ 的数据类型转换
C 与 C++ 的强制类型转换?
- 将类型名作为强制类型转换运算符的做法是 c 的老式做法,c++ 为保持兼容而予以保留
- C++ 引入新的强制类型转换机制,克服 C 语言的缺点:(1)转换太过随意,可以在任意类型之间转换;(2)没有统一的关键字和标示符。对于大型系统,做代码排查时容易遗漏和忽略
C++ 上的隐式类型转换
- 定义―自动执行的,无需显式的操作符,比如函数实参到形参的类型转换、函数返回值类型的自动转换等等
- 种类
- 数值类型转换
- 负数转化为无符号类型,通常会采用二进制补码表示
- 数值类型转 bool 类型,false 等价于 0 (数值类型) 或者空指针 (指针类型); true 则等价于其它任何数值或者由 true 转化为 1
- 浮点数转化为整数会采取截断操作,即移除小数部分。如果转换时发生了数值溢出,可能出现未定义的行为
- 指针类型转换
- 空指针可以转换到任意指针类型
- 任意指针类型都可以转换到 void* 指针
- 继承类指针可以转换到可访问的明确的基类指针, 同时不改变 const 或者 volatile 属性
- 一个 C 风格的数组隐式把数组的第一个元素转换为一个指针。 虽然此方法很方便,但它也有潜在的错误
- 数值类型转换
C++ 上的显式类型转换
- C 风格的强制类型转换:
1
2
3// 不推荐使用
(int) x;
int(x); - 声明为 explicit 的构造函数不能在隐式转换中使用
- C++ 上的 4 种强制显式类型转换
C++ 上强制类型转换中的 explicit 关键字?
- 可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生。即声明为 explicit 的构造函数不能在隐式转换中使用
- 使用例子
- 不使用 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// 类的通过构造函数的隐式转换:
using namespace std;
class A {};
class B {
public:
// conversion from A (constructor):
B (const A& x) {}
// conversion from A (assignment):
B& operator= (const A& x) {return *this;}
// conversion to A (type-cast operator)
operator A() {return A();}
};
int main ()
{
A foo;
B bar = foo; // 调用构造函数实现隐式类型转换
bar = foo; // calls assignment
foo = bar; // calls type-cast operator,相当于 foo = A(bar);
return 0;
} - 使用 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
25
26
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;
}
- 不使用 explicit
C++ 上的 4 种强制显式类型转换?
- static_cast (静态类型转换)
- 进行比较 “自然” 和低风险的转换
- 使用场景
- 用于类层次结构中基类和派生类之间指针或引用的转换
- 用于基本数据类型之间的转换,如把 int 转换为 char,安全性问题由开发者来保证
- 把空指针转换成目标类型的空指针
- 把任何类型的表达式转为 void 类型
- 特点
- 主要执行非多态的转换操作,用于代替 C 中通常的转换操作
- 隐式转换都建议使用 static_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
using namespace std;
class A
{
public:
operator int() { return 1; }
operator char*() { return NULL; }
};
int main()
{
A a;
int n;
double dpi = 3.1415926;
char* p = "New Dragon Inn";
n = (int)dpi; // C语言写法
n = static_cast (dpi); // n 的值变为 3
n = static_cast (a); // 调用 a.operator int,n 的值变为 1
p = static_cast (a); // 调用 a.operator char*,p 的值变为 NULL
n = static_cast (p); // 编译错误,static_cast不能将指针转换成整型
p = static_cast (n); // 编译错误,static_cast 不能将整型转换成指针
return 0;
}
- dynamic_cast (动态类型转换)
- 使用场景
- 只有在派生类之间转换时才使用 dynamic_cast
- 使用特点
- 基类必须要有虚函数,因为 dynamic_cast 是运行时类型检查,需要运行时类型信息,而这个信息是存储在类的虚函数表中,只有一个类定义了虚函数,才会有虚函数表(如果一个类没有虚函数,那么一般意义上,这个类的设计者也不想它成为一个基类)
- 对于下行转换,dynamic_cast 是安全的(当类型不一致时,转换过来的是空指针),而 static_cast 是不安全的(当类型不一致时,转换过来的是错误意义的指针,可能造成踩内存,非法访问等各种问题)
- 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
using namespace std;
class Base
{
public:
virtual ~Base() {} //有虚函数,因此是多态基类
};
class Derived : public Base { };
int main()
{
Base b;
Derived d;
Derived* pd;
pd = reinterpret_cast (&b); // 此处pd不会为 NULL。reinterpret_cast不检查安全性,总是进行转换
if (pd == NULL)
cout << "unsafe reinterpret_cast" << endl; // 不会执行
pd = dynamic_cast (&b); // 结果会是NULL,因为 &b 不指向派生类对象,此转换不安全
if (pd == NULL)
cout << "unsafe dynamic_cast1" << endl; // 会执行
pd = dynamic_cast (&d); // 安全的转换,此处 pd 不会为 NULL
if (pd == NULL)
cout << "unsafe dynamic_cast2" << endl; // 不会执行
return 0;
}
- 使用场景
- const_cast (去只读属性转换)
- 使用场景
- 常量指针转换为非常量指针,并且仍然指向原来的对象
- 常量引用被转换为非常量引用,并且仍然指向原来的对象
- 使用特点
- 四种类型转换符中唯一可以对常量进行操作的转换符
- 去除常量性是一个危险的动作,尽量避免使用
- 使用例子
1
2
3const string s = "Inception";
string& p = const_cast (s);
string* ps = const_cast (&s); // &s 的类型是 const string*
- 使用场景
- 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
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(a); // 强行让 r 引用 a
r = 200; // 把 a.i 变成了 200
cout << a.i << "," << a.j << endl; // 输出 200,100
int n = 300;
A *pa = reinterpret_cast (&n); // 强行让 pa 指向 n
pa->i = 400; // n 变成 400
pa->j = 500; // 此条语句不安全,很可能导致程序崩溃
cout << n << endl; // 输出 400
long long la = 0x12345678abcdLL;
pa = reinterpret_cast(la); // la太长,只取低32位0x5678abcd拷贝给pa
unsigned int u = reinterpret_cast(pa); // pa逐个比特拷贝到u
cout << hex << u << endl; // 输出 5678abcd
typedef void (* PF1) (int);
typedef int (* PF2) (int,char *);
PF1 pf1;
PF2 pf2;
pf2 = reinterpret_cast(pf1); // 两个不同类型的函数指针之间可以互相转换
}
- 使用场景