C位操作介绍

  • Post author:
  • Post category:其他


C++位操作包括两种:传统的C语言方式的位操作和C++中利用bitset容器的位操作


一、传统的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<<n,将容器中所有的位向左移n位,空出的位用0填充。

>> 按位右移操作符: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 <bitset>



2.声明一个容器:



(a)声明一个指定位数的空容器(所有位设为0): bitset<int> bits;



(b)声明一个指定位数并将指定的几个位初始化为相应值的容器: bitset<n> bits(int);



bitdet<int> bits(string&)



总结:bitset模板类中类型参数传递容器的位数,而构造函数参数通过一个int或一个string&值来从右至左初始化容器中的相应值。



3.bitset的基本用法:



操作                            功能                                   用法



test(pos)                       pos位是否为1                    a.test(4)



any()                            任意位是否为1                   a.any()



none()                          是否没有位为1                   a.none()



count()                         值是1的位的小数              a.count()



size()                           位元素的个数                     a.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());





C语言中常见的置位操作




如何对某一位置0或者置1?

方法一:

写成宏,方便移植


#define setbit(x,y) x|=(1<<y) //将X的第Y位置1

#define clrbit(x,y) x&=!(1<<y) //将X的第Y位清0

方法二:

C语言位运算除了可以提高运算效率外,在嵌入式系统的编程中,它的另一个最典型的应用,而且十分广泛地正在被使用着的是位间的与(&)、或(|)、非(~)操作,这跟嵌入式系统的编程特点有很大关系。我们通常要对硬件寄存器进行位设置

譬如,我们通过将AM186ER型80186处理器的中断屏蔽控制寄存器的

第低6位设置为0(开中断2),最通用的做法是:


#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

outword(INT_MASK, wTemp &~INT_I2_MASK);


而将该位设置为1的做法是:


#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

outword(INT_MASK, wTemp | INT_I2_MASK);


判断该位是否为1的做法是:


#define INT_I2_MASK 0x0040

wTemp = inword(INT_MASK);

if(wTemp & INT_I2_MASK)

{


… /* 该位为1 */

}


方法三:

int a|=(1<<x) //X就是某位需要置1的数字,如第四位置1为: a|=(1<<4)

int b&=~(1<<x) //把某位置0

x=x|0x0100    //把第三位置1

x=x&0x1011    //把第三位置0

#define BitGet(Number,pos) ((Number) >> (pos)&1)) //用宏得到某数的某位

#define BitGet(Number,pos) ((Number) |= 1<<(pos)) //把某位置1

#define BitGet(Number,pos) ((Number) &= ~(1<<(pos)) //把某位置0

#define BitGet(Number,pos) ((Number) ^= 1<<(pos)) //把Number的POS位取反

典型操作有:

WTCON |=  (1 << 5) //WTCON的第五位清1

WTCON &= ~(1 << 5) //WTCON的第五位清0

上述方法在嵌入式系统的编程中是非常常见的,我们需要牢固掌握

C/C++位操作技巧  检测一个无符号数是不为2^n-1(^为幂):   x&(x+1)

将最右侧0位改为1位:   x   |   (x+1)

二进制补码运算公式:

-x   =   ~x   +   1   =   ~(x-1)

~x   =   -x-1

-(~x)   =   x+1

~(-x)   =   x-1

x+y   =   x   –   ~y   –   1   =   (x|y)+(x&y)

x-y   =   x   +   ~y   +   1   =   (x|~y)-(~x&y)

x^y   =   (x|y)-(x&y)

x|y   =   (x&~y)+y

x&y   =   (~x|y)-~x

x==y:         ~(x-y|y-x)

x!=y:         x-y|y-x

x<   y:         (x-y)^((x^y)&((x-y)^x))

x<=y:         (x|~y)&((x^y)|~(y-x))

x<   y:         (~x&y)|((~x|y)&(x-y))//无符号x,y比较

x<=y:         (~x|y)&((x^y)|~(y-x))//无符号x,y比较

使用位运算的无分支代码:

计算绝对值

int   abs(   int   x   )

{

int   y   ;

y   =   x   >>   31   ;

return   (x^y)-y   ;//or:   (x+y)^y

}

符号函数:sign(x)   =   -1,   x<0;   0,   x   ==   0   ;   1,   x   >   0

int   sign(int   x)

