0%

聊聊C++中的类型转换

在C语言中我们经常要做类型转换,例如malloc函数分配内存时需要从void *转换成你指定的类型指针。如下面这样:

1
int* block = (int*)malloc(sizeof(int));

上面的代码是将void*转换成int*,这种转换方式在C语言中称为强制转换。它的好处是简洁,灵活;缺点是需要人来决定转换后类型是否正确,因此对开发人员的要求是很高的。

C++的四种类型转换

C++觉得C的强制转换方式不是很友好,尤其是没法通过编译器或运行时检测工具来提供帮助,光靠人的能力来判断是很不靠谱的事儿。

而且相对于C来说,分析C++程序的运行轨迹要比分析C复杂得多。因此C++提出了四种新的类型转换方法,这四种类型转换方法分别是:static_castdynamic_castconst_cast以及reinterpret_cast

下面我们就来对这四种类型转换方法做下详细讨论。

static_cast

static_cast主要用于不同类型变量之间的转换及左值转右值等。比如说double转int就需要用static_cast转换。我们来举个例子:

1
2
3
4
5
6
7
//不同类型之间的转换
double d = 10.1;
int i = static_cast<int>(d);

//左值转右值
int lv = 10;
int && rv = static_cast<int&&>(lv);

这里需要注意的是:int 转 double是隐式转换,右值转左值也是隐性转换,所以对这两种情况是不需要用static_cast进行显示转换的。

上面我们说的是普通类型的转换。而对于类对象来说,static_cast不能直接将一种类对象转成另一种类对象。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class A{
public:
int a;
};

class B:public A {
public:
int b;
};

class C {
public:
int c;
};

int main(int argc, char *argv[]){
A a;
B b;
b = static_cast<B>(a); //不允许static_cast将一个对象转成另一个对象
}

像上面这种用static_cast将A类型的对象转成B类型对象是不允许的。但你可以利用static_cast将基类指针/引用转成子类指针/引用。如下所示:

1
2
3
4
5
6
7
...
A ca;
B & crb = static_cast<B&>(ca);
...
A * pa = new A();
B * cpb = static_cast<B*>(pa);
...

但这里有个前提条件,即只有有父子关系的类之间才可以做如上转换,否则编译失败。还有,虽然以上两种使用static_cast的方式都可以编译通过,但用户自己要防止越界访问的问题。

static_cast除了上面讨论的几种情况外,还有一点需要牢记,即**static_cast不允许不同类型之间指针/引用的转换(有父子关系的类对象除外)**。看个具体的例子::

1
2
3
4
5
...
double *pd = new double();
int * pi = static_cast<int*>(pd); //报错
...

上面的代码在编译时会报错,因为它不允许不同类型之间的指针或引用转换。对于有父子关系的类对象之间之所以可以转换是因为static_cast把它们当做同一类型看待了。

所以总结来说,static_cast主要用于不同类型变量之间的转换,指针和引用的转换不能用static_cast,而应该用reinterpret_cast。

reinterpret_cast

reinterpret_cast类似于C语言中不同类型指针间的类型转换,最接近于C的强制转换。举个例子:

1
2
3
4
...
double *pd = new double();
int * pi = reinterpret_cast<int*>(pd);
...

上面的代码是将double* 转成 int*。如果你使用static_cast做这种转换转换是不允许的,但改用reinterpret_cast就一切正常。 当然,如果你用reinterpret_cast做static_cast善长的变量类型转换也会报错。从上面的描述我们应该知道reinterpret_cast与static_cast之间的区别了。

如果我们像下面这样用reinterpret_cast去做类型变量的转换,编译器会报错:

1
2
3
4
...
double d = 10.1;
int i = reinterpret_cast<int>(d);
...

这样的转换是绝对不允许的。

reinterpret_cast还有一个特点,它可以将指针转成长整型,也可以将长整型转成指针。如下所示:

1
2
3
4
5
...
int a = 10;
long ll = reinterpret_cast<long>(&a);
double *dd = reinterpret_cast<double*>(ll);
...

上面是将一个int*转成long型,又将long型转成double*,这些都是reinterpret_cast善长做的转换。

reinterpret_cast对于对象指针/引用的转换与普通类型的指针/引用转换是一样的。因此不同类型的对象指针/引用可以随意转换,但转换后是否会引起问题它不关心,这要由开发人员自己保证。

1
2
3
4
5
6
7
A * pa = new A();

B b;
B & rb = B();

C * cc = reinterpret_cast<C*>(pa);
C & rcc = reinterpret_cast<C&>(rb);

总结一下,reinterpret_cast是对指针/引用的转换,其中必须至少有一个是指针或引用,否则它会报错。

const_cast

这个比较简单,它的作用是去掉指针/引用中的const限制。这里要注意的是被转换的一定是指针/引用的const,而常数的const是不能去掉的。举个例子:

1
2
3
4
5
...
const int a = 10;
int b = const_cast<int>(a);
...

上面的代码是想通过const_cast将常数的const去掉?这是决对不可以的!!!编译器一定会报错。

而如果是加了const的指针/引用就没问题了,我们再来一个列子:

1
2
3
4
5
6
...

const int * pca = new int(10);
int * pa = const_cast<int*>(pca);
...

将一个const 指针转换成非const指针正是const_cast做的事儿。

我们再来想一种case,是否可以将一种类型的const指针转换成另一种类型的非const指针呢?如下所示:

1
2
3
4
...
const int * pca = new int(10);
double * pa = const_cast<double*>(pca);
...

这样也是不允许的。对于const_cast来说,它只能将同一类型的const 指针/引用 转成非const指针/引用。

所以我们这里总结一下,const_cast是一个专门去掉同一类型的const限制的类型转换方法。它不如static_cast和reinterpret_cast应用的广泛。

dynamic_cast

这个转换方法限制比较多,一、它只能处理类对象;二、它只能处理指针;三、它只能用于将子对象转换成父对象这样的操作。我们来看一个例子:

1
2
3
4
5
...
A * a;
B * b =new B();
a = dynamic_cast<A*>(b);
...

只有上面这一种情况可以编译成功,其它情况都会失败!

小结

下面我们总结一下这四种类型转换方法。四种转换方法中,用的比较多的是static_cast和reinterpret_cast这两种转换方法。

static_cast主要用于普通类型变量的转换,如double到int的转换,或左值转右值。当然它也可以在父对象与子对象之间进行指针转换。

reinterpret_cast主要用于不同类型指针/引用间的转换。也可以将指针/引用转成长整型或将长整型转成指针类型。但不可以像static_cast一样在两个不同的类型变量间转换。也就是说reinterpret_cast在转换时必须有一个是指针/引用。

const_cast就比较简单了,它只能将同一类型的const指针转成同一类型的非const指针。

dynamic_cast只能用于有父子关系的类对象之间的转换,而且只能用于将子对象转换成父对象。

参考

C++高阶知识:深入分析移动构造函数及其原理
细说智能指针
重学C/C++中的const

欢迎关注我的其它发布渠道