C语言类型的本质(四):类型转换

终于到类型转换了,类型转换是我最喜欢的一部分,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
}

charint的存储格式都是存储数字的补码,最高位表示数字的正负,正整数的原码、反码、补码相同,负数的补码为反码加1,反码就是源码按位取反。

先来看char * p,对p进行偏移时,偏移步长为char->空间占用 1:
p[0]: 内存块【0 - 11111 0100
 // 最高位为1,表示负数,剩余部分的补码减1得到反码111 0011,反码取反得到源码000 1100,转换十进制为12,所以p[0]最终的结果为-12
 
p[1]: 内存块【1 - 20000 0001
// 最高位为0,表示正数,补码即源码,转换为数字为1,所以p[0]为1

p[2]: 内存块【2 - 30000 0000
p[3]: 内存块【3 - 40000 0000
// 这两个都是0

char c:
 (&c)[0]: 内存块【13 - 141111 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节讲完指针后会接着讲类型转换。