C++类型转换


前言,C中的类型转换

在C语言中你可能会看到这样的操作,

int i = 0;
double d = 2.22;
i = d;

我们把这种操作叫做类型转换,因为变量d和i的类型不相同,当你编译时,编译器会提醒你可能会发生数据丢失,因为相比int,double是更高的精度,但仅仅是个warning,谁关心呢?但如果你写出下面这样的代码,那么编译器就不敢苟同了:

int a = 0;
int* ap = a; // int型数据给指针变量赋值 (1)

const int b = 0;
int* bp = &b; // 非const指针指向const类型数据  (2)

同样是类型转换,下面的两种为什么编译器给出了error呢?因为这是两种相差较大的类型(这种相差较大是程序员认为的,更是编译器认为的),但是在C中我们也有方法,加上强制类型转换就可以,

int a = 0;
int* ap = (int*)a; // int型数据给指针变量赋值 (1)

const int b = 0;
int* bp = (int*)&b; // 非const指针指向const类型数据  (2)

我们把这种类型转换成为强制类型转换,而最上面那种叫做隐式类型转换,在C中如果你不想类型转换报错或者警告,直接强制转换。

int i = 0;
double d = 2.22;
i = (int)d; //这行不会警告,因为我强转了

但是在C++中我们有了一套新玩法。

一、static_cast

C++中引入的每一个feature都有它的目的,引入新的类型转换目的就是为了规范代码,当遇到类型转换,每个C++程序员都这样写,就会统一起来。C++引入了四个类型转换,我们来逐一介绍。
static_cast是很简单的类型转换,对应C中的隐式类型转换,即相差不大的两种类型。

int i = 0;
double d = 2.22;
i = d;  // C写法,会警告
i = static_cast<int>(d);  //C++写法,不会警告

类型转换的用法类型生成一个匿名对象,4个类型转换的用法相同。

二、reinterpret_cast

我觉得,比起如何使用,记住并正确写出reinterpret_cast更困难一点。
reinterpret_cast为两种相差较大的类型提供桥梁,这对应了C中的强制类型转换(不包括const),而强制类型转换是C/C++非常灵活的一个栗子,怎样转换取决于程序员而非编译器,同样的,我们也应该为代码负责。

int a = 0;
int* ap1 = (int*)a;
int* ap2 = reinterpret<int*>(a);

这个栗子有点做作,因为我想不到谁会在实际中这么写。

三、const_cast

首先一点,const类型和非const类型是相差较大的类型!!这也许与我们的认知不符,但事实如此。const_cast的目的是为了去掉const的修饰,使得非const的指针或者引用可以指向const对象。

const int a = 0;
int* p = const_cast<int*>(&a); //这可以

这里还有一个题目,

const int a = 0;
int* p = const_cast<int*>(&a);
*p = 1;

cout << a << endl;
cout << *p << endl;

上面的两个输出是多少呢?都是1吗?运行过后我们发现是0和1.实际上,因为a是const类型,你已经对编译器下了保证a是不可变的,a就会被放在寄存器中,直到你的代码用到a的地址,它才会为a分配内存,而cout的输出是从寄存器里面取出数据,所以a在寄存器里面就是0,而*p是内存中的a,已经被改变,是1.

四、dynamic_cast

C语言中的类型转换都处理完毕,但是C++又新增了一个类型转换。dynamic_cast是为了处理继承中的多态问题,父类必须有虚函数,而子类可以没有虚函数。
我们都知道,子类对象,指针或者是引用可以传给父类的对象,指针或者引用,这是天经地义的(至少编译器是这样认为的),我们把这种叫做切片。那么反过来,父类的对象,指针or引用能不能用于子类的呢?
对象的拷贝显然不行。指针和引用有时候可以。像这样,

class A
{
public:
virtual void Fun()  //A类写了虚函数
{}
protected:
int _a;
};
class B : public A //B类继承A类
{
private:
int _b;
};

A a;
B b;
A* ap1 = &b; // 父类指针指向子类对象,切片,可以
B* bp1 = (B*)ap1; // ap虽然是父类指针,但是指向子类对象,赋值给子类指针可以。

A* ap2 = &a; // 父类指针指向父类对象
B* bp2 = (B*)ap2; // 此时父类指针想赋值给子类指针,不行。

dynamic_cast就是为了处理上面两种情况应运而生的类型转换,如果是第一种情况,dynamic_cast允许它发生,如果是第二种情况,dynamic_cast会返回nullptr。

void dynamic_cast_test(A* ap)
{
   B* bp = dynamic_cast<B*>(ap);
   if(bp)
   {
     cout << "传入的指针指向子类对象" << endl;
   }
   else
   {
     cout << "传入的指针指向父类对象" << endl;
   }
}

dynamic_cast_test(ap1);
dynamic_cast_test(ap2);

输出结果是第一个输出指向子类对象,第二个输出指向父类对象。
相比到这里,有一个疑惑会盘旋在各位脑海中(也许没有),那就是我一开始提出的,为什么父类一定要有虚函数呢?我们都知道,有了虚函数就会有虚表和虚表指针,编译器是如何通过dynamic_cast判断指针是指向子类还是父类呢?实际上编译器会在虚表上面的位置存入对象的类型信息,编译器就是有了虚表才能找到指针的指向对象的类型。
1

五,explicit

explicit它的名字一样,保证了构造不支持隐式类型转换,像下面这样,

class C
{
public:
   C(int a = 0)
   :_a(a)
   {}
private:
int _a;
};

C c = 1; //这实际上是隐式类型转换,编译器会
       //先用1构造一个C的临时对象,再用临时对象拷贝构造c

如果你不想让上面这种事情发生,你可以这样,

explicit  C(const int a = 0)
   :_a(a)
   {}

在C的构造函数前面加上关键字explicit。在EffectiveC++一书中,作者在一开始也提醒我们如果没有什么充分的理由不加上explicit,我们最好还是使用它。在C++11中,explicit也可以避免多参数的隐式类型转换。

class D
{
public:
explicit D(const int a = 0, const int b = 0)
          :_a(a)
          ,_b(b)
          {}
private:
     int _a;
     int _b;          
};

D d = {1,2}; // C++11的初始化列表,但是由于explicit,无法成功。

(全文完)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值