const int是什么类型_C++的类型转换可能没有那么简单 | 第94期

开发C++程序的朋友都接触过类型转换,即使你没有明确写出来。隐式转换是一种编译器规定的语言标准的类型转换。此外,从C语言过渡到C++的朋友,一定忘不了强制类型转换。但是在C++中,并不建议使用C语言的那种强制转换,而最好采用C++提供的这一套接口。很多开发者都觉得类型转换不屑一顾,但事实往往并非如此。

1 隐式转换

隐式转换大概是C++中最常见的类型转换,不过正因为是编译器自动完成,所以很多人没有意识到这是一种类型转换。比如下面的这个例子就是赋值操作的隐式转换。

int p = 1.2;

这行代码表示将一个 double 类型的数字赋值给一个 int 类型的数字,熟悉内存机制的朋友很明显就知道,这两种数据类型的存储方式不一样,但程序正常运行了,所以这中间必定存在这一种隐式的转换。下面的例子与上面的稍有不同:

double q = 1 + 1.2;

当我们把一个 int 类型的数字与一个 double 类型的数字相加的时候,就应该要意识到,这里也是肯定有类型转换的。 此外,如果函数返回值的类型与声明的类型不一致时,也是会进行隐式转换的, 比如:

int func() {return 1.2;}

当然,你还能总结出其他各种各样的隐式转换,但其本质上都是一样,即实际类型与规则要求的类型不一致。 那么,我把一个结构体直接赋值给一个 int 类型,会有隐式转化吗?答案显然是否定的。所以,这种隐式转换毕竟还是有一些规则的:

规则1: 范围小的向范围大的转换

规则2: 子类对象向父类对象转换

e1df10e590488739e0140235f1a7a5f2.png

隐式转换

2 隐式转换的弊端

上面的隐式转换能够让开发者省去很多的麻烦,但并非所有的隐式转换都那么令人愉悦的。比如下面这种情况

class str {public:    str(const char * p);    str(const int n);};

本来设计这两个构造函数分别用于从C风格字符串创建对象,以及初始化指定长度的字符串。但是下面这种诡异的代码也能正常运行:

str s1 = 3;str s2 = 'g';

这并不符合设计初衷,也没有正面意义,因此常常需要禁止这种隐式转换。解决办法就是给类的构造函数添加修饰词 explicit,比如:

class str {public:    str(const char * p);    explicit str(const int n);};

3 C风格强制类型转换

在C语言中常常是需要使用强制数据类型转换的,比如在申请内存时,得到的是一个 void * 类型,这就需要强制转换成所需要的类型。C++是C语言的超集,因此这些C风格的强制数据类型转换也可以在C++中使用, 比如

int x = (int)(1.2+2.3);

按规则来说,其实没必要强制数据类型转换,但是这么做了也没错,而且让代码的逻辑更加清晰了。但是不得不说,在C++中并不建议使用C风格的强制类型转换。理由是 C风格强制类型转换路子太野,又没有安全机制,很容易干傻事还不自知。 而C++为强制类型转换专门提供了一组函数,没错,建议就是使用这组函数完成强制数据类型转换。

4 C++风格的强制类型转换

C++提供了4个强制类型转换的函数,分别是 static_cast、dynamic_cast、reinterpret_cast以及const_cast。下面就好好讨论一下这组函数接口。

55a4a8c3156fa6f8a337c17a0354da25.png

强制转换接口

(1) static_cast

你基本可以理解 static_cast 就是等同于C风格的强制类型转换。 通常用于将 void * 转换为其他数据类型的指针,或者其他任何可以进行的强制转换。下面的这个简单例子可以说明其用法:

int x = static_cast(1.2 + 2.3);

static_cast 是一个模版函数,所以需要在调用时指定类型以进行特化,然后才能使用。需要注意的是,static_cast没有安全检查机制,也不会改变const 或者 volatile 属性。所以,能不用就不用,能少用就少用,万不得已才使用。

(2) dynamic_cast

dynamic_cast 用于在继承体系之上来进行类型转换, 那么你猜猜下面的这段代码会有什么表现呢?

class A {public:    void func(){}};class B : public A {public:    void func2() {}};void dyn_cast_test() {    A * a = new A;    B * b = dynamic_cast(a);}

上面这段代码确实是在继承体系之上的,但很可惜,编译还是报错的,错误信息如下:

error: 'A' is not polymorphic

