目录

C语言数据类型转换

1、自动类型转换

(1)算术表达式的自动类型转换

(2)赋值运算中的自动类型转换

2、强制类型转换

C++数据类型转换

1、static_cast

2、const_cast

3、dynamic_cast

4、reinterpret_cast


C语言数据类型转换

1、自动类型转换

(1)算术表达式的自动类型转换

在实际运算中,整型(int,short,long,char)和浮点型(float,double)是可以混合运算的,例如下面这段代码就是合法的。

10+'a'+1.5-8765.1234*'b'

只是在算术表达式进行运算的过程中,不同类型的数据要自动转换成同一类型,然后进行运算。

算术表达式中自动类型转换的具体规则:

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

横向向左的箭头表示必定的转换,如char,short型数据在运算时必定先自动类型转换为int类型(char,short自动类型转换为int也称为整型提升),float型数据在运算时一律先自动类型转换为double类型以提高运算精度。

纵向的箭头表示当运算对象为不同类型时转换的方向,类型转换方向由低到高,如某类型数据与double型数据进行运算时,该类型数据自动类型转换为double类型,然后在两个double型数据间进行运算,结果为double型。如果表达式中,类型转换的最高级别是long,那么其它类型的数据就会自动类型转换为long类型。以此类推…

注:

  • 不要错误地将该图理解为int型先转换成unsigned int型,再转换成long型,再转成double型
  • 在算术表达式中进行自动类型转换的时候,原数据类型并无发生变化,实际上是得到一个自动类型转换后的中间变量,表达式运算的过程是对转换后的中间变量进行运算,运算结果为转换后的数据类型

如图所示b+c在进行运算的时候肯定会发生自动类型转换,但是我们最终算b的大小发现它依然是2字节,这就说明原数据类型并 没有发生变化,在运算过程中发生的自动类型转换实际上得到的是一个自动类型转换后的中间变量以参加运算,我们再输出b+c 的大小发现它是4字节,这就说明运算后的结果类型就是转换后的数据类型。

接下来我们来看下面的例子。

例1:

int i;float f;double d,result;long e;result = 10 + 'a' + i * f - d / e;

10+’a’在进行运算的时候,会先将’a’自动类型转换为int类型,即97,运算结果为107。i * f在进行运算的时候,会将i与f都转换成double类型,运算结果为double类型。整数107与i*f的积相加,先将整数107转换成double类型,最终运算结果为double类型。d/e在进行运算的时候,会将变量e转换为double类型,所以d/e的结果为double类型。所以最终的结果就是double类型。

例2:

int main(){char c = 1;printf("%u\n", sizeof(c));printf("%u\n", sizeof(+c));printf("%u\n", sizeof(-c));printf("%u\n", sizeof(!c));return 0;}

在该例中,c只要参与表达式运算,就会发生自动类型转换,表达式 +c ,就会使得变量c自动类型转换为int(整型提升),所以 sizeof(+c) 是4个字节,表达式 -c 也会自动类型转换为int(整型提升),所以 sizeof(-c) 是4个字节,但是 sizeof( c) 和sizeof(!c),就是1个字节。

例3:

一道经典面试题,下列代码的输出结果是什么:

#include int i;int main(){i--;if (i > sizeof(i)){printf(">\n");}else{printf("<\n");}return 0; }

A.>

B.<

C.不输出

D.程序有问题

解析:

C语言中,0为假,非0即为真。

全局变量,没有给初始值时,编译器会默认将其初始化为0。

i的初始值为0,i–结果-1,i为整形,sizeof(i)求i类型大小是4,按照此分析来看,结果应该选择B,但是sizeof的返回值类型实际为无符号整形,因此编译器会将左侧i自动转换为无符号整形的数据。

-1的原码:10000000000000000000000000000001

-1的反码:111111111111111111111111111111111110

-1的补码:111111111111111111111111111111111111