{

return   (x>>31)   |   (unsigned(-x))>>31   ;//x=-2^31时失败(^为幂)

}

三值比较:cmp(x,y)   =   -1,   x<y;   0,   x==y;   1,   x   >   y

int   cmp(   int   x,   int   y   )

{

return   (x>y)-(x-y)   ;

}

doz=x-y,   x>=y;   0,   x<y

int   doz(int   x,   int   y   )

{

int   d   ;

d   =   x-y   ;

return   d   &   ((~(d^((x^y)&(d^x))))>>31)   ;

}

int   max(int   x,   int   y   )

{

int   m   ;

m   =   (x-y)>>31   ;

return   y   &   m   |   x   &   ~m   ;

}

不使用第三方交换x,y:

1.x   ^=   y   ;   y   ^=   x   ;   x   ^=   y   ;

2.x   =   x+y   ;   y   =   x-y   ;   x   =   x-y   ;

3.x   =   x-y   ;   y   =   y+x   ;   x   =   y-x   ;

4.x   =   y-x   ;   x   =   y-x   ;   x   =   x+y   ;

双值交换:x   =   a,   x==b;   b,   x==a//常规编码为x   =   x==a   ?   b   :a   ;

1.x   =   a+b-x   ;

2.x   =   a^b^x   ;

下舍入到2的k次方的倍数:

1.x   &   ((-1)<<k)

2.(((unsigned)x)>>k)<<k

上舍入:

1.   t   =   (1<<k)-1   ;   x   =   (x+t)&~t   ;

2.t   =   (-1)<<k   ;   x   =   (x-t-1)&t   ;

位计数,统计1位的数量:

1.

int   pop(unsigned   x)

{

x   =   x-((x>>1)&0x55555555)   ;

x   =   (x&0x33333333)   +   ((x>>2)   &   0x33333333   )   ;

x   =   (x+(x>>4))   &   0x0f0f0f0f   ;

x   =   x   +   (x>>8)   ;

x   =   x   +   (x>>16)   ;

return   x   &   0x0000003f   ;

}

2.

int   pop(unsigned   x)   {

static   char   table[256]   =   {   0,1,1,2,   1,2,2,3,   ….,   6,7,7,8   }   ;

return   table[x&0xff]+table[(x>>8)&0xff]+table[(x>>16)&0xff]+table[(x>>24)]   ;

}

奇偶性计算:

x   =   x   ^   (   x>>1   )   ;

x   =   x   ^   (   x>>2   )   ;

x   =   x   ^   (   x>>4   )   ;

x   =   x   ^   (   x>>8   )   ;

x   =   x   ^   (   x>>16   )   ;

结果中位于x最低位,对无符号x,结果的第i位是原数第i位到最左侧位的奇偶性

位反转:

unsigned   rev(unsigned   x)

{

x   =   (x   &   0x55555555)   <<   1   |   (x>>1)   &   0x55555555   ;

x   =   (x   &   0x33333333)   <<   2   |   (x>>2)   &   0x33333333   ;

x   =   (x   &   0x0f0f0f0f)   <<   4   |   (x>>4)   &   0x0f0f0f0f   ;

x   =   (x<<24)   |   ((x&0xff00)<<8)   |   ((x>>8)   &   0xff00)   |   (x>>24)   ;

return   x   ;

}

递增位反转后的数:

unsigned   inc_r(unsigned   x)

{

unsigned   m   =   0x80000000   ;

x   ^=   m   ;

if(   (int)x   >=   0   )

do   {   m   >>=   1   ;   x   ^=   m   ;   }   while(   x   <   m   )   ;

return   x   ;

}

混选位:

abcd   efgh   ijkl   mnop   ABCD   EFGH   IJKL   MNOP->aAbB   cCdD   eEfF   gGhH   iIjJ   kKlL   mMnN   oOpP

unsigned   ps(unsigned   x)

{

unsigned   t   ;

t   =   (x   ^   (x>>8))   &   0x0000ff00;   x   =   x   ^   t   ^   (t<<8)   ;

t   =   (x   ^   (x>>4))   &   0x00f000f0;   x   =   x   ^   t   ^   (t<<4)   ;

t   =   (x   ^   (x>>2))   &   0x0c0c0c0c;   x   =   x   ^   t   ^   (t<<2)   ;

t   =   (x   ^   (x>>1))   &   0x22222222;   x   =   x   ^   t   ^   (t<<1)   ;

return   x   ;

}

