C++ 教程 | C++ 类型转换
C++强制类型转换:
在C++语言中新增了四个关键字 static_cast
, const_cast
, reinterpret_cast
和 dynamic_cast
.
这四个关键字都是用于强制类型转换的。
新类型的强制转换可以提供更好的控制强制转换过程,允许控制各种不同种类的强制转换。
C++中风格是 static_cast<type>(content)
。
C++ 风格的强制转换其他的好处是,它们能更清晰的表明它们要干什么。程序员只要扫一眼这样的代码, 就能立即知道一个强制转换的目的。
static_cast
在 C++ 语言中 static_cast
用于数据类型的强制转换,强制将一种数据类型转换为另一种数据类型。例如将整型数据转换为浮点型数据。
1
2
3
int a{10};
int b{5};
double c = static_cast<double>(a)/ static_cast<double>(b);
用法:
static_cast<类型说明符>(变量或表达式)
- 用于类层次结构中
基类
和派生类
之间指针或引用的转换:- 进行上行转换(
把派生类的指针或引用转换成基类表示
)是安全的 - 进行下行转换(
把基类的指针或引用转换为派生类表示
),由于没有动态类型检查,所以是不安全的
- 进行上行转换(
- 用于基本数据类型之间的转换,如把int转换成char。这种转换的安全也要开发人员来保证:
- 把空指针转换成目标类型的空指针
- 把任何类型的表达式转换为void类型
注意:
- static_cast 不能转换掉expression的
const
、volitale
或者__unaligned
属性。 - 如果涉及到类的话, static_cast 只能在有相互联系的类型中进行相互转换,不一定包含虚函数。
const_cast
const
限定符通常被用来限定变量,用于表示该变量的值不能被修改。
const_cast
用于强制去掉这种不能被修改的常数特性,但需要特别注意的是 const_cast
不是用于去除变量的常量性,而是去除指向常数对象的指针或引用的常量性,其去除常量性的对象必须为指针或引用。
用法:
const_cast<type_id>(expression)
- 该运算符用来修改类型的
const
或volatile
属性。除了const
或volatile
修饰之外,type_id和expression的类型是一样的。 - 常量指针被转化成非常量指针,并且仍然指向原来的对象;
- 常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。
一个错误的例子
1
2
3
4
5
const int a = 10;
const int * p = &a;
*p = 20; //compile error
int b = const_cast<int>(a); //compile error
- 第一个编译错误是*p因为具有常量性,其值是不能被修改的;
- 另一处错误是const_cast强制转换对象必须为指针或引用,而例3中为一个变量,这是不允许的!
const_cast 使用案例
1
2
3
4
5
6
7
8
9
10
const int a = 10;
const int * p = &a;
int *q;
q = const_cast<int *>(p);
*q = 20; //fine
### result :
# a = 10, &a = 002CFAF4
# *p = 20, p = 002CFAF4
# *q = 20, q = 002CFAF4
查看运行结果,问题来了,p
和q
都是指向a
变量的,指向地址相同,而且经过调试发现002CFAF4
地址内的值确实由10
被修改成了20
,这是怎么一回事呢?为什么a的值打印出来还是10
呢?
其实这是一件好事,我们要庆幸a变量最终的值没有变成20!变量a一开始就被声明为一个常量变量,不管后面的程序怎么处理,它就是一个常量,就是不会变化的。
试想一下如果这个变量a最终变成了20会有什么后果呢?对于这些简短的程序而言,如果最后a变成了20,我们会一眼看出是q指针修改了,但是一旦一个项目工程非常庞大的时候,在程序某个地方出现了一个q这样的指针,它可以修改常量a,这是一件很可怕的事情的,可以说是一个程序的漏洞,毕竟将变量a声明为常量就是不希望修改它,如果后面能修改,这就太恐怖了。
上面的我们称*q=20
语句为未定义行为语句,所谓的未定义行为是指在标准的C++规范中并没有明确规定这种语句的具体行为,该语句的具体行为由编译器来自行决定如何处理。对于这种未定义行为的语句我们应该尽量予以避免!
可以看出我们是不想修改变量a的值的,既然如此,定义一个 const_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
#include<iostream>
using namespace std;
const int* Search(const int* a, int n, int val);
int main()
{
int a[10] = {0,1,2,3,4,5,6,7,8,9};
int val = 5;
int *p;
p = const_cast<int *>(Search(a, 10, val));
if (p == NULL)
cout<< "Not found the val in array a" <<endl;
else
cout<< "hvae found the val in array a and the val = " <<*p<<endl;
return 0;
}
const int * Search(const int * a, int n, int val)
{
int i;
for (i=0; i<n; i++)
{
if (a[i] == val)
return &a[i];
}
return NULL;
}
- 用于在a数组中寻找val值,如果找到了就返回该值的地址,如果没有找到则返回NULL。
- 函数Search返回值是const指针,当我们在a数组中找到了val值的时候,我们会返回val的地址,最关键的是a数组在main函数中并不是const,因此即使我们去掉返回值的常量性有可能会造成a数组被修改,但是这也依然是安全的。
reinterpret_cast
reinterpret_cast
主要有三种强制转换用途:改变指针
或引用
的类型、将指针
或引用
转换为一个足够长度的整形
、将整型
转换为指针
或引用
类型。
用法:
reinterpret_cast<type_id>(expression)
type_id
必须是一个指针
、引用
、算术类型
、函数指针
或者成员指针
。- 它可以把
一个指针
转换成一个整数
,也可以把一个整数
转换成一个指针
(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。 - 在使用
reinterpret_cast
强制转换过程仅仅只是比特位的拷贝,因此在使用过程中需要特别谨慎!
1
2
int *a = new int;
double *d = reinterpret_cast<double *>(a);
dynamic_cast
用法:
dynamic_cast<type_id>(expression)
- 其他三种都是编译时完成的,
dynamic_cast
是运行时处理的,运行时要进行类型检查。 - 不能用于内置的基本数据类型的强制转换。
dynamic_cast
转换如果成功的话返回的是指向类的指针或引用
,转换失败的话则会返回NULL。- 使用
dynamic_cast
进行转换的,基类中一定要有虚函数,否则编译不通过。 - 在类的转换时,在类层次间进行上行转换时,
dynamic_cast
和static_cast
的效果是一样的。在进行下行转换时,dynamic_cast
具有类型检查的功能,比static_cast
更安全。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Base{
public:
void m(){
std::cout << "m" << std::endl;
}
};
class Derived : public Base{
public:
void f(){
std::cout << "f" << std::endl;
}
};
int main(int argc, char*argv[])
{
Derived *d = static_cast<Derived *>(new Base()); // 存在风险
d->f();
return 0;
}
- 对于指针类型,如果转换失败了,则返回0;
- 对于引用类型,如果失败了,则抛出
std::bad_cast
异常,因为不允许存在空的引用。
1
2
3
4
5
6
7
Base *d = dynamic_cast<Base *>(new Derived());
if(d) {
// 转换成功
}else{
// 转换失败
}