C语言位运算

         位运算一直都是初学C语言的难点内容,虽然只有与、或、异或、非、左移和右移6种操作,但是它们的搭配使用才是最常见的,单纯的使用一种为运算还是比较少且简单的情况。举例来说,“不用加法实现加法”、“不用减法实现减法”、“求一个整数的二进制中1的个数”、“实现bitmap”……

         下面是我以C语言为基础总结的位运算的一些细小知识点,后面附有上述问题的解决代码,希望对初学C语言的朋友们有一点帮助。

 

1.      位运算符中除了~以外,均为二目运算符,即要求两侧各有一个运算量。运算量只能是整型或字符型的数据,不能为实型数据。


2.      位运算都是对补码进行运算。


3.      按位与(&)运算符的作用

a)      清零。将待清零位与0进行按位与即可。

b)     取一个数中某些指定位或保留指定位。将指定位与1进行按位与,而其他位与0按位与即可。

c)      统计整型数据二进制位中1的个数(见后附代码)

d)      判断奇偶

Int a = 7, b = 0;

b = a & 1;  //结果为1则为奇数,为0则为偶数


4.      按位或(|)运算符的作用

a)      指定某些位为1。将指定位与1进行按位或即可


5.      按位异或(^)运算符的作用

a)      使特定位翻转。将特定位与1进行按位异或即可。

b)     与0异或保留原值。

c)      当一组数据中只有一个数据有奇数个,其他数据都有偶数个,找到这个有奇数个的数据

int nArray[]={11,22,33,11,22,44,55,22,33,44,55,22,33,55,55};

intnResult = 0;

intnSize = sizeof(nArray)/sizeof(nArray [0]);

    int I = 0;

for(i=0; i< nSize; ++i)

         nResult = nResult ^ nArray [i];//自己和自己异或为0,0再与其他数据异或,其他数据保持不变,最后就只剩下奇数个的数据被保留在nResult中

    printf("%d\n", nResult);//输出33

d)     交换两个值,不用临时变量。

例如:intnValue1 = 3,;

intnValue2 = 4;

              nValue1 = nValue1 ^ nValue2;

//相当于nValue2= nValue1 ^ nValue2 ^ nValue2 = nValue1 ^ 0 = nValue1

              nValue2 = nValue1 ^ nValue2;

//相当于nValue1= nValue1^ nValue2^ nValue1= nValue2^0 = nValue2

              nValue1 = nValue1 ^ nValue2;

即最后交换了nValue1和nValu2的值。

当然,也可以不用位操作和临时变量

如:intnValue1 = 3,;

intnValue2 = 4;

              nValue1 = nValue1 + nValue2;

//相当于nValue2= nValue1 +nValue2 - nValue2 =nValue1

              nValue2 = nValue1 - nValue2;

//相当于nValue1= nValue1+ nValue2- nValue1 = nValue2

              nValue1 = nValue1 - nValue2;


6.      按位取反(~)运算符的作用

a)      将数据最后一位置零

例如:a= a&~1     //~的优先级大于&的优先级

b)对一个整型数据按位取反,所有位都进行了取反操作,包括符号位。(如:inta = -1;a= ~a;结果a的值为0)


7.      左移运算符(<<)和右移运算符(>>)

a)      左移1位相当于该数乘以2^1,左移n位相当于该数乘以2^n。但此结论只适用于该数左移时被溢出舍弃的高位中不包含1的情况。左移时高位溢出舍弃,低位补0。

b)     右移时,如果是无符号类型,低位溢出舍弃,高位补0;如果是有符号类型,低位溢出舍弃,高位补充符号位(即正数补0,负数补1)。此外,对于不同的系统来说,当符号位为1,右移时,有的系统在高位补0,有的系统在高位补1。补0的称为“逻辑右移”,即简单右移;补1的称为“算术右移”。

c)      循环右移

intLoopRight(int a, int n)//数据a右移n位,低位溢出的n位放到高位前n位

{

    int i,j;

    i = (unsigned int)a >> n;//a右移n位高位前n位为0

          j = a << (32 - n);//a左移(32-n)位,将低n位的数据移到高位的前n位处

         return i | j;//按位或运算后,即将低位溢出的n为放到了高位的前n位处。因为,i高位的前n位为0,其他位为a中原来的数据,保持不变,而j高位的前n位是a溢出的低n位,其他位为0.

}


