signed char
,char
并且unsigned char
都是整数类型。为了简单起见,我假设 CHAR_BIT 是 8,而有符号类型是 2 的补码。所以:
signed char
是从 -128 到 +127 的数字
unsigned char
是一个从 0 到 255 的数字
char
是与 相同的范围signed char
,或与 相同的范围unsigned char
,具体取决于您的 C 实现。
就 C 而言,字符只是char
类型范围内的数字(尽管各种字符函数,例如tolower
要求在输入的过程中将值强制转换为无符号类型,即使char
是有符号的)。
所以signed char
和unsigned char
都是位形式的字符表示。对于 0 到 +127 范围内的数字,它们都使用相同的表示形式(只有一种方法可以用二进制表示正数)。对于该范围之外的数字,负数n
的有符号表示与n + 256
(2 的补码的定义)的无符号表示相同。
此代码使用的原因unsigned char
是带有负符号值的右移具有实现定义的结果。带负符号值的左移具有未定义的行为。通常左移的行为与无符号值的行为相同,这是可以的,但右移会在左侧插入值为 1 的位,即所谓的“算术移位”,这不是这里想要的。无符号值总是移入零,正是移入零使此代码可以构建旋转结果的两个部分和/或它们在一起。
因此,假设输入值为 x = 254 ( 11111110
),并且 n = 1,我们得到:
x << 7 is 0111111100000000
x >> 1 is 01111111
| is 0111111101111111
convert to unsigned char to return is 01111111
如果我们使用有符号类型而不是unsigned char
,我们很可能会得到:
x is -2 11111110
x << 7 is 11111111111111111111111100000000 (assuming 32-bit int, since
smaller types are always promoted to int for arithmetic ops)
x >> 1 is implementation-defined, possibly
11111111111111111111111111111111
| is 11111111111111111111111111111111
convert to signed char to return is -1
因此,使用 unsigned char 进行位操作会产生正确的答案,旋转 1 位以将 0 从末尾移动到开头。使用有符号字符进行位操作,可能会给出错误的结果,如果负符号值进行逻辑右移,可能会给出正确的结果,但在真正不寻常的实现中可以做任何事情。
对于像旋转这样的位操作任务,几乎总是要使用无符号类型。它消除了对实现的依赖(除了类型的宽度),并且避免了你必须分别推理负值和非负值。