0. 预备知识:整数在计算机中的表示
原码、反码、补码、移码的定义:
原码:如果机器字长为n,那么一个数的原码就是一个n位的二进制数,其中最高位为符号位:正数为0,负数为1;剩下的n-1位表示该数的绝对值,位数不够的用0补全。
下面是机器字长为8的情况:
X = +101011 , [X]原 = 00101011
X = -101011 , [X]原 = 10101011
反码: 正数的反码与原码一样,负数的反码就是在原码的基础上,符号位不变其他位按位取反(就是0变1,1变0)。
例如:
X = -101011 , [X]原 = 10101011 ,[X]反 = 11010100
补码:正数的补码与原码一样,负数的补码就是在反码的基础上按照正常的加法运算加1。
例如:
X = -101011
[X]原 = 10101011
[X]反 = 11010100
[X]补 = 11010101
0的原码跟反码都有两个,补码是唯一的,因为这里0被分为+0和-0,如果机器字长为8那么[0]补 = 00000000。
移码: 不管正负数,只要将其补码的符号位取反即可。
例如:
X = -101011
[X]原 = 10101011
[X]反 = 11010100
[X]补 = 11010101
[X]移 = 01010101
整数在计算机中是以二进制补码的方式表示的
以int型为例:int有4个字节,最高位为符号位
正数为0xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
例如:int n = 3;
在计算机里存储为00000000 00000000 00000000 00000011
(正数的原码反码和补码都一样)
负数为1xxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx
,
例如:int n = -3;
在计算机里存储为11111111 11111111 11111111 11111101
(补码)
1.char类型的使用
字符型char
用于储存字符(character),如英文字母或标点。严格来说,char 其实也是整数类型(integer type),因为 char 类型储存的实际上是整数,而不是字符。计算机使用特定的整数编码来表示特定的字符。美国普遍使用的编码是 ASCII(American Standard Code for Information Interchange 美国信息交换标准编码)
。例如:ASCII 使用 65 来代表大写字母 A,因此存储字母 A 实际上存储的是整数65。
在C语言中,char类型通常有以下两种使用方法:
(1)当字符用
原始的ASCII标准里,定义的字符码值是只有0~127,所以char类型可以表示其中的任何一个字符,于是普通意义上我们对它的理解就是它表示一个字符,也就是表示一个ASCII码(这也是C语言基础中对它的解释)。
(2)当单字节整数
从本质上来说,字符(‘a’, ‘b’, ‘c’等)本质上也是一个整数,只是字符代表的值是0~127,我们可以用char表示一个不太大的整数。
2.char,signed char及unsigned char的区别
char
,signed char
与unsigned char
都是字符类型,根据编译器的不同,char会用signed char或unsigned char来实现,例如VC编译器、x86上的GCC都把char定义为signed char,而arm-linux-gcc却把char定义为 unsigned char。
**说明:我们现在默认在VC编译环境下 char 定义为 signed char**
那么char与unsigned char有什么区别呢?
这里讨论两者的区别其实是相对“运算”而说的,已经脱离了我们字面含义“字符”。
首先在内存中,char与unsigned char没有什么不同,都是一个字节,都能表示256个数字(8个bit,最多256种情况),唯一的区别是,char的最高位为符号位,因此char能表示-128~127, unsigned char没有符号位,因此能表示0~255。
在实际使用过程中有什么区别呢?
在普通的赋值,读写文件和网络字节流都没什么区别,反正就是一个字节,不管最高位是什么,最终的读取结果都一样,只是你怎么理解最高位而已,在屏幕上面的显示可能不一样。但是如果进行*数据转换*
,char与unsigned char就有区别了,如果是char,系统认为最高位是符号位,那么将对最高位进行拓展,如果是unsigned char,系统认为没有符号位,不会对最高位拓展。
对最高位拓展是什么意思呢?
char
在C里占一个字节,比如0x80的一个char,二进制为10000000,如果转为int,则认为最高位的1是符号位,int是4个字节,剩下的3个怎么办呢,肯定得填充了,系统会在前面填充。如果最高位是符号位,那么系统会在前面填充3个字节的符号位(这和计算机里数值用补码表示有关)。
比如0x80的一个char
,10000000,表示-128,转为int类型时,我们还是希望是-128,此时系统会在前三个字节里全部填充符号位1,变成11111111 11111111 11111111 1000000,这个数在计算机里还是表示-128。
如果一个0x80的unsigned char
,二进制也是1000 0000,由于是无符号型(只能表示正数),系统认为该数最高位不是符号是数,所以系统认为所表示的值是128,若转为int型,系统会在前三个字节填充0,变成00000000 00000000 00000000 1000000,这个数在计算机int里还是表示128。
**注意:赋给unsigned int也会扩展。**
3.char 和 unsigned 与int 之间的转换示例
3.1 int型转换为char和unsigned char
因为char
和unsigned char
型都是一个字节,int
型是四个字节的,从 int 型到 char 或 unsigned char型的转换都是直接将int的最低字节赋予char 或 unsigned char;
当int为正数时:
|
|
3是int型,其在计算机里的存储为00000000 00000000 00000000 00000011;所以 c 和 uc 在计算机里的存储都为 00000011,由于是正数,故值都为3。
当int为负数时:
|
|
-3是int 型,其在计算机里的存储为11111111 11111111 11111111 11111101;所以c和uc在计算机里的存储都为1111 1101;但由于char是有符号的,unsigned char 是无符号的;char c的最高位为符号位,符号位1表示负数,0表示正数,所以c的值为-3;而unsigned char uc是无符号的,所以值为253。
3.2 char和unsigned char 转换为int 型
由于char或者unsigned char是一个字节的,int型是四个字节的,当char 或者 unsigned char向int 型转换的时候,高的三个字节计算机怎么处理呢?
3.2.1 char 转换为int型
因为char
是有符号的,所以int
的高三个字节都是以char的最高位(符号位去填充)
例如:当c是char型,并且在计算机里存储为1111 1101
时,
|
|
n在计算机里的存储为:11111111 11111111 11111111 11111101
例如:当c是char型,并且在计算机里存储为0000 0011
时,
|
|
n在计算机里的存储为:00000000 00000000 00000000 00000011
;
3.2.2 unsigned char 转int型
因为unsigned char
是无符号的,所以int
的高三个字节都是以0填充,例如:
当uc是unsigned char型,并且在计算机里存储为1111 1101
时,
|
|
n在计算机里的存储为:00000000 00000000 00000000 11111101
,
又例如:
当uc是unsigned char型,并且在计算机里存储为0000 0011时,
|
|
n在计算机里的存储为:00000000 00000000 00000000 00000011
;
4. 更多示例
下面是一段C语言程序,通过查看控制台的输出信息就可以分析char和unsigned char的区别:
|
|
对输出的内容进行分析:
首先对占位符进行说明:
占位符 | 含义 |
---|---|
%c | 读入一个字符 |
%X | 读入十六进制整数 |
%u | 读入一个无符号十进制整数 |
%d | 读入十进制整数 |
(1) test(0xFF)
输出语句 | 输出内容 | 内容分析 |
---|---|---|
printf(“%%c: %c, %c\n”, c, uc); | %c: ?, ? | 0xFF转为二进制为11111111,在char中表示-127,在unsigned char中表示的值为255,两者都不在ASCII所定义的字符范围内,所以显示“ ?” |
printf(“%%X: %X, %X\n”, c, uc); | %X: FFFFFFFF, FF | %X要将值转换成int整数类型再来进行输出,所以char类型中的符号位会进行扩展,转换后为11111111 11111111 11111111 11111111,转成16进制即FFFFFFFF;unsigned char类型没有符号位,所以系统会在前面填充0,转换后为00000000 00000000 00000000 11111111,16进制表示为FF(这里的c和uc都是字符型,所以直接将存储的二进制数转换为16进制后输出) |
printf(“%%u: %u, %u\n”, a, b); | %u: 4294967295, 255 | 这里是把char和unsigned char类型转换为unsigned int 型,然后按照无符号10进制整形输出;这里同样需要对符号位进行扩展,然后按照无符号10进制输出,所以输出的结果为4294967295 ,255 |
printf(“%%d: %d, %d\n”, i, j); | %d: -1, 255 | 这里是把char和unsigned char转换为int型,然后按10进制整形输出;char类型转成int型后为11111111 11111111 11111111 11111111,因为是有符号型int,所以这里存储的二进制数会被当作补码,转换为原码后为10000000 00000000 00000000 00000001,按照10进制整形输出即为-1;unsigned char 转成int型后为00000000 00000000 00000000 11111111,转换为10进制整形后输出为255 |
(2) test(0x80)
输出语句 | 输出内容 | 内容分析 |
---|---|---|
printf(“%%c: %c, %c\n”, c, uc); | %c: ?, ? | 0x80转为二进制为10000000,在char中表示-128,在unsigned char中表示的值为128,两者都不在ASCII所定义的字符范围内,所以显示“ ?” |
printf(“%%X: %X, %X\n”, c, uc); | %X: FFFFFF80, 80 | %X要将值转换成int整数类型再来进行输出,所以char类型中的符号位会进行扩展,转换后为11111111 11111111 11111111 10000000,转成16进制即FFFFFF80;unsigned char类型没有符号位,所以系统会在前面填充0,转换后为00000000 00000000 00000000 10000000,16进制表示为80(这里的c和uc都是字符型,所以直接将存储的二进制数转换为16进制后输出) |
printf(“%%u: %u, %u\n”, a, b); | %u: 4294967168, 128 | 这里是把char和unsigned char类型转换为unsigned int 型,然后按照无符号10进制整形输出;这里同样需要对符号位进行扩展,然后按照无符号10进制输出,所以输出的结果为4294967168 ,128 |
printf(“%%d: %d, %d\n”, i, j); | %d: -128, 128 | 这里是把char和unsigned char转换为int型,然后按10进制整形输出;char类型转成int型后为11111111 11111111 11111111 10000000,因为是有符号型int,所以这里存储的二进制数会被当作补码,转换为原码后为10000000 00000000 00000000 10000000,按照10进制整形输出即为-128;unsigned char 转成int型后为00000000 00000000 00000000 10000000,转换为10进制整形后输出为128 |
(3) test(0x7F)
输出语句 | 输出内容 | 内容分析 |
---|---|---|
printf(“%%c: %c, %c\n”, c, uc); | %c: , | 0x7F转为二进制为01111111,在char和unsigned char中表示的值都为127,两者对应的ASCII的字符为“DEL (delete)”,所以输出“ , ” |
printf(“%%X: %X, %X\n”, c, uc); | %X: 7F, 7F | %X要将值转换成int整数类型再来进行输出,char和unsigned char类型转换后都表示为00000000 00000000 00000000 01111111,转成16进制即7F(这里的c和uc都是字符型,所以直接将存储的二进制数转换为16进制后输出) |
printf(“%%u: %u, %u\n”, a, b); | %u: 127, 127 | 这里是把char和unsigned char类型转换为unsigned int 型(这里同样需要对符号位进行扩展),转换后都为00000000 00000000 00000000 01111111,然后按照无符号10进制整形输出,所以输出的结果为127 ,127 |
printf(“%%d: %d, %d\n”, i, j); | %d: 127, 127 | 这里是把char和unsigned char转换为int型,然后按10进制整形输出;char类型和unsigned char转成int型后均为00000000 00000000 00000000 01111111,按照10进制整形输出即为127 |
(4) test(0x41)
输出语句 | 输出内容 | 内容分析 |
---|---|---|
printf(“%%c: %c, %c\n”, c, uc); | %c: A, A | 0x41转为二进制为01000001,在char和unsigned char中表示的值都为65,两者对应的ASCII的字符为“A”,所以输出“A,A” |
printf(“%%X: %X, %X\n”, c, uc); | %X: 41, 41 | %X要将值转换成int整数类型再来进行输出,char和unsigned char类型转换后都表示为00000000 00000000 00000000 01000001,转成16进制即41(这里的c和uc都是字符型,所以直接将存储的二进制数转换为16进制后输出) |
printf(“%%u: %u, %u\n”, a, b); | %u: 65, 65 | 这里是把char和unsigned char类型转换为unsigned int 型(这里同样需要对符号位进行扩展),转换后都为00000000 00000000 00000000 01000001,然后按照无符号10进制整形输出,所以输出的结果为65,65 |
printf(“%%d: %d, %d\n”, i, j); | %d: 65, 65 | 这里是把char和unsigned char转换为int型,然后按10进制整形输出;char类型和unsigned char转成int型后均为00000000 00000000 00000000 01000001,按照10进制整形输出即为65 |
通过上面的分析可以发现,当char和unsigned char对应的字节存储相同的二进制码,若最高位为0时,二者没有区别,若最高位为1,则在数据转换时两者就有区别了。