第二章:数据类型
第八节 强制转换
所谓强制转换,就是对一个数据类型明确的数据进行重新说明,例如有一个int类型的数据a,我们在某个运算或者其他数据处理的时候,希望将这个int类型的数据当作float类型的浮点数据来处理,此时,我们就需要对这个a进行重新说明,叫做强制转换。如下例:
int a;
float f;
a=100;
printf("我们把a看作浮点数进行显示:%f\n",(float)a);
或者
f=100.1+(float)a;
从上面的代码可以看出,所谓的强制转换或者重新说明数据类型,其实现的格式就是在数据的前添加新的数据类型,然后用括号括起来。经过这么一次临时说明,编译器在当前的这个语句内,就会认为这个数据或者变量的数据类型就是新的数据类型。例如在上述printf语句中,变量本来是int类型,但是在a前面添加了(float)后,编译器就会在printf语句内,认为a是一个浮点数(但是在其他没有对a进行强制转换的地方,a依然是int数据类型)。
这么总结一下,一个变量的数据类型,其默认数据类型是这个变量定义的时候给定的数据类型,但在使用的时候,可以临时把这个变量看成其他数据类型,其方法就是强制转换,这个转换是临时的,只在转换时有效,其他没有强制转换的地方,变量保持其默认数据类型。
变量如此,常量呢?编译器一般会根据常量的形式,确定常量的数据类型,例如"This is a string",只可能是一个字符串。但10可能是整数,也可能是浮点数,一般来说编译器能够根据上线文和常量的格式,来确定常量的数据类型。如果不能确定,建议程序员对常量数据类型进行说明,就是在常量前面,用括号添加对应的数据类型。例如:
float f = (float)10;
指针类型的强制转换规则:
转换规则比较简单,指针类型的变量被强制转换后,指针变量对应的内存地址的值不会改变,但指针指向的内存地址中,保存的二进制串代表的事物或者含义会被改变。如下举例:
/*******冒牌程序员-毛哥 c-pointer.c ******/
#include<stdio.h>
int main()
{
float a = 100;//这里编译器可以根据上下文,判断出100就是一个浮点数。
unsigned int *p;
float *fp;
fp=&a; //这里不需要强制转换,因为fp是float*类型,&也是float*类型。
p = (unsigned int *)&a;
/*
&a是一个指针,其类型是float*,而p的类型是(unsigned int*)类型。
其实这里对于编译器来说,也不难推断出需要将&a转换成unsigned int*类型。
(其实也不用转换,直接将&a的值,赋值给p就可以了)。但是,如果没有这个转换
说明,编译器会报错或者报警告(大家可以测试下)。编译器为啥要多此一举呢?
这是编译器为你好,因为这种强制一般来说非常危险(还记得我说的,C语言的这种
转换是被诟病的地方吗?)。搞不好程序就会出问题,所以编译器会提醒你,你真的
要这么做吗?如果你确认要这么做,请明确告诉编译器。
*/
printf("unsigned int = %#X\n",*p);
/*
本来&a这个地址内保存的是一个float类型的浮点数,但在上面一句printf语句中,
这个内存地址内的二进制串被解释成了无符号整数unsigned int。前面我们在研究
数据类型的时候,就使用了这个强制转换。
*/
return 0;
}
总结:指针类型强制转换,指针对应的地址值不会改变,改变的是对该地址内二进制串的含义,或者说解释方法。
非指针类型的强制转换
我们还是用程序说话
/***********冒牌程序员-毛哥 coercion.c */
#include<stdio.h>
int main()
{
/*无符号整数之间的转换*/
unsigned char a;
unsigned long b;
a=100;
b=a; //这里不进行强制转换也没有警告
printf("b = %#lx a=%#hhx\n",b,a);
/*
可以看出值没变,我们用gdb看一下,a变量内存地址和b内存地址比较一下
(gdb) x/1xb &a
0x7fffffffe037: 0x64
(gdb) x/1xg &b
0x7fffffffe038: 0x0000000000000064
可以看出,转换的规则,就是高位用0补齐,其他不变。这就是小字节宽度的无符号整数
到大宽度的无符号整数的转换规则(所谓字节宽度指的是这个类型所占的字节数)。
*/
b=0x12345678;
a=b; //这里同样没有警告
printf("b = %#lx a=%#hhx\n",b,a);
/*
同样,用gdb看一下两个变量的内存地址中的二进制串:
(gdb) x/1xb &a
0x7fffffffe037: 0x78
(gdb) x/1xg &b
0x7fffffffe038: 0x0000000012345678
可以看出,大字节宽度的无符号整数往小字节宽度转换的时候,就是把高位扔掉,保留低位数据。
*/
/*
有符号数之间的转换
*/
char a1;
long b1;
a1=100;
b1=a1;
/*
程序暂停到下一句,看看,a的二进制串和b的二进制串
(gdb) x/1tb &a1
0x7fffffffe02f: 01100100
(gdb) x/1tg &b1
0x7fffffffe038: 0000000000000000000000000000000000000000000000000000000001100100
可以看出,a1这个有符号数的符号位是0,根据观察结果,小字节类型到大字节类型转换,高位用0补齐。
*/
a1=-100;
b1=a1;
/*
程序暂停到下一句,看看,a的二进制串和b的二进制串
(gdb) x/1tb &a1
0x7fffffffe02f: 10011100
(gdb) x/1tg &b1
0x7fffffffe038: 1111111111111111111111111111111111111111111111111111111110011100
可以看出,a1这个有符号数的符号位是1,根据观察结果,小字节类型到大字节类型转换,高位用1补齐。
总结,有符号数,小字节往大字节转换,用符号位填充多出来的高位。
*/
b1=-1000000035L;
a1=b1;
/*
程序暂停到下一句,看看,a的二进制串和b的二进制串
(gdb) x/1tb &a1
0x7fffffffe02f: 11011101
(gdb) x/1tg &b1
0x7fffffffe038: 1111111111111111111111111111111111000100011001010011010111011101
高位扔掉,保留地位
*/
b1=1000000178L;
a1=b1;
/*
程序暂停到下一句,看看,a的二进制串和b的二进制串
(gdb) x/1tb &a1
0x7fffffffe02f: 10110010
(gdb) x/1tg &b1
0x7fffffffe038: 0000000000000000000000000000000000111011100110101100101010110010
高位扔掉,保留地位
结论,有符号整数大字节宽度往小字节宽度转换,就是把高位直接扔掉,保留低位。
*/
float f;
double df;
df=2e128;
f=df;
printf("f=%f,df=%lf\n",f,df);
/*
可以看出,浮点数大字节到小字节转换比较简单,超出范围的,用对应的无限inf来表示,不超出范围,
就按近似值来转换。
float到double转换我就不试验了,按照值不变转换就可以了。不信的可以试验一下。
*/
a=255;
a1=a;
printf("a1 = %hhd\n",a1);
/*
有符号整数和无符号整数转换:
同字节数的类型转换,其实就是对二进制串进行重新解释。
*/
a1=-1;
b=a1;
printf("b = %lu\n",b);//符号位扩展完毕后,再进行二进制串的重新解释。
a = 255;
b1=a;
printf("b1 = %ld\n",b1); //按照值进行转换
b1 = -2232323232323;
a=b1;
/*
查看变量内存地址内的二进制串:
(gdb) x/1tb &a
0x7fffffffe022: 10111101
(gdb) x/1tg &b1
0x7fffffffe030: 1111111111111111111111011111100000111111001010101010110110111101
看一看出来,就是把高位抛弃,留低位。
*/
return 0;
}
从上面的程序可以看出,这个转换很乱,基本能符合人类直觉,但有时候也有例外,那么最保险的办法就是按照上面程序的办法,如果要使用两个数据类型之间的强制转关规则,试一下,有数了再进行转换。好脑子不如烂爪子,试一下最好。
总结:
本章介绍了C语言中基本数据类型,和这些数据类型的本质。下面一章我们将介绍如何操作这些数据类型,也就是这些数据类型的操作符,或者说运算符。