位压缩:

选择并右移字x中对应于掩码m的1位的位,如:compress(abcdefgh,01010101)=0000bdfh

compress_left(x,m)操作与此类似,但结果位在左边:   bdfh0000.

unsigned   compress(unsigned   x,   unsigned   m)

{

unsigned   mk,   mp,   mv,   t   ;

int   i   ;

x   &=   m   ;

mk   =   ~m   <<   1   ;

for(   i   =   0   ;   i   <   5   ;   ++i   )   {

mp   =   mk   ^   (   mk   <<   1)   ;

mp   ^=   (   mp   <<   2   )   ;

mp   ^=   (   mp   <<   4   )   ;

mp   ^=   (   mp   <<   8   )   ;

mp   ^=   (   mp   <<   16   )   ;

mv   =   mp   &   m   ;

m   =   m   ^   mv   |   (mv   >>   (1<<i)   )   ;

t   =   x   &   mv   ;

x     =   x   ^   t   |   (   t   >>   (   1<<i)   )   ;

mk   =   mk   &   ~mp   ;

}

return   x   ;

}

位置换:

用32个5位数表示从最低位开始的位的目标位置,结果是一个32*5的位矩阵,

将该矩阵沿次对角线转置后用5个32位字p[5]存放。

SAG(x,m)   =   compress_left(x,m)   |   compress(x,~m)   ;

准备工作:

void   init(   unsigned   *p   )   {

p[1]   =   SAG(   p[1],   p[0]   )   ;

p[2]   =   SAG(   SAG(   p[2],   p[0]),   p[1]   )   ;

p[3]   =   SAG(   SAG(   SAG(   p[3],   p[0]   ),   p[1]),   p[2]   )   ;

p[4]   =   SAG(   SAG(   SAG(   SAG(   p[4],   p[0]   ),   p[1])   ,p[2]),   p[3]   )   ;

}

实际置换:

int   rep(   unsigned   x   )   {

x   =   SAG(x,p[0]);

x   =   SAG(x,p[1]);

x   =   SAG(x,p[2]);

x   =   SAG(x,p[3]);

x   =   SAG(x,p[4]);

return   x   ;

}

二进制码到GRAY码的转换:

unsigned   B2G(unsigned   B   )

{

return   B   ^   (B>>1)   ;

}

GRAY码到二进制码:

unsigned   G2B(unsigned   G)

{

unsigned   B   ;

B   =   G   ^   (G>>1)   ;

B   =   G   ^   (G>>2)   ;

B   =   G   ^   (G>>4)   ;

B   =   G   ^   (G>>8)   ;

B   =   G   ^   (G>>16)   ;

return   B   ;

}

找出最左0字节的位置:

int   zbytel(   unsigned   x   )

{

static   cahr   table[16]   =   {   4,3,2,2,   1,1,1,1,   0,0,0,0,   0,0,0,0   }   ;

unsigned   y   ;

y   =   (x&0x7f7f7f7f)   +   0x7f7f7f7f   ;

y   =   ~(y|x|0x7f7f7f7f)   ;

return   table[y*0x00204081   >>   28]   ;//乘法可用移位和加完成

}

在第一节概述里就说了,C语言是一种中级语言,能对计算机硬件直接操作,这就涉及到位的概念。

一、位的概念

我们知道,在计算机中,一字节占8位(现在的某些电脑也有占16位的),这样表示的数的范围为0-255,也即00000000-11111111。位就是里面的0和1。

char c=100;

实际上c应该是01100100,正好是64H。其中高位在前,低位在后。                       |             |

第7位   第0位

二、位逻辑运算符

符号         描述

&           位逻辑与

|           位逻辑或

^           位逻辑异或

~           取补

表中除去最后一个运算符是单目运算符,其他都是双目运算符。这些运算符只能用于整型表达式。位逻辑运算符通常用于对整型变量进行位的设置、清零、取反、以及对某些选定的位进行检测。在程序中一般被程序员用来作为开关标志。较低层次的硬件设备驱动程序,经常需要对输入输出设备进行位操作。

& 运算的规则是当两个位都为1时,结果为1,否则为0;

| 运算的规则是当两个位都为0时,结果为0,否则为1;