到这里我们才明白,原来 dynamic_cast 不仅要求继承体系,还要求是具有多态特性的继承体系。 那么该怎么办呢?那就给 class A 中增加个虚函数试试,代码如下:

class A {public:    virtual void func(){}};class B : public A {public:    void func2() {}};void dyn_cast_test() {    A * a = new A;    B * b = dynamic_cast(a);    cout << b << endl;}

编译了一下,没报错,通过了,说明语法上是没问题的。接下来运行一下试试,发现输出结果居然是 0x0,这就诡异了,为什么转换完成之后指针变成空指针了?

我们再来梳理一下当前的转换逻辑发现,原来我们是要把父类对象强制转换为子类对象,如果转换之后的子类对象调用了父类没有的成员,那岂不是要程序崩溃了!所以,从这个角度解释,一个安全的强制类型转换接口就必须阻止这种转换。既然如此,那么这个 dynamic_cast 还有什么用?

先别着急,我们来看看下面这段代码:

void dyn_func(A * a) {    B * b = dynamic_cast(a);    cout << b << endl;}void dyn_cast_test() {    B * bb = new B;    dyn_func(bb);}

编译运行发现这段代码转换之后的指针不为空。这时,如果我们利用转换完成之后的指针调用 class B 中的 func2() 函数就合情合理了。我们可以总结一下 dynamic_cast 的应用场合了。如果你在使用一个第三方库,有一个新的功能需要添加,但不能修改其源代码,这时你只有继承其中的类,然后实现自己的功能。而调用接口函数早就写好了,所以你把子类对象传给了父类对象的指针,那么就需要在这个调用函数中将父类对象再次转换成子类对象,然后调用新增加的功能函数。

总而言之,使用 dynamic_cast 一定要在逻辑上能够转换,这就需要只能往原始对象的自己这这一类型或者前辈类型转换,而不能向其子类型转换。建议使用时对转换结果进行判断,以避免风险。

(3) reinterpret_cast

reinterpret_cast 可以把一个指针转换成整数,也可以把整数转换成指针。虽然听起来很强大,但是建议不要使用这个转换函数。因为这个函数在不同的平台、不同编译器实现得不一样,可能出现一些匪夷所思的错误。

(4) const_cast

const_cast 看名字就知道其功能是移除变量的一些特殊属性,比如 const、volatile。不过,这里可有一个大坑,请先看以下代码

const int x = 12;int * p = const_cast(&x);*p = 10; cout << x << ", " << (*p) << endl;

大家可以猜猜看,这段代码按照逻辑应该输出什么?很遗憾地告诉大家,结果与你预想的不一致,真实输出是 12, 10。你会发现原始变量 x 的内容并没有被修改,const_cast开辟了一个新的存储空间,然后返回其指针。虽然不是很合逻辑,但是我们只能接受了。那么这个 const_cast 该怎样使用呢?

const int * xx = new int(12);int * pp = const_cast(xx);// 编译报错:ead-only variable is not assignable// *xx = 1; *pp = 10;cout << *xx << ", " << *pp << endl;

以上这段代码中,如果直接修改 指针变量 xx 指向的内容就会报错,这是合情合理的。如果通过 const_cast 将 const指针变量转换为非const 指针变量,那么输出结果会是什么呢?答案是:10, 10。也就是说 const_cast 在处理 const指针变量 的时候才能按照预期工作。 那么还能处理别的情况吗?请看下面这段代码

const C c1;C & c2 = const_cast(c1);c2.x = 2;cout << c1.x << "," << c2.x << endl;

大家可以猜猜这段代码会输出什么,这里可以直接说出答案:0,2。同样是转换没有按照预期进行,如果我们想针对引用使用 const_cast 该怎样操作呢?

const C & c3 = C();C & c4 = const_cast(c3);c4.x = 2;cout << c3.x << "," << c4.x << endl;

这段代码的输出为 2,2,表明按照预期修改了 const引用 为 非const引用。看到这里,大家可能有些疑惑,到底 const_cast 在哪些情况下能够处理成功呢?

这就需要好好想一想我们需要使用 const_cast 的动机了。通常,我们需要对一个只读对象进行写操作,才需要使用 const_cast。为了避免产生不必要的副本,应当使用指针或者引用来指向这个对象,因此 const_cast 的操作对象就是指针变量和引用变量。

到此,关于C++中的数据类型转换就全都讨论完了。大多数的规则都是可以通过逻辑推导出来,但是还是需要记住,因为在使用时可能并没有那么好的心境来想清楚这其中的逻辑。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值