在按位求反运算中使用unsigned char有什么用?
unsigned char a = 10,b; b = ~a; printf(" %d \n ",b);
输出:
245
相同的程序,如果我使用int
而不是unsigned char
,o / p更改为-11
每当~
用于小整数类型的操作数时,例如char
, unsigned char
, short
等,该操作数总是被隐式提升为int
类型。 这称为整数提升规则 。
这意味着无论您的示例中的char类型a
什么,它都将被提升为带符号类型的int
。 表达式~a
相当于~(int)10
。 结果将是(假设32位CPU) 0xFFFFFFF5
,以二进制补码表示为-11
。
但是,当您将此数字存储回unsigned char时,它将转换为值为245
的无符号类型。 将b
声明为int
,不会发生此转换,值仍为-11
。
以上就是为什么~
是一个相当危险的操作者使用的原因。 在更复杂的表达式中,它可以创建由签名的无声更改引起的非常微妙的错误。
变量a
包含unsigned char
值10.假设使用8位char
和32位int
类型的正统设置,则a
中的位是:
0000 1010
存储在b
的值是b
内容的按位反转,这意味着它包含以下位,对应于245十进制:
1111 0101
将unsigned char
传递给printf()
:
printf("a = %d\n", a); printf("b = %d\n", b);
比int
short
类型(字符类型和short
类型 – 我假设short
是16位类型,通常但不一定是这种情况)会自动提升为int
。 由于源类型是unsigned char
,因此该值将转换为正int
值 – 示例代码中为10和245。
将类型从unsigned char
更改为int
并更改内容。
a: 0000 0000 0000 0000 0000 0000 0000 1010 b: 1111 1111 1111 1111 1111 1111 1111 0101
由于2的补码算术 (存储)的工作方式, b
的位模式对应于-11
。 当这些版本的a
和b
传递给printf()
,不需要转换,因此printf()
将10
和-11
视为值,并相应地进行打印。
关键是促销活动。
在C标准中,它说:
6.5.2.2 函数调用
¶7如果表示被调用函数的表达式具有包含原型的类型,则将参数隐式转换为相应参数的类型,就像通过赋值一样,将每个参数的类型作为不合格的版本它的声明类型。 函数原型声明符中的省略号表示法导致参数类型转换在最后声明的参数之后停止。 默认参数提升是在尾随参数上执行的。
当然,声明的printf()
类型是:
int printf(const char * restrict format, ...);
因此,值a
和b
位于最后声明的参数之后,并受“默认参数提升”的约束。 他们在第6段中指明(部分):
¶6如果表示被调用函数的表达式具有不包含原型的类型,则对每个参数执行整数提升,并将类型为
float
参数提升为double
。 这些被称为默认参数促销 。 …
您必须再回过头来查找整数促销 :
6.3.1算术运算数
6.3.1.1布尔,字符和整数
¶2可以在任何可以使用int或unsigned int的表达式中使用以下内容:
- 具有整数类型(
int
或unsigned int
除外)的对象或表达式,其整数转换等级小于或等于int
和unsigned int
的等级。_Bool
,int
,signed int
或unsigned int
类型的位字段。如果
int
可以表示原始类型的所有值(由宽度限制,对于位字段),该值将转换为int
; 否则,它将转换为unsigned int
。 这些被称为整数促销 。 58)所有其他类型由整数促销不变。¶3整数提升保留包括符号在内的值。 如前所述,“plain”
char
是否被视为已签名是实现定义的。
秩的概念在§6.3.1.1¶1中定义,我不打算引用它(它相当长且非常详细)。
关键点是较小的类型(如char
)被提升为int
,并保留包含符号的值。 而你所看到的完全匹配。
测试代码
#include int main(void) { unsigned char a1 = 10; unsigned char b1 = ~a1; printf("a1 = %d, b1 = %d\n", a1, b1); int a2 = 10; int b2 = ~a2; printf("a2 = %d, b2 = %d\n", a2, b2); return 0; }
输出:
a1 = 10, b1 = 245 a2 = 10, b2 = -11
无符号的字符
a
|128|64|32|16|8|4|2|1| |0 |0 |0 |0 |1|0|1|0|
等于10
~
有点否定
(unsigned char)~a
|128|64|32|16|8|4|2|1| |1 |1 |1 |1 |0|1|0|1|
等于245
由于8b
char
小于32b
int
,因此该值扩展为32b
但在调用printf()
时会保留该符号。
诠释
二进制补码 usually
用于签名操作。 我们期望sizeof(int)
等于4B
( 32bit
)。 (Sizeof int
取决于体系结构。)
4B中的10
|0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |1|0|1|0|
4B中~10
|1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |1|1|1|1| |0|1|0|1|
获得decimal
值
-
~
否定价值 - 加1
- 改变标志
所以你有了
- |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |0|0|0|0| |1|0|1|1|
并且它等于-11
十进制
在按位求反运算中使用unsigned char有什么用?
通过使用unsigned char
你得到了8
位unsigned
,这意味着值为0..255
。 (2^8 -1)
所以对于signed char
你有值-127..127
。 (+/-)2^7 -1
unsigned char仅表示:使用最高有效位而不是在执行算术运算时将其视为+/-符号的位标志。
int
变量存储32 bits
,第一位代表数字符号。 如果该位打开,则该数字为负数。
负数表示如下:
-1 | 11111...111 -2 | 11111...110 -3 | 11111...101 -4 | 11111...100
序列与正整数几乎相同,但reversed
。
在C中,int和char必须是类型
- 签
- 无符号
如果用户给出int [16位]或char [8位],则编译器默认为signed。 对于unsigned,用户需要明确提及。
unsigned char的范围是0到255 [8位]。
signed char的范围是-128到0到127 [8位]。
按照你的计划,
情况1:
a是unsigned char,输出245的范围是0到255.所以输出为245。
0, 1, 2, 3, 4, ..................... 254, 255 | | |<- <- <- <- <- <- <- <- <- <- <- <- <- <- | example: unsigned char a = 257; // more than 255. printf("a = %d", a); **Output** : a = 1. // [ 0,1,...255,0 [256th], **1** [257th] unsigned char a = 129; // with in range of 255. printf("a = %d", a); **Output** : a = 129.
案例2:
a是签名字符,相同的输出245从0到127开始,然后从-128,-127,-126,...... -11(第245位)开始并打印-11。
-128, -127, -126, ..... -11, -10, .. -1, 0, 1, 2, 3, .....126, 127 | | |<- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <- <-<-| example: char a = 257; // more than 255. printf("a = %d", a); **Output** : a = 1. // [ 0,1,...127,-128,-127,....-1, 0 [256th], **1** [257th]. char a = 129; // with in range of 255. printf("a = %d", a); **Output** : a = -127. // [ 0,1,...127,-128 [128th], **-127** [129th].
同样的情况也适用于int数据类型。