^ 运算的规则是当两个位相同时,结果为0,否则为1;

~ 运算的规则是当为1时结果为0,当为0时,结果为1。

设置位:设置某位为1,而其他位保持不变,可以使用位逻辑或运算。

char c;

c=c|0x40;

这样不论c原先是多少,和01000000或以后,总能使第6位为1,而其他位不变。

清除位:设置某位为0,而其他位保持不变。可以使用位逻辑与运算。

c=c&0xBF;

这样c和10111111与以后,总能使第6位为0,其他位保持不变。

那如果想让某位为1,其他位都为0怎么办呢?

三、位移运算符

符号             描述

<<              左移

>>              右移

位移运算符作用于其左侧的变量,其右侧的表达式的值就是移动的位数,运算结果就是移动后的变量结果。

b=a<<2;

就是a的值左移两位并赋值为b。a本身的值并没有改变。

向左移位就是在低位沙锅补0,向右移位就是在高位上补0。右移时可以保持结果的符号位,也就是右移时,如果最高位为1,是符号位,则补1而不是补0。

程序员常常对右移运算符来实现整数除法运算,对左移运算符来实现整数乘法运算。其中用来实现乘法和除法的因子必须是2的幂次。

举例:输入一个整数,判断这个数中有几个二进制位1?例如输入67,输出结果应该为3。因为67的相应二进制数为00000000 01000011(0043H),有3个1出现。

分析:要判断是不是1,只需要判断该位与1与以后是不是1就可以知道。一个整数,判断16次即可。

main()

{


int num,k;

int count=0;               /* 记录1的个数 */

scanf(\”%d\”,&num);

for(k=0;k<16;k++)

{


if(num&1==1) count++;    /* 判断最低位是不是1 */

num>>=1;                 /* num右移1位 */

}

printf(\”%d\\n\”,count);

}

这样每次都判断最低位是不是1,判断完以后,让前面的右移一位即可。

对位的操作,一般程序中用的不多,但是在对计算机硬件操作时,肯定会涉及到。例如,我们以后要讲到的对串口和声卡操作就要用到一些。



单片机的C语言中位操作用法







在对单处机进行编程的过程中,对位的操作是经常遇到的。


C51


对位的操控能力是非常强大的。从这一点上,就可以看出


C


不光具有高级语言的灵活性,又有低级语言贴近硬件的特点。这也是在各个领域中都可以看到


C


的重要原因。在这一节中将详细讲解


C51


中的位操作及其应用。





1


、位运算符




C51


提供了几种位操作符,如下表所示:



运算符



含义



运算符



含义



&



按位与



~



取反



|



按位或



<<



左移



^



按位异或



>>



右移



1


)“按位与”运算符(


&








参加运算的两个数据,按二进位进行“与”运算。原则是全


1





1,





0





0,


即:


0&0=0; 0&1=0; 1&0=0; 1&1=1;





如下例:





a=5&3; //a=(0b 0101) & (0b 0011) =0b 0001 =1






那么如果参加运算的两个数为负数,又该如何算呢?会以其补码形式表示的二进制数来进行与运算。





a=-5&-3; //a=(0b 1011) & (0b1101) =0b 1001 =-7






在实际的应用中与操作经常被用于实现特定的功能:




1.


清零





“按位与”通常被用来使变量中的某一位清零。如下例:





a=0xfe;

//a=0b 11111110




a=a&0x55;



//


使变量


a


的第


1


位、第


3


位、第


5


位、第


7


位清零


a= 0b

01010100





2.


检测位




要知道一个变量中某一位是‘


1





还是‘


0





,可以使用与操作来实现。





a=0xf5;

//a=0b 11110101




result=a&0x08; //


检测


a


的第三位


,result=0





3.


保留变量的某一位





要屏蔽某一个变量的其它位,而保留某些位,也可以使用与操作来实现。




a=0x55; //a=0b 01010101



a=a&0x0f; //


将高四位清零,而保留低四位



a=0x05








2


)“按位或”运算符(|)







参与或操作的两个位,只要有一个为‘


1


’,则结果为‘


1


’。即有‘


1


’为‘


1


’,全‘


0


’为‘


0


’。






0|0=0; 0|1=1; 1|0=1; 1|1=1;





例如:






a=0x30|0x0f; //a=(0b00110000)|(0b00001111)=(0b00111111)=0x3f






“按位或”运算最普遍的应用就是对一个变量的某些位置‘


1





。如下例:




a=0x00; //a=0b 00000000



a=a|0x7f; //





a


的低


7


位置为


1,a=0x7f





3)


“异或”运算符(^)



异或运算符^又被称为


XOR


运算符。当参与运算的两个位相同(‘


1


’与‘


1


’或‘


0


’与‘


0


’)时结果为‘


0


’。不同时为‘


1


’。即相同为


0


,不同为


1









0^0=0; 0^1=1; 1^0=1;1^1=0;





例如:






a=0x55^0x3f; //a=(0b01010101)^(0b00111111)=(0b01101010)=0x6a






异或运算主要有以下几种应用:





1.


翻转某一位



当一个位与‘


1


’作异或运算时结果就为此位翻转后的值。如下例:




a=0x35; //a=0b00110101



a=a^0x0f; //a=0b00111010

a


的低四位翻转




关于异或的这一作用,有一个典型的应用,即取浮点的相反数,具体的实现如下:




f=1.23; //f


为浮点型变量 值为


1.23



f=f*-1; //f


乘以


-1


,实现取其相反数,要进行一次乘运算






f=1.23;



((unsigned char *)&f)[0]^=0x80;

//


将浮点数


f


的符号位进行翻转实现取相反数








2.


保留原值



当一个位与‘


0


’作异或运算时,结果就为此位的值。如下例:




a=0xff; //a=0b11111111



a=a^0x0f; //a=0b11110000





0x0f


作异或,高四位不变,低四位翻转






3.


交换两个变量的值,而不用临时变量



要交换两个变量的值,传统的方法都需要一个临时变量。实现如下:




void swap(unsigned char *pa,unsigned char *pb)



{





unsigned char temp=*pa;//


定义临时变量,将


pa


指向的变量值赋给它




*pa=*pb;




*pb=temp;




//


变量值对调



}






而使用异或的方法来实现,就可以不用临时变量,如下:




void swap_xor(unsigned char *pa,unsigned char *pb)



{





*pa=*pa^*pb;




*pb=*pa^*pb;




*pa=*pa^*pb;

//


采用异或实现变量对调



}






从上例中可以看到异或运算在开发中是非常实用和神奇的。





4


)“取反”运算符(


~






与其它运算符不同,“取反”运算符为单目运算符,即它的操作数只有一个。它的功能就是对操作数按位取反。也就是是‘


1


’得‘


0


’,是‘


0


’得‘


1


’。






~1=0; ~0=1;














如下例:




a=0xff; //a=0b11111111



a=~a; //a=0b00000000





1.


对小于


0


的有符号整型变量取相反数





d=-1;



//d


为有符号整型变量,赋值为


-1


,内存表示为


0b 11111111 11111111




d=~d+1; //





d


的相反数


,d=1,


内存表示


0b 00000000 00000001








此例运用了负整型数在内存以补码方式来存储的这一原理来实现的。负数的补码方式是这样的:负数的绝对值的内存表示取反加


1


,就为此负数的内存表示。如


-23


如果为八位有符号整型数,则其绝对值


23


的内存表示为


0b00010111


,对其取反则为


0b11101000


,再加


1





0b11101001


,即为


0XE9


,与


Keil


仿真结果是相吻合的:




2.


增强可移植性



关于“增强可移植性”用以下实例来讲解:



假如在一种单片机中


unsigned char


类型是八个位(


1


个字节),那么一个此类型的变量


a=0x67


,对其最低位清零。则可以用以下方法:




a=0x67; //a=0b 0110 0111




a=a&0xfe; //a=0b 0110 0110






上面的程序似乎没有什么问题,使用


0xfe


这一因子就可以实现一个


unsigned char


型的变量最低位清零。但如果在另一种单片机中的


unsigned char


类型被定义为


16


个位(两个字节),那么这种方法就会出错,如下:




b=0x6767; //


假设


b


为另一种单片机中的


unsigned char


类型变量,值为


0b 0110 0111 0110 0111




b=b&0xfe; //


如果此时因子仍为


0xfe


的话,则结果就为


0b 0000 0000 0110 0110





0x0066


,而与


0x6766


不相吻合










上例中的问题就是因为不同环境中的数据类型差异所造成的,即程序的可移植性不好。对于这种情况可以采用如下方法来解决:






a=0x67; //a=0b 0110 0111



a=a&~1; //


在不同的环境中


~1


将自动匹配运算因子,实现最后一位清零


a=0x66



其中


~1





0b 11111110



b=0x6767; //a=0b 0110 0111 0110 0111



b=a&~1; //~1=0b 1111 1111 1111 1110





b=0b 0110 0111 0110 0110


,即


0x6766







5


)左移运算符(


<<








左移运算符用来将一个数的各位全部向左移若干位。如:






a=a<<2





表示将


a


的各位左移


2


位,右边补


0


。如果


a=34(0x22





0b00100010)


,左移


2


位得


0b10001000


,即十进制的


136


。高位在左移后溢出,不起作用。



从上例可以看到,


a


被左移


2


位后,由


34


变为了


136


,是原来的


4


倍。而如果左移


1


位,就为


0b01000100


,即十进制的


68


,是原来的


2


倍,很显然,左移


N


位,就等于乘以了


2

N



。但一结论只适用于左移时被溢出的高位中不包含‘



1



’的情况。比如:






a=64; //a=0b 0100 0000



a=a<<2; //a=0b 0000 0000






其实可以这样来想,


a





unsigned char


型变量,值为


64


,左移


2


位后等于乘以了


4


,即


64X4





256


,而此种类型的变量在表达


256


时,就成为了


0x00


,产生了一个进位,即溢出了一个‘


1









在作乘以


2

N



这种操作时,如果使用左移,将比用乘法快得多。因此在程序中适应的使用左移,可以提高程序的运行效率。







6



)右移运算符











右移与左移相类似,只是位移的方向不同。如:









a=a>>1







表示将



a



的各位向右移动



1



位。与左移相对应的,左移一位就相当于除以



2



,右移



N



位,就相当于除以


2

N















在右移的过程中,要注意的一个地方就是符号位问题。对于无符号数右移时左边高位移和‘



0



’。对于有符号数来说,如果原来符号位为‘



0



’,则左边高位为移入‘



0



’,而如果符号位为‘



1



’,则左边移入‘



0



’还是‘



1



’就要看实际的编译器了,移入‘



0



’的称为“逻辑右移”,移入‘



1



’的称为“算术右移”。



Keil



中采用“算术右移”的方式来进行编译。如下:






d=-32; //d


为有符号整型变量,值为


-32


,内存表示为


0b 11100000



d=d>>1;//


右移一位


d





0b 11110000





-16





Keil


采用





算术逻辑





进行编译






7



)位运算赋值运算符





在对一个变量进行了位操作中,要将其结果再赋给该变量,就可以使用位运算赋值运算符。位运算赋值运算符如下:







&=, |=,^=,~=,<<=, >>=








例如:



a&=b



相当于



a=a&b,a>>=2



相当于



a>>=2












8



)不同长度的数据进行位运算





如果参与运算的两个数据的长度不同时,如



a







char



型,



b







int



型,则编译器会将二者按右端补齐。如果



a



为正数,则会在左边补满‘



0



’。若



a



为负数,左边补满‘



1



’。如果



a



为无符号整型,则左边会添满‘



0



’。






a=0x00; //a=0b 00000000



d=0xffff; //d=0b 11111111 11111111



d&=a; //a


为无符号型,左边添


0


,补齐为


0b 00000000 00000000





d=0b 00000000 00000000


[C语言的移位操作]

一,

I=257 = 1 0000 0001  (二进制)

I=257/8 = 32.125 = 32   (I为int类型)

I=257>  >  3

=1 0000 0001>  >    3 = 0…010 0000=2^5=32

右移3位将将最后一个001移除了,1/8=0.125   所以这等价是没问题的



J=456=0…01 1100 1000

J=456%32=8;      (J为int类型)

J=456-(456>  >  4  <  <4);

456>  >  4=0…01 1100 1000>  >  4

=0…01 1100

然后再左移四位,则低四位补0:   0…01 1100 0000

上面两步实际上是将低四位变为了0,也就是由原来的1000变为现在的0000

所以现在数比移位操作前少了1000,也就是8

所以J=456-(456>  >  4  <  <4)=8

这里的求余运算我们还可以这样:

456=0…01 1100 1000=0x1c8

那么求456%32,就只要知道低5位是多大就OK了

