您的位置:首页 > 其它

C位操作介绍

2014-07-04 15:14 579 查看
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位,就等于乘以了2N。但一结论只适用于左移时被溢出的高位中不包含‘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’。

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

6)右移运算符

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

                 

a=a>>1

表示将a的各位向右移动1位。与左移相对应的,左移一位就相当于除以2,右移N位,就相当于除以2N。

         
在右移的过程中,要注意的一个地方就是符号位问题。对于无符号数右移时左边高位移和‘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++位运算(收藏用)楼主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;

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: