char与signed char, unsigned char的区别

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 charunsigned 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

因为charunsigned char型都是一个字节,int型是四个字节的,从 int 型到 char 或 unsigned char型的转换都是直接将int的最低字节赋予char 或 unsigned char;

当int为正数时:

1
2
char c = 3;
unsigned char uc= 3;

3是int型,其在计算机里的存储为00000000 00000000 00000000 00000011;所以 c 和 uc 在计算机里的存储都为 00000011,由于是正数,故值都为3。

当int为负数时:

1
2
char c = -3;
unsigned char uc= -3;

-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时,

1
int n = c;

n在计算机里的存储为:11111111 11111111 11111111 11111101

例如:当c是char型,并且在计算机里存储为0000 0011时,

1
int n = c;

n在计算机里的存储为:00000000 00000000 00000000 00000011;

3.2.2 unsigned char 转int型

因为unsigned char 是无符号的,所以int的高三个字节都是以0填充,例如:
当uc是unsigned char型,并且在计算机里存储为1111 1101时,

1
int n = uc;

n在计算机里的存储为:00000000 00000000 00000000 11111101
又例如:
当uc是unsigned char型,并且在计算机里存储为0000 0011时,

1
int n = uc;

n在计算机里的存储为:00000000 00000000 00000000 00000011;

4. 更多示例

下面是一段C语言程序,通过查看控制台的输出信息就可以分析char和unsigned char的区别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <stdio.h>
void test(unsigned char v) {
char c = v;
unsigned char uc = v;
unsigned int a = c, b = uc;
int i = c, j = uc;
printf("%%c: %c, %c\n", c, uc);
printf("%%X: %X, %X\n", c, uc);
printf("%%u: %u, %u\n", a, b);
printf("%%d: %d, %d\n", i, j);
}
int main(int argc, const char * argv[]) {
test(0xFF);
test(0x80);
test(0x7F);
test(0x41);
return 0;
}

对输出的内容进行分析:

首先对占位符进行说明:

占位符 含义
%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,则在数据转换时两者就有区别了。

参考文档

百度文库: char与unsigned char的本质区别

zx824的博客: char与signed char, unsigned char的区别

yifeiboss的博客:char和unsigned与int之间的转换