【c++笔记三】类型转换

2015年1月23日
       今天寒假第三天,程序猿第三天,减肥第三天(已经减了四斤了!!! 得意
       其实每天学的东西都挺多的,但是发现写一篇文章真的很费时间。把一个知识点说透了,就得花上三四个小时的时间。
       贵在坚持,贵在用心吧!
       今天我想讲的是—— 类型转换。
——————————————————————华丽的分割线—————————————————————————

一.什么是类型转换呢?

        其实关于类型转换我们都不陌生啦。
比如我们定义一个double a = 1.23;我们想去这个数的整数部分舍去小数部分我们经常会这样做:int b = a;现在b的值为1。
其实这里就发生了类型转换,1.23由double型变成了int型的1。
        所谓的类型转换,就是把一种数据类型变成另外一种数据类型。

二.c++中都有哪些类型转换呢?

       转换无非三种: 隐式类型转换显示类型转换不能转换。然后c++又新增了四种强制类型转换(其实属于显示类型转换)。
总结一下:
  1. 隐式类型转换
  2. 显示类型转换
  • 静态类型转换(static_cast)
  • 常量类型转换(const_cast)
  • 重解释类型转换(reinterpret_cast)
  • 动态类型转换(dynamic_cast)
      不急,我们一一为大家讲解。

三.隐式类型转换

       其实这一种类型转换在c中经常用到。所谓隐式类型转换,就是不同类型之间的转换不用做任何的声明,编译器帮我们做好了。
       请看代码:
#include <iostream>
using namespace std;
int main(){
	double pi = 3.14;
	int ip = pi;
	cout<<ip<<endl;
	char ch = 'a';
	int ic = ch;
	cout<<ic<<endl;
	return 0;
}

       由结果我们可以看到,pi(double型)赋值给ip(int)就是发生了隐式类型转换(double -> int),而我们并没有告诉编译器怎么转,一切都是编译器自动完成的。所以也算一种自动类型转换(不做解释,自行理解)。

在C语言中,自动类型转换遵循以下规则:(摘自百度百科)
1、若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
2、转换按数据长度增加的方向进行,以保证精度不降低。如int型和long型运算时,先把int量转成long型后再进行运算。
a、若两种类型的字节数不同,转换成字节数高的类型
b、若两种类型的字节数相同,且一种有符号,一种无符号,则转换成无符号类型
3、所有的浮点运算都是以双精度进行的,即使仅含float单精度量运算的表达式,也要先转换成double型,再作运算。
4、char型和short型参与运算时,必须先转换成int型。
5、在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度左边长时,将丢失一部分数据,这样会降低精度,丢失的部分直接舍去。

       但隐式类型转换有局限性,并不是所有的转换编译器都能自动完成。
       比如:
#include <iostream>
using namespace std;
int main(){
	int* a = new int(2);
	int b = a;
	return 0;
}

       编译器开始报错了,他说 从 int* 到 int的转换是无效的。所以隐式转换有局限性,在编程时要多注意隐式转换可能带来的问题(比如, 损失精度等)。

四.显示类型转换

       所谓显示类型转换,根据字面意思,我们能理解成:我把要转换成的目标类型明确显示出来,告诉编译器怎么去转换。
还是看代码吧:
#include <iostream>
using namespace std;
int main(){
	int pi = (int)3.14;
	cout<<pi<<endl;
	int ic = (int)'a';
	cout<<ic<<endl;
	return 0;
}


       当然,显示类型转换,也分两种:c风格的显示类型转换和c++风格的显示类型转换。
       有什么区别吗?
      c风格: (目标类型)源类型变量,如: int pi = (int)3.14
        c++风格:目标类型(源类型变量),如: int pi = int(3.14)
       不过就是括号加的地方不一样而已嘛。但是注意一点,c++可以使用c风格的显示类型转换,而c不能用c++的哦。
       显示类型转换都是一种强制类型转换。所谓强制,就是我一定要编译器按照我的要求来!(霸道程序猿)
       怎么个强制法?我们来写一个程序,来改动具有const属性的变量的值。(我们都知道const属性的变量值不能改变)
#include <iostream>
using namespace std;
int main(){
    const volatile int a = 0;
    int* b = (int*)&a;
    *b = 100;
    cout<<a<<endl;
    return 0;
}

        由程序可以看出来,我们巧妙的改变了一个const常属性变量a的值(volatile是为了让a具有挥发性,不懂的自行百度)。
通过显示强制类型转换,我们可以让编译器做一切我们想要的。
        强制带来的可能是无尽的BUG,一切类型转换都会带有风险的。所以c++提出了四种更加安全的显示类型转换。

五.c++新增四种显示类型转换(static、const、reinterpret、dynamic)

再谈这四种类型转换之前,我们先来思考一个问题:为什么要引入这四种转换方式?
其实我也一直在思考这个问题。
我的猜测是这样的:在程序中,难免会碰到需要数据类型进行转换的地方,那么转换的方式必不可少。以前c中的转换方式,粗鲁且让人难以理解(到底这种转换是什么样的转换,转换的方式是什么?)。c++中通过加入这四种类型转换后,完全可以替代了原来c中使用的那种强制类型转换的方式。我想这样做的目的可能有两个。一是为了转换更便于理解,比如我用const_cast转换我知道是为了去除const等(下面再讲)。二是安全性更高,c的强制转换是不加任何限制的转换,什么都能做往往容易出BUG,但是我如果用static_cast(举例说明)来进行转换,我只能对隐式类型的逆转换做转换,其他我通通做不到,是不是安全性更高?
当然,我个人理解,实践中往往很少会去用到类型转换。c++之父就说了,最好不要做类型转换,因为这一切都是你的设计不当造成的。(说的好像很有道理,但是我等菜鸟还是会用到的)

1.静态类型转换(static_cast
        能进行静态类型转换的要求是:
如果在源类型和目标类型之间有一个方向可以做隐式类型转换,那么在两个方向上都可以做静态类型转换。 如果在源类型和目标类型之间哪个方向都不能做隐式类型转换,那么在两个方向上也都不能做静态类型转换。
        说的简单点,就是能进行 隐式类型转换的两个数据类型之间才能用 静态类型转换
目的:隐式转换的 逆转换
格式就是:static_cast<目标数据类型>(源类型变量)
还是看代码来的实在:
#include <iostream>
using namespace std;
int main(){
    int* pi = new int(100);
    void* pv = pi;
    pi = pv;
    return 0;
}

既然第五行int*我能转成void*(隐式类型转换),为什么现在我再转回去就不行了呢?真是气人啊!
用上我们的static_cast看看。
pi = static_cast<int*>(pv);
程序就不会报错啦。
2.常量类型转换(const_cast)
目的:去除指针或引用上的const属性。
好好理解这一句话,我们用const_cast只能对  指针引用做操作,并且是 去除他们原有的 const属性
格式:const_cast<目标类型>(源类型变量)
还是看代码吧:
#include <iostream>
using namespace std;
int main(){
    int volatile const ivc = 100;
    int volatile const* p = &ivc;
    int* q = const_cast<int*>(p);
    *q = 200;
    cout<<ivc<<" "<<*p<<" "<<*q<<endl;
    int volatile const& r = ivc;
    int& s = const_cast<int&> (r);
    s = 300;
    cout << ivc << endl;
    return 0;
}

我们来分析一下代码:
ivc是一个具有const属性的变量,const属性指针p指向ivc的。首先我们使用const_cast去除了指针p的const属性赋给了q,q就可以随时改动ivc值。因为p和q都指向ivc,但是p具有const属性,不能修改ivc 的值。
变量r是对ivc的一个具有const属性的引用。同样的我们使用const_cast去除了引用r的const属性赋给了s,改变s就是改变ivc。因为引用r具有const属性,所以不能通过改动r去改动ivc。

3.重解释类型转换(interpret_cast
这种转换可就厉害了,功能很强大的。
目的:a.任意类型的 指针或引用之间的转换。b.任意类型的 指针和整型之间的转换
注意目的a中那个“或”字,表明使用这种转换可以将任意的指针转成任意的指针,或者任意的引用转换成任意的引用。
目的b中的那个“和”字,表明可是现实任意的指针和整型之间的转换。
格式:reinterpret_cast<目标类型>(源类型变量)
看例子:
#include <iostream>
#include <stdio.h>
using namespace std;
int main(){
    int y = 0x12345678;
    char* t = reinterpret_cast<char*> (&y);
    for (int i = 0; i < 4; ++i)
	printf ("%#x ", t[i]);
    cout << endl;
    return 0;
}

我们都知道char为一个字节,占8位。所以这个代码的功能就是将int y的32位数据拆分成了4个8位的char数据。
之所以这种转换叫重解释,意思就是为了重新解释这种数据类型。就像这个代码一样,32位数字我一样可以重新解释为4个8位的数据。
t的类型为char*,这个指针指向了y的地址,每次取8位,取出中间的4个数。(注意都是16进制数)
这里的reinterpret_cast将int*指针转换成了char*指针。这是 指针间相互转换的例子。
我们再来看一个 指针和整型之间转换的例子:
#include <iostream>
#include <stdio.h>
using namespace std;
int main(){
    int y = 0x12345678;
    char* t = reinterpret_cast<char*> (&y);
    int i = reinterpret_cast<int> (t);
    printf ("%#x %p\n", i, t);
    return 0;
}
我们都知道地址一般是用16进制表示的,你有没有想过我们怎么使用一个整型数来存放一个地址呢?
如果你直接写:int i = t;编译器肯定报错说,不能把char*转换成int
用上了reinterpret_cast之后,这个程序的功能就是把t这个char*类型的值(char数组的首地址),存放在了i这个整型数(int)中。输出结果可以表明,i的值和t的地址值一模一样。
4.动态类型转换(dynamic_cast
有静态的,就有动态的。为什么我要把它放在最后讲呢?
因为这牵涉到类的多态和继承(还没有好好复习到这里,所以一下无法组织语言说清楚)。
所以这一块,我先暂时放着,过几天再来补全~
-------------------------------------------------------------------------------------------------------------------------------------------

(未完待续。。。)









  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值