终于到类型转换了,类型转换是我最喜欢的一部分,C语言的美在这体现的淋漓尽致,类型转换就像一把美工刀,提供了无限的可能让你去修剪数据。
C语言中有两种类型转换:自动(隐式)类型转换和强制(显式)类型转换,自动类型转换由编译器完成,例如:
code
c
char c = 'a'; int x = c; // 等价于 int x = (int)c;
自动类型转换一般发生在较低类型转换为较高类型,此处的高低指存储空间占用的大小,这样可以保证数据完整性,如果从较高类型向较低类型转换,多出的数据将会丢弃,这样会导致数据不完整,所以需要强制类型转换(显示的调用,要清楚自己在做什么)。从格式的概念看,类型转换实则为读取器、存储器和空间占用的改变,下面通过一个例子解释强制转换为什么会丢失数据:
code
c
int a = 500; char* p = &a; char c = a; printf("%d\t%d\t%d\n", p[0], p[1], p[2], p[2]); // -12 1 0 0 printf("%d\t%d\t%d\n", (&c)[0], (&c)[1], (&c)[2], (&c)[3]); // -12 -52 -52 -52
内存模型:
code
c
// 内存块【0 - 3】 a = { 空间占用: int->空间占用, 存储格式: int->存储格式, 读取格式: int->读取格式, 存储空间指针: 指向内存块首地址0 } // 4个字节一共32位,存储的500为正整数,正整数的补码、原码、反码均相同: // 0000 0000 - 0000 0000 - 0000 0001 - 1111 0100 // 内存块【4 - 12】,此中存储地址a内存块的首地址0 p = { 空间占用: 指针->空间占用, 存储格式: 指针->存储格式, 读取格式: 指针->读取格式, 目标格式: char, 存储空间指针: 指向内存块首地址4 } // 内存块【13 - 14】,此中存储a内存块中的第一个字节 1111 0100 c= { 空间占用: char>空间占用, 存储格式: char>存储格式, 读取格式: char>读取格式, 存储空间指针: 指向内存块首地址13 } char和int的存储格式都是存储数字的补码,最高位表示数字的正负,正整数的原码、反码、补码相同,负数的补码为反码加1,反码就是源码按位取反。 先来看char * p,对p进行偏移时,偏移步长为char->空间占用 1: p[0]: 内存块【0 - 1】1111 0100 // 最高位为1,表示负数,剩余部分的补码减1得到反码111 0011,反码取反得到源码000 1100,转换十进制为12,所以p[0]最终的结果为-12 p[1]: 内存块【1 - 2】0000 0001 // 最高位为0,表示正数,补码即源码,转换为数字为1,所以p[0]为1 p[2]: 内存块【2 - 3】0000 0000 p[3]: 内存块【3 - 4】0000 0000 // 这两个都是0 char c: (&c)[0]: 内存块【13 - 14】1111 0100,表示数字-12 (&c)[1]: 内存块【14 - 15】未知内存 (&c)[2]: 内存块【15 - 16】未知内存 (&c)[3]: 内存块【16 - 17】未知内存 对比c和p两个变量,都发生了强制转换,指针p虽然也开辟了新的内存空间,但是读取的时候仍是读取的变量a对应的内存块,这时候数据并没有丢失,只是变更了读取方式。而变量c只拷贝了内存块a中的一个字节,原来的数据无法与c关联,所以发生了数据丢失,变量c这种情况,也叫做数据溢出。
char有两种存储器,分别是整数存储器和字符存储器,读取格式也同样有整数读取器和字符读取器。整数的存储器和读取器同int类似,只不过操作的空间大小不一样,字符存储器在存储时会先查询一遍字符哈希表,把字符转换为对应的ASCII码,例如'A'对应ASCII码为65,然后在将65调用整数存储器进行存储,读取时再将ASCII码装换为字符。关于哈希表的原理,可以参考我的另一篇文章:C语言实现HashMap
类型转换讲到这里并没有结束,但我们必须更深入了解下指针才能更好的理解类型转换,第4节讲完指针后会接着讲类型转换。