8.      如果两个数据长度不同(例如long型占8个字节和int型占4个字节)进行位运算时(如a& b,而a为long型,b为int型),系统会将二者按右端对齐。如果b为正数,则左端32位补满0;若b为负数,左端应补满1;如果b为无符号整型,则左端补满0。


9.      位段

a)      位段成员的类型必须指定位unsigned或int类型。

例如:struct  packed_data

              {

                            unsigned a:2;

                            unsigned b:6;

                            unsigned c:7;

                            unsigned d:2;

                            int I;

};

              packed_datadata;

              data.a= 2;

注意:a占2位,最大值为3.如果大于3将自动取赋予它的数的低位。即如果给data.a赋8,data.a的值将为0.

b)     若某一位段要从另一个字开始存放,可以用以下形式定义:

unsigneda:2;

unsignedb:6;

unsigned:0;

unsignedc:2;

本来a、b、c应连续存放在一个存储单元(字)中,由于用了长度为0的位段,其作用是使下一个位段从下一个存储单元开始存放。因此,现在只将a、b存储在一个存储单元中,c另存放在下一个单元(上述“存储单元”可能是一个字节,也可能是两个字节,视不同的编译系统而异)。

c)       一个位段必须存储在同一存储单元中,不能跨两个单元。如果第一个单元空间不能容纳下一个位段,则该空间不用,而从下一个单元起存放该位段。

d)     可以定义无名位段

例如:unsigneda:2;

unsignedb:6;

unsigned:6;//这6位空间空着,不用来存储数据

unsignedc:2;

e)     位段的长度不能大于存储单元的长度,也不能定义位段数组。

f)       位段可以用整型格式符输出。

例如:printf(“%d,%d,%d\n”,data.a, data.b, data.c);

当然,也可以用%u、%o、%x等格式符输出。

g)      位段可以在数值表达式中引用,它会被系统自动地转换成整型数据。

 

程序实例

#include 
    
    
     
     
 
//函数声明
int Add(int nValue1, int nValue2);
int Sub(int nValue1, int nValue2);
int Avg(int nValue1, int nValue2);
int NumOf1(int nValue);
 
int main (int argc, char **argv)
{
         intnValue1 = 0;
         intnValue2 = 0;
 
         printf("请输入参加运算的两个整数\n");
 
         printf("nValue1:");
         scanf("%d",&nValue1);
         printf("nValue2:");
         scanf("%d",&nValue2);
 
         printf("%d+ %d = %d\n", nValue1, nValue2, Add(nValue1, nValue2));
         printf("%d- %d = %d\n", nValue1, nValue2, Sub(nValue1, nValue2));
         printf("(%d+ %d)/2 = %d\n", nValue1, nValue2, Avg(nValue1, nValue2));
         printf("%d的二进制中1的个数为:%d\n", nValue1, NumOf1(nValue1));
 
         return0;
}
 
//加法
int Add(int nValue1, int nValue2)
{
         intnSum = 0;
         intnCarry = 0;
 
         do
         {
                   nSum= nValue1 ^ nValue2;//将二进制每位相加的结果保存到nSum中(不考虑进位)
                   nCarry= (nValue1 & nValue2) << 1;//将进位保存到nCarry中
 
                   nValue1= nSum;
                   nValue2= nCarry;
 
         }while(0!= nCarry);//循环计算,直到进位为0
 
         returnnSum;
}
 
//减法
int Sub(int nValue1, int nValue2)
{
         returnAdd(nValue1, ~nValue2+1);
}
 
//求两个数的平均数
int Avg(int nValue1, int nValue2)
{
         return(((nValue1 ^ nValue2) >> 1) + (nValue1 & nValue2));
}
 
//计算二进制1的个数
int NumOf1(int nValue)
{
         intnCount = 0;//计数器
 
         while(nValue)
         {
                   ++nCount;
                   nValue= (nValue - 1) & nValue;//每次将二进制最左边的1变为0
         }
 
         returnnCount;
}
    
    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值