将-1转化为无符号整型,实际上就是将它的符号位视为有效位,故此时它是一个正数,正数的补码,反码,原码相同,故此时-1的原码就是11111111111111111111111111111111

所以-1对应的无符号整形是一个非常大的数字,超过4或者8,故实际应该选择A

有符号数与无符号数其实是一种看待内存中数据的角度,并不是绝对的,就比如我们把-1看作无符号数,那么他的符号位就会被视作为有效位

整型提升的原理:

无符号整形的整形提升,高位补0

有符号整型的整形提升是按照变量的符号位来提升的,即高位补符号位。

负数的整型提升如下:

char c1 = -1;//-1是int类型的整数,存储在char类型的变量中会进行高位截断//变量c1的二进制位(补码)中只有8个比特位:1111111//因为char为有符号的char,所以整形提升的时候,高位补充符号位,即为1//提升之后的结果是:11111111111111111111111111111111

正数的整型提升如下:

char c2 = 1;//变量c2的二进制位(补码)中只有8个比特位:00000001//因为 char 为有符号的 char,所以整形提升的时候,高位补充符号位,即为0//提升之后的结果是:00000000000000000000000000000001

(2)赋值运算中的自动类型转换

C语言规定:如果赋值运算符两侧的类型不一致,但都是数值型或字符型时,在赋值时会进行自动类型转换。

赋值运算中自动类型转换的规则:

  • 将浮点型数据赋值给整型变量时,舍弃浮点数的小数部分。如这段代码:int i = 8.24;i的值为8,在内存中以整数形式存储
  • 将整型赋值给浮点型变量时,数值不变,但以浮点数的形式存储到变量中,如float f = 35;,先将35转换成35.00000,再存储到f中。double d = 35;,则将35转换成35.00000000000000,然后以双精度浮点数形式存储到d中。
  • 将一个double型数据赋值给float类型变量时,截取前面7位有效数字,存放到float变量的存储单元(32位)中,但是应注意数值范围不能溢出
  • 将一个float类型数据赋值给double类型变量时,数值不变,有效位数扩展到16位,在内存中以64位存储。
  • 将字符型数据赋值给其它整型变量时,由于字符只有1个字节,而其它整型变量至少为2个字节,因此将字符数据放到整型变量的低八位中,将其他整形变量赋值给char型变量时,只将其低8位原封不动地送到char型变量(高位截断)。

例1:

int i = 289;char c = 'a';c = i;//以%d形式输出,c的值为33,以%c形式输出,得到字符’!'(其ASCII码值为33)

例2:

int main(){unsigned char a = 200;unsigned char b = 100;unsigned char c = 0;c = a + b;printf("%d\n", c);return 0;}

c=a+b这句代码在运算的过程中,a和b会发生整型提升,但是在存储的时候,变量c还一个char类型的变量,所以存不下去整型提升后的结果,所以会发生高位截断。

例3:

int main(){char a = 0xb6;short b = 0xb600;int c = 0xb6000000;if(a==0xb6)printf("a");if(b==0xb600)printf("b");if(c==0xb6000000)printf("c");return 0;}

0xb6,0xb600是int类型的整数分别存储在char类型和short类型的变量,所以发生了截断,故变量a和变量b的值实际上并不分别等于0xb6,0xb600,所以最终输出c。

2、强制类型转换

自动类型转换常被称为隐式类型转换,强制类型转换常被称为显示类型转换,强制类型转换的一般形式为(类型标识符)(操作数)或(类型标识符)(表达式)。其功能就是把操作数或表达式结果的数据类型暂时地强制转换为圆括号()中的类型。

类型标识符两边的圆括号就是C语言中的强制类型转换符。

在进行强制类型转换时,原来变量的类型未发生变化,我们所得到的是一个所转类型的中间变量。 如:

int main(){float x;int i;x = 3.6;i = (int)x;printf("x = %f,i = %d", x, i);return 0;}

在进行强制类型转换后得到的是一个int型的中间变量,它的值等于x的整数部分,而x的类型不变,仍为float型,x的值也不变。

输出结果如下:

在了解了强制类型转换后,我们简单地看一下强制类型转换的例子:

int main(){int a = 20000000, b = 30000000, x;x = a * b;printf("%d*%d = %d", a, b, x);return 0;}

输出结果如下:

这样的输出结果显然是错误的,因为正确的结果显然超过了int的范围,发生了溢出错误,截去了高位部分,那如果我们把程序改为下面这样呢?

int main(){long long x;int a = 20000000, b = 30000000;x = a * b;printf("%d*%d = %d", a, b, x);return 0;}

运行结果依然为:

因为a和b就是int型,a*b也是int型,这时已经产生了溢出错误,所以只是把高位截断后的值赋值给了long long型的变量,然后将左边高位补零,数值大小不变。

要得到正确的结果,可以用强制类型转换把程序改为

int main(){long long x;int a = 20000000, b = 30000000;x = (long long)a * b;printf("%d*%d = %lld", a, b, x);return 0;}

这样就能得到最终的正确结果:

C++数据类型转换

C++兼容C语言的类型转换风格,同时C++提供了自己的类型转换操作符,一共有四种,如下代码所示:

static_cast(expression);const_cast(expression);dynamic_cast(expression);reinterpret_cast(expression);

下面就对这四种类型转换操作符进行总结:

1、static_cast

static_cast是最常用的类型转换操作符,它主要执行非多态的转换,用于代替C语言中通常的转换操作,但同时它还可以用于类层次结构中基类和子类之间指针或引用的转换,在进行上行转换(把子类指针或引用转换成基类类型)是安全的,但是在进行下行转换(把基类指针或引用转换成子类类型)时,由于没有动态类型检查,是不安全的。

根据代码进行分析,首先是将double类型的变量a转化为int类型,但是根据输出结果发现,变量a依然是double类型,所以和C语言数据类型转换一样,只是创建了一个转换类型后的临时变量,然后将临时变量的值赋值给指定变量,被转换的原变量本身类型不变。然后下面也进行了上行转换和下行转换,只是下行转换是没有动态类型检查的,是不安全的。

2、const_cast

const_cast在进行类型转换时用来删除类型的const或volatile属性,除了const或volatile修饰之外,原来的数据值和数据类型都是不变的,const_cast中的数据类型必须是指针或者引用。

首先将const指针p转换为非const指针然后赋值给指针变量p2,转换成功,因为如果没有将const指针p转换为非const指针,那么就是将const指针赋值给非const指针p2,这是权限的放大,编译器会报错,后面通过指针修改p所指变量的值,编译器报错,说明指针p依然是const的,只是创建了一个转换后的非const的临时变量,然后将这个非const临时变量赋值给p2,被转换的原变量本身的const和volatile属性是不变的。

3、dynamic_cast

该操作符用于运行时检查类型转换是否安全,但只在多态类型时合法,即该类型至少具有一个虚函数。它与static_cast具有相同的语法,但dynamic_cast主要用于类层次间的上行和下行转换,以及类之间的交叉转换。在类层次间进行上行转换时,它和static_cast效果是一样的,在进行下行转换时,它具有类型检查的功能,比static_cast更安全,dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回0。

4、reinterpret_cast

该操作符将一个类型(如int类型)的数据a转换为另一个类型(如double类型)的数据b时仅仅是将a的比特位复制给b,不作数据转换,也不进行类型检查。而且reinterpret_cast要转换的new_type类型必须是指针类型、引用或算术类型,例如下面的代码:

char c = 'a';int d = reinterpret_cast(c);

这个转换只是单纯的将字符c中的比特位复制给了d,并没有进行必要的分析和类型等的检查。这种转换方式一般较少使用。

本篇文章部分内容参考书籍《C语言程序设计》(刘天印 冯运仿 主编)和博客链接 https://book.itheima.net/course/223/1275663370879508481/1275663662815649798