Char、Unsigned char 移位和转int 问题

更新时间:2024-06-20 19:54:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Char 、Unsigned char 移位和转int 问题

今天小菊同学遇到了个问题,是关于char 移位之后转化为unsigned int 之后结果和预期不一样。

先讲讲char 和Unsigned char 的区别,首先在内存中 两者没什么区别都是一个字节,唯一的区别是,char的首位是符号位和unsigned char 的首位是数字位,char能表示-128~127,unsigned char能表示 0~256。当char或unsigned char 移位的时候都会先转化为int型,转化的时候需要对char或unsigned char 进行扩展;如下代码:

char buffer = 0xF0; Buffer >>2 ;

这时候buffer先会转为int 型然后在移位,转int型的时候,由于计算机里数值都是以补码形式存在,所以这个时候转化为int的Buffer 值应该为0xFFFFFFF0(将-16以补码形式的表现),然后在做右移两位操作;

而如果是unsigned char buffer = 0xF0的话转化为Int还是0xF0,所以不会有影响;

不仅仅对于移位操作,还有很多运算符都会转化的,如下: void foo(void) {

unsigned int a = 6; int b = -20;

(a+b>6)?puts(\//puts为打印函数 }

这个时候 会b会隐式转化为 unsigned int类型 变成了正数 上面的两个例子主要说明了两点:

1.隐式转化,运算符在运算的时候可能在不同类型之间存在隐式转化; 2.计算机内,数值是以补码形式存储的; 在来讲讲C++的隐式转化问题:

C++定义了一组内置的类型对象之间的标准转换,在必要时它们被编译器隐式的应用到对象上。在算式转换保证了二元操作符,如加法或乘法的两个操作数被提升为共同的类型,然后再用它表示结果的类型。两个通用的指导原则如下: 1、为防止精度损失,如果必要的话,类型总是被提升为较宽的类型。 2、所有含有小于整形的有序类型的算术表达式在计算之前其类型都会被转换成整形。

规则的定义如上面所述,这些规则定义了一个类型转换层次结构,我们从最宽的类型long double 开始,那么另一个操作数无论是什么类型都将被转换成long double .如果两个操作数都不是long double 型,那么若其中一个操作数的类型是double 型,则另一个就被转换成double 型。例如: int ival; float fval; double dval;

dval + fval + ival //在计算加法前fval和ival都被转换成double

否则如果两个操作数都不是3种浮点类型之一,它们一定是某种整值类型。在确定共同的目标提升类型之前,编译器将在所有小于int 的整值类型上施加一个被称为整值提升的过程。

在进行整值提升时类型char、signed char、unsigned char和short int 都被提升为类型int 。如果机器上的类型空间足够表示所有unsigned short 型的值,这通常发生在short用半个字而int 用一个字表示的情况下,则

unsigned short int 也被转换成int 否则它会被提升为unsigned int 。wchar_t和枚举类型被提升为能够表示其底层类型所有值的最小整数类型。在下列表达式中: char cval; bool found; enum mumber{m1,m2,m3}mval; unsigned long ulong; cval + ulong;ulong + found; mval + ulong; 在确定两个操作数被提升的公共类型之前,cval found 和mval都被提升为int 类型。

一旦整值提升执行完毕,类型比较就又一次开始。如果一个操作数是unsigned long 型,则第二个也被转换成unsigned long 型。在上面的例子中所有被加到ulong上的3个对象都被提升为unsigned long 型。如果两个操作数类型都不是unsigned long型 而其中一个操作数是long型,则另一个也被转换成long型。例如:

char cval; long lval; cval + 1024 + lval; //在计算加法前cval和1024都被提升为long型。 long类型的一般转换有一个例外。如果一个操作数是long型而另一个是unsigned int 型,那么只有机器上的long型的长度足以容纳unsigned int 的所有值时(一般来说,在32位操作系统中long型和int 型都用一长表示,所以不满足这里的假设条件),unsigned int 才会被转换为long型,否则两个操作数都被提升为unsigned long 型。若两个操作数都不是long型而其中一个是unsigned int 型,则另一个也被转换成unsigned int 型,否则两个操作数一定都是int 型。

一般来说各种类型的长度关系

为 long double > double > float >= int >= short > char,unsigned > signed 。

尽管算术转换的这些规则带给你的困惑可能多于启发,但是一般的思想是尽可能地保留类型表达式中涉及到的值的精度。这下是通过把不同的类型提升到当前出现的最宽的类型实现的。

参考:http://blog.csdn.net/echoisland/article/details/6851686 隐式类型转换&&负数的补码

偶然看到一道C++面试题: void foo(void) {

unsigned int a = 6; int b = -20;

(a+b>6)?puts(\puts(\//puts为打印函数

}

问输出是什么?答案是输出>6。 这道题主要考察两个东西。

1.隐式类型转换:int型变量转化成unsigned int, b成了正数. 2.负数的补码:计算机系统中的数值是以补码形式表示(存储)的。

一、C++隐式类型转换

C++定义了一组内置的类型对象之间的标准转换,在必要时它们被编译器隐式的应用到对象上。在算式转换保证了二元操作符,如加法或乘法的两个操作数被提升为共同的类型,然后再用它表示结果的类型。两个通用的指导原则如下:

1、为防止精度损失,如果必要的话,类型总是被提升为较宽的类型。

2、所有含有小于整形的有序类型的算术表达式在计算之前其类型都会被转换成整形。 规则的定义如上面所述,这些规则定义了一个类型转换层次结构,我们从最宽的类型long double 开始,那么另一个操作数无论是什么类型都将被转换成long double .如果两个操作数千不是long double 型,那么若其中一个操作数的类型是double 型,则另一个就被转换成double 型。例如: int ival; float fval; double dval;

dval + fval + ival //在计算加法前fval和ival都被转换成double

类似地,如果两个操作数都不是double型而其中一个操作float型,则另一个被转换成float型。例如: char cval; int ival; float fval;

cval + ival + fval //在计算加法前ival和cval都被转换成float

否则如果两个操作数都不是3种浮点类型之一,它们一定是某种整值类型。在确定共同的目标提升类型之前,编译器将在所有小于int 的整值类型上施加一个被称为整值提升的过程。 在进行整值提升时类型char、signed char、unsigned char和short int 都被提升为类型int 。如果机器上的类型空间足够表示所有unsigned short 型的值,这通常发生在short用半个字而int 用一个字表示的情况下,则unsigned short int 也被转换成int 否则它会被提升为unsigned int 。wchar_t和枚举类型被提升为能够表示其底层类型所有值的最小整数类型。在下列表达式中: char cval; bool found;

enum mumber{m1,m2,m3}mval; unsigned long ulong;

cval + ulong;ulong + found; mval + ulong;

在确定两个操作数被提升的公共类型之前,cval found 和mval都被提升为int 类型。 一旦整值提升执行完毕,类型比较就又一次开始。如果一个操作数是unsigned long 型,则第二个也被转换成unsigned long 型。在上面的例子中所有被加到ulong上的3个对象都被提升

为unsigned long 型。如果两个操作数类型都不是unsigned long型而其中一个操作数是long型,则另一个也被转换成long型。例如: char cval; long lval;

cval + 1024 + lval; //在计算加法前cval和1024都被提升为long型。

long类型的一般转换有一个例外。如果一个操作数是long型而另一个是unsigned int 型,那么只有机器上的long型的长度足以容纳unsigned int 的所有值时(一般来说,在32位操作系统中long型和int 型都用一长表示,所以不满足这里的假设条件),unsigned int 才会被转换为long型,否则两个操作数都被提升为unsigned long 型。若两个操作数都不是long型而其中一个是unsigned int 型,则另一个也被转换成unsigned int 型,否则两个操作数一定都是int 型。 一般来说各种类型的长度关系为 long double > double > float >= int >= short > char,unsigned > signed 。

尽管算术转换的这些规则带给你的困惑可能多于启发,但是一般的思想是尽可能地保留类型表达式中涉及到的值的精度。这下是通过把不同的类型提升到当前出现的最宽的类型实现的。

二、负数的补码

在计算机系统中,数值一律用补码来表示(存储)。

主要原因:使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。另外,两个用补码表示的数相加时,如果最高位(符号位)有进位,则进位被舍弃。 补码与原码的转换过程几乎是相同的。 数值的补码表示也分两种情况: (1)正数的补码:与原码相同。 例如,+9的补码是00001001。

(2)负数的补码:符号位为1,其余位为该数绝对值的原码按位取反;然后整个数加1。 例如,-7的补码:因为是负数,则符号位为“1”,整个为10000111;其余7位为-7的绝对值+7的原码0000111按位取反为1111000;再加1,所以-7的补码是11111001。 已知一个数的补码,求原码的操作分两种情况:

(1)如果补码的符号位为“0”,表示是一个正数,所以补码就是该数的原码。

(2)如果补码的符号位为“1”,表示是一个负数,求原码的操作可以是:符号位为1,其余各位取反,然后再整个数加1。

例如,已知一个补码为11111001,则原码是10000111(-7):因为符号位为“1”,表示是一个负数,所以该位不变,仍为“1”;其余7位1111001取反后为0000110;再加1,所以是10000111。

C语言移位运算符

位移位运算符是将数据看成二进制数,对其进行向左或向右移动若干位的运算。位移位运算符分为左移和右移两种,均为双目运算符。第一运

算对象是移位对象,第二个运算对象是所移的二进制位数。

位移位运算符的运算对象、运算规则与结果、结合性如表2-16所示。

移位时,移出的位数全部丢弃,移出的空位补入的数与左移还是右移花接木有关。如果是左移,则规定补入的数全部是0;如果是右移,还与被移位的数据是否带符号有关。若是不带符号数,则补入的数全部为0;若是带符号数,则补入的数全部等于原数的最左端位上的原数(即原符号位)。具体移位规则如下所示。

位移位运算符的优先级如下:

·算术运算符 优先于 位移位运算符优先于关系运算符 ·位移位运算符是同级别的,结合性是自左向右 例如,设无符号短整型变量a为0111(对应二进制数为0000000001001001),

则:a<<3 结果为01110(对应二进制数为0000001001001000),a不变

a>>4 结果为04 (对应二进制数为0000000000000100),a不变

又如,设短整型变量a为-4(对应二进制数为 1111111111111100), 则:a<<3 结果为-32(对应二进制数为1111111111100000),a不变

a>>4 结果为-1(对应二进制数为1111111111111111),a不变

C语言里的左移和右移运算 2006-09-30 13:52

先说左移,左移就是把一个数的所有位都向左移动若干位,在C中用<<运算符.例如:int i = 1;i = i << 2; //把i里的值左移2位也就是说,1的2进制是000...0001(这里1前面0的个数和int的位数有关,32位机器,gcc里有31个0),左移2位之后变成 000... 0100,也就是10进制的4,所以说左移1位相当于乘以2,那么左移n位就是乘以2的n次方了(有符号数不完全适用,因为左移有可能导致符号变化,下面解释原因)需要注意的一个问题是int类型最左端的符号位和移位移出去的情况.我们知道,int是有符号的整形数,最左端的1位是符号位,即0正1负,那么移位的时候就会出现溢出,例如:int i = 0x40000000; //16进制的40000000,为2进制的01000000...0000 i = i << 1;

那么,i在左移1位之后就会变成0x80000000,也就是2进制的100000...0000,符号位被置1,其他位全是0,变成了int类型所能表示的最小值,32位的int这个值是-2147483648,溢出.如果再接着把i左移1位会出现什么情况呢?在C语言中采用了丢弃最高位的处理方法,丢弃了1之后,i的值变成了0.左移里一个比较特殊的情况是当左移的位数超过该数值类型的最大位数时,编译器会用左移的位数去模类型的最大位

数,然后按余数进行移位,如:int i = 1, j = 0x80000000; //设int为32位 i = i << 33; // 33 % 32 = 1 左移1位,i变成2

j = j << 33; // 33 % 32 = 1 左移1位,j变成0,最高位被丢弃 在用gcc编译这段程序的时候编译器会给出一个warning,说左移位数>=类型长度.那么实际上i,j移动的就是1位,也就是332 后的余数.在gcc下是这个规则,别的编译器是不是都一样现在还不清楚.总之左移就

是: 丢弃最高位,0补最低位再说右移,明白了左移的道理,那么右移就比较好理

解了.右移的概念和左移相反,就是往右边挪动若干位,运算符是>>.右移对符号位的处理和左移不同,对于有符号整数来说,比如int类型,右移会保持符号位不变,例如:int i = 0x80000000;

i = i >> 1; //i的值不会变成0x40000000,而会变成0xc0000000 就是说,符号位向右移动后,正数的话补0,负数补1,也就是汇编语言中的算术右移.同样当移动的位数超过类型的长度时,会取余数,然后移动余数个位.

负数10100110 >>5(假设字长为8位),则得到的是 11111101 总之,在C中,左移是逻辑/算术左移(两者完全相同),右移是算术右移,会保持符号位不变 .

实际应用中可以根据情况用左/右移做快速的乘 /除运算,这样会比循环效率高很多.

在很多系统程序中常要求在位(bit)一级进行运算或处理。C语言提供了位运算的功能,这使得C语言也能像汇编语言一样用来编写系统程序。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

操作符作用

──────────────────────────── &位逻辑与 | 位逻辑或 ^ 位逻辑异或 - 位逻辑反 >>右移 <<左移

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

按位运算是对字节或字中的实际位进行检测、设置或移位, 它只适用于字符型和整数型变量以及它们的变体, 对其它数据类型不适用。 我们要注意区分位运算和逻辑运算。

1. 按位与运算按位与运算符\是双目运算符。其功能是参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1 ,否则为0。参与运算的数以补码方式出现。

例如:9&5可写算式如下: 00001001 (9的二进制补码)&00000101 (5的二进制补码) 00000001 (1的二进制补码)可见9&5=1。

按位与运算通常用来对某些位清0或保留某些位。例如把a 的高八位清 0 ,保留低八位,可作 a&255 运算 ( 255 的二进制数为0000000011111111)。

main(){

int a=9,b=5,c; c=a&b;

printf(\}

2. 按位或运算按位或运算符“|”是双目运算符。其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就

为1。参与运算的两个数均以补码出现。 例如:9|5可写算式如下: 00001001|00000101 00001101 (十进制为13)可见9|5=13 main(){

int a=9,b=5,c; c=a|b;

printf(\}

3. 按位异或运算按位异或运算符“^”是双目运算符。其功能是参与运算的两数各对应的二进位相异或,当两对应的二进位相异时,结果为1。参与运算数仍以补码出现,例如 9^5可写成算式如下: 00001001^00000101 00001100 (十进制为12) main(){ int a=9; a=a^15;

printf(\}

4. 求反运算求反运算符~为单目运算符,具有右结合性。其功能是对参与运算的数的各二进位按位求反。例如~9的运算为: ~(0000000000001001)结果为:1111111111110110

5. 左移运算左移运算符“<<”是双目运算符。其功能把“<< ”左边的运算数的各二进位全部左移若干位,由“<<”右边的数指定移动的位数,高位丢弃,低位补0。例如: a<<4 指把a的各二进位向左移动4位。如a=00000011(十进制3),左移4位后为00110000(十进制48)。

6. 右移运算右移运算符“>>”是双目运算符。其功能是把“>> ”左边的运算数的各二进位全部右移若干位,“>>”右边的数指定移动的位数。例如:设 a=15,a>>2 表示把000001111右移为00000011(十进制3)。应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补0,而为负数时,符号位为1,最高位是补0或是补1 取决于编译系统的规定。 main(){

unsigned a,b;

printf(\scanf(\b=a>>5; b=b&15;

printf(\}

请再看一例!

main(){

char a='a',b='b'; int p,c,d; p=a;

p=(p<<8)|b; d=p&0xff;

c=(p&0xff00)>>8;

printf(\}

当进行按位与或时,最好使用16进制,在程序中这样表示:0x01 表示0000 0001

所以,字符类型a的最高位强制1可以这样:a=a|0x80。其他的可以依次类推!

C++移位运算符

关于逻辑移位、算术移位可参见迅雷深大笔试题部分。的一道题。 以前看到C++标准上说,移位运算符(<<、>>)出界时的行为并不确定:

The behavior is undefined if the right operand is negative, orgreater than or equal to the length in bits of the promoted left operand.

我当时也没有深究过这个问题。前几天有个网友来信问起这件事,我才发现,这和IntelCPU的移位运算有关。下面是那位网友的来信以及我的回复:

您好!运算符<<作为位操作中的高效的操作,但我遇到一个问题:下面在VC环境下发现一个很不明白的地方,下面标注。 #include void main() {

unsigned int i,j; i=35;

//为什么下面两个左移操作结果不一样? j=1<

不知是哪里没有理解对。

原因是这样的:i=35;j=1<

mov dword ptr [i],23h mov eax,1

mov ecx,dword ptr [i] shl eax,cl

mov dword ptr [j],eax

在shl一句中,eax=1,cl=35。而IntelCPU执行shl指令时,会先将cl与31进行and操作,以限制左移的次数小于等于31。因为35 & 31 =3,所以这样的指令相当于将1左移3位,结果是8。

而j=1<<35;一句是常数运算,VC即使不做优化,编译器也会直接计算1<<35的结果。VC编译器发现35大于31时,就会直接将结果设置为0。这行代码编译产生的机器指令是: mov dword ptr [j],0

对上面这两种情况,如果把VC编译器的优化开关打开(比如编译成Release版本),编译器都会直接将结果设置为0。

所以,在C/C++语言中,移位操作不要超过界限,否则,结果是不可预期的。 下面是Intel文档中关于shl指令限制移位次数的说明:

The destination operand can be a register or a memory location.The count operand can be an immediate value or register CL. The count is maskedto 5 bits, which limits the count range to 0 to 31. A special opcode encodingis provided for a count of 1. 1.掩码

就是一串2进制对目标字段进行位与运算,屏蔽当前的输入位。

将源码与掩码经过逻辑运算得出新的操作数。其中要用到逻辑运算如OR运算。AND运算。用于如将ASCLL码中大写字母改作小写字母。 2.与或异或转换成补码运算 3. 用法:掩码(&) 4. 用法:打开位(|) 5.用法:关闭位(&~) 6. 用法:转置位(^)

7. 将Value的第bit_number位置1 Value |= 1 << bit_number; 8. 将Value的第bit_number位置0 Value &= ~( 1 << bit_number ); 9.value & 1 << bit_number 如果该位置已被置为1,则表达式的结果为非零值

C/C ++提供位逻辑运算符和移位运算符。二者只能用于整形和字符型。位运算符是对每位进行操作而不影响左右两位,这有别于常规运算符(&&|| !)是将整个数进行操作的。 一. 位逻辑运算符 1. ~ 按位取反 将1变为0,将0变为1 EG: ~(10011010) (01100101) 注:

VC++编译器,计算~10,得出的结果是-11。为什么不是5呢

10的二进制表示为1010,按位取反应该为0101,也就是十进制的5,为什么会得出-11?

VC是32位编译器,所以

10 = 00000000 00000000 00000000 00001010

~10 = 11111111 11111111 11111111 11110101 = -11 可以通过掩码(位与) 与15位与

15 = 00000000 00000000 00000000 00001111 ~10 = 00000000 00000000 00000000 00000101 = -11 2. & 按位取与

只有两个操作数都是1结果才是1,否则为0 10 = 00000000 00000000 00000000 00001010 12 = 00000000 00000000 00000000 00001100 &

8 = 00000000 00000000 00000000 00001000 3. | 按位取或

两个操作数任意一位为1结果就是1

10 = 00000000 00000000 00000000 00001010 12 = 00000000 00000000 00000000 00001100 |

14 = 00000000 00000000 00000000 00001110

4. ^ 按位异或

两个操作数不同为1,相同为0

10 = 00000000 00000000 00000000 00001010 12 = 00000000 00000000 00000000 00001100 ^

14 = 00000000 00000000 00000000 00000110

5. 用法:掩码

掩码是通过&(位与)将某些位设置为开(1),将某些位设置为关(0)。将掩码0看做不透明,将1看着透明。 EG:

如只显示第二、三位 107 = 0110 1011 6 = 0000 0110 &

2 = 0000 0010

6. 用法:打开位

打开位是通过 |(位或)打开一个值的特定位,同时保持其他位的不变。这是因为和0位或都为0,和1位或都为1。 EG:

如只打开第二、三位 107 = 0110 1011 6 = 0000 0110 |

111 = 0110 1111 7. 用法:关闭位 关闭某些位 EG:

如关闭第二、三位 107 = 0110 1011 6 = 0000 0110 & ~

105 = 0110 1001 8. 用法:转置位

如果一位为1则转置为0,如果一位为1则转置为0 EG:

如转置第二、三位 107 = 0110 1011 6 = 0000 0110 ^

105 = 0110 1101

二. 移位运算符

1. << 左移

左移运算符是把操作数的值的每一位向左移动,移动的位数有右边的操作数决定,右侧空出的位数用0填充 EG:

如转置第二、三位 107 = 0110 1011 <<2

<<

172 = 1010 1100

在计算机中由于是32位的

107 = 0000 0000 0000 0000 0000 0000 0110 1011 <<2 <<

428 = 0000 0000 0000 0000 0000 0001 1010 1100

1. >> 右移

右移运算符是把操作数的值的每一位向右移动,移动的位数有右边的操作数决定,左边丢弃的位数用0填充 EG:

如转置第二、三位 107 = 0110 1011 >>2 >>

26 = 0001 1010

一、传统的C方式位操作: 1.基本操作:

使用一个unsigned int变量来作为位容器。 2.操作符:

| 按位或操作符:result=exp1|exp2;当exp1和exp2中对应位中至少有一个为1时,result中对应位为1,否则为0。

& 按位与操作符::result=exp1&exp2;当exp1和exp2中对应位全为1时,result中对应位为1,否则为0。

^ 按位异或或操作符:result=exp1^exp2;当exp1和exp2中对应位不相同时,result中对应位为1,否则为0。

~ 反转操作符:将位容器中的所有位都反转,1变为0,0变为1。

<<按位左移操作符:exp<>按位右移操作符:exp>>n,将容器中所有的位向右移n位,空出的位用0填充。 |=,&=,^= 分别对应|&^三种操作符的复合操作符。 3.常用操作

这里我们假设有一个result的unsigned int变量用来储存32个学生的成绩(通过和不通过分别用0和1),这样result就有33位(result从右至左,从0开始计算位数,在这个例子中0位被浪费)。 (a) 将第27位设置为及格(设作1)其他位不变: result|=(1<<27) //任意的位值与1作按位或操作其值为1,而与0作按位与操作其值不变 (b) 将第27位设置成不及格(设为0)。 result&=~(1<<27) //任意的位值与0作按位与操作其值为0,而与1作按位与操作其值不变 (c) 反转第27位的值。 result^=(1<<27) //任意的位值与1作按位异或操作其值为1,而与0作按位异与操作其值不变 二、C++中的bitset容器 1.头文件: #include 2.声明一个容器: (a)声明一个指定位数的空容器(所有位设为0): bitset bits; (b)声明一个指定位数并将指定的几个位初始化为相应值的容器: bitset bits(int); bitdet bits(string&) 总结:bitset模板类中类型参数传递容器的位数,而构造函数参数通过一个int或一个string&值来从右至左初始化容器中的相应值。 3.bitset的基本用法: 操作 功能 用法 test(pos) pos位是否为1? a.test(4) any() 任意位是否为1? a.any() none() 是否没有位为1? a.none() count() 值是1的位的小数 count() size() 位元素的个数 size() [pos] 访问pos位 a[4] flip() 翻转所有位 a.flip() flip(pos) 翻转pos位 a.flip(4) set() 将所有位置1 a.set() set(pos) 将pos位置1 a.set(4) reset() 将所有位置0 a.reset() reset(pos) 将pos位置0 a.reset(4) 4.bitset与传统C位操作及字符串的转换

可以通过to_string()成员将容器转输出为一个string字符串,另外还可以用to_long()成员将容器输出到传统的用于C风格的位容器中。如: unsigned long bits = bits.to_long(); sting str(bits.to_string());

本文来源:https://www.bwwdw.com/article/jam3.html

Top