所以456%32=0x1c8 & 0x01f,这样从第6位开始就全部变为0,余下的就是所求。

J=456=0…01 1100 1000

从上面的分析就知道

456>  >  3  <  <3比456而言就是低3位 000 变为现在的 000,

嘿嘿,没变,所以 k  = 456 – (456>  >  3  <  <3)=0

456>  >  4  <  <4比456而言就是低4位 1000 变为现在的 0000,

小了8,所以 l  = 456 – (456>  >  4  <  <4)=8

456>  >  5  <  <5比456而言就是低5位 01000 变为现在的 00000,

小了8,所以 m  = 456 – (456>  >  5  <  <5)=8

456>  >  6  <  <6比456而言就是低6位 001000 变为现在的 000000,

小了8,所以 n  = 456 – (456>  >  6  <  <6)=8

456>  >  7  <  <7比456而言就是低7位 1001000 变为现在的 0000000,

小了2^6+2^3=64+8=72,所以 o = 456 – (456>  >  7  <  <7)=72

c++位运算(收藏用)[url=]

楼主

[/url]snowingbf(snowingbf)2004-10-19 00:03:07 在 C/C++ / C++ 语言 提问

前言

看到有些人对位运算还存在问题,于是决定写这篇文章作个简要说明。

什么是位(bit)?

很简单,位(bit)就是单个的0或1,位是我们在计算机上所作一切的基础。计算机上的所有数据都是用位来存储的。一个字节(BYTE)由八个位组成,一个字(WORD)是二个字节或十六位,一个双字(DWORD)是二个字(WORDS)或三十二位。如下所示:

0   1   0   0   0   1   1   1   1   0   0   0   0   1   1   1   0   1   1   1   0   1   0   0   0   1   1   1   1   0   0   0

|   |                             |                               |                               |                             |   |

|   +-   bit   31             |                               |                               |               bit   0   -+   |

|                                 |                               |                               |                                 |

+–   BYTE   3   —-   -+—-   BYTE   2   —+—-   BYTE   1   —+—   BYTE   0   —–+

|                                                                 |                                                                 |

+————   WORD   1   ————+———–   WORD   0   ————-+

|                                                                                                                                   |

+—————————–   DWORD   —————————–+

使用位运算的好处是可以将BYTE,   WORD   或   DWORD   作为小数组或结构使用。通过位运算可以检查位的值或赋值,也可以对整组的位进行运算。

16进制数及其与位的关系

用0或1表示的数值就是二进制数,很难理解。因此用到16进制数。

16进制数用4个位表示0   –   15的值,4个位组成一个16进制数。也把4位成为半字节(nibble)。一个BYTE有二个nibble,因此可以用二个16进制数表示一个BYTE。如下所示:

NIBBLE       HEX   VALUE

======       =========

0000                 0

0001                 1

0010                 2

0011                 3

0100                 4

0101                 5

0110                 6

0111                 7

1000                 8

1001                 9

1010                 A

1011                 B

1100                 C

1101                 D

1110                 E

1111                 F

如果用一个字节存放字母”r”(ASCII码114),结果是:

0111   0010         二进制

7         2           16进制

可以表达为:’0x72′

有6种位运算:

&       与运算

|       或运算

^       异或运算

~       非运算(求补)

>>       右移运算

<<       左移运算

与运算(&)

双目运算。二个位都置位(等于1)时,结果等于1,其它的结果都等于0。

1       &       1       ==       1

1       &       0       ==       0

0       &       1       ==       0

0       &       0       ==       0

与运算的一个用途是检查指定位是否置位(等于1)。例如一个BYTE里有标识位,要检查第4位是否置位,代码如下:

BYTE   b   =   50;

if   (   b   &   0x10   )

cout   <<   “Bit   four   is   set”   <<   endl;

else

cout   <<   “Bit   four   is   clear”   <<   endl;

上述代码可表示为:

00110010     –   b

&   00010000     –   &   0x10

—————————-

00010000     –   result

可以看到第4位是置位了。

或运算(   |   )

双目运算。二个位只要有一个位置位,结果就等于1。二个位都为0时,结果为0。

1       |       1       ==       1

1       |       0       ==       1

0       |       1       ==       1

0       |       0       ==       0

与运算也可以用来检查置位。例如要检查某个值的第3位是否置位:

BYTE   b   =   50;

BYTE   c   =   b   |   0x04;

cout   <<   “c   =   ”   <<   c   <<   endl;

可表达为:

00110010     –   b

|   00000100     –   |   0x04

———-

00110110     –   result

异或运算(^)

双目运算。二个位不相等时,结果为1,否则为0。

1       ^       1       ==       0

1       ^       0       ==       1

0       ^       1       ==       1

0       ^       0       ==       0

异或运算可用于位值翻转。例如将第3位与第4位的值翻转:

BYTE   b   =   50;

cout   <<   “b   =   ”   <<   b   <<   endl;

b   =   b   ^   0x18;

cout   <<   “b   =   ”   <<   b   <<   endl;

b   =   b   ^   0x18;

cout   <<   “b   =   ”   <<   b   <<   endl;

可表达为:

00110010     –   b

^   00011000     –   ^0x18

———-

00101010     –   result

00101010     –   b

^   00011000     –   ^0x18

———-

00110010     –   result

非运算(~)

单目运算。位值取反,置0为1,或置1为0。非运算的用途是将指定位清0,其余位置1。非运算与数值大小无关。例如将第1位和第2位清0,其余位置1:

BYTE   b   =   ~0x03;

cout   <<   “b   =   ”   <<   b   <<   endl;

WORD   w   =   ~0x03;

cout   <<   “w   =   ”   <<   w   <<   endl;

可表达为:

00000011     –   0x03

11111100     –   ~0x03     b

0000000000000011     –   0x03

1111111111111100     –   ~0x03     w

非运算和与运算结合,可以确保将指定为清0。如将第4位清0:

BYTE   b   =   50;

cout   <<   “b   =   ”   <<   b   <<   endl;

BYTE   c   =   b   &   ~0x10;

cout   <<   “c   =   ”   <<   c   <<   endl;

可表达为:

00110010     –   b

&   11101111     –   ~0x10

———-

00100010     –   result

移位运算(>>   与   <<)

将位值向一个方向移动指定的位数。右移   >>   算子从高位向低位移动,左移   <<   算子从低位向高位移动。往往用位移来对齐位的排列(如MAKEWPARAM,   HIWORD,   LOWORD   宏的功能)。

BYTE   b   =   12;

cout   <<   “b   =   ”   <<   b   <<   endl;

BYTE   c   =   b   <<   2;

cout   <<   “c   =   ”   <<   c   <<   endl;

c   =   b   >>   2;

cout   <<   “c   =   ”   <<   c   <<   endl;

可表达为:

00001100     –   b

00110000     –   b   <<   2

00000011     –   b   >>   2

译注:以上示例都对,但举例用法未必恰当。请阅文末链接的文章,解释得较为清楚。

位域(Bit   Field)

位操作中的一件有意义的事是位域。利用位域可以用BYTE,   WORD或DWORD来创建最小化的数据结构。例如要保存日期数据,并尽可能减少内存占用,就可以声明这样的结构:

struct   date_struct   {

BYTE       day       :   5,       //   1   to   31

month   :   4,       //   1   to   12

year     :   14;     //   0   to   9999

}date;

在结构中,日期数据占用最低5位,月份占用4位,年占用14位。这样整个日期数据只需占用23位,即3个字节。忽略第24位。如果用整数来表达各个域,整个结构要占用12个字节。

|   0   0   0   0   0   0   0   0   |   0   0   0   0   0   0   0   0   |   0   0   0   0   0   0   0   0   |

|                                                           |                   |                     |

+————-   year   ————–+   month+–   day   –+

现在分别看看在这个结构声明中发生了什么

首先看一下位域结构使用的数据类型。这里用的是BYTE。1个BYTE有8个位,编译器将分配1个BYTE的内存。如果结构内的数据超过8位,编译器就再分配1个BYTE,直到满足数据要求。如果用WORD或DWORD作结构的数据类型,编译器就分配一个完整的32位内存给结构。

其次看一下域声明。变量(day,   month,   year)名跟随一个冒号,冒号后是变量占用的位数。位域之间用逗号分隔,用分号结束。

使用了位域结构,就可以方便地象处理普通结构数据那样处理成员数据。尽管我们无法得到位域的地址,却可以使用结构地址。例如:

date.day   =   12;

dateptr   =   &date;

dateptr->year   =   1852;




版权声明:本文为lizhifa2011原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。