C语言位运算总结

4 篇文章 0 订阅
1 篇文章 0 订阅

目录

目录

一、引言

二、位逻辑运算和移位运算

1、按位与(&)

2、按位或(|)

3、按位异或(^)

4、取反(~)

5、左移操作(<<)

6、右移操作(>>)

三、常识与技巧

1、判断某一位是0还是1

2、任意数与0异或保持不变

3、任意数与本身异或为0

四、数据结构练习题

1、消失的数字

2、数组中数字出现的次数


一、引言

程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算说白了,就是直接对整数在内存中的二进制位进行操作。运位算包括位逻辑运算移位运算,位逻辑运算能够方便地设置或屏蔽内存中某个字节的一个二进制位或几个二进制位,也可以对两个数按位相加等;移位运算可以对内存中某个二进制数左移或右移几位等。需要注意的一点是计算机中的数值是以补码的形式存储的。。。

二、位逻辑运算移位运算

1、按位与(&)

运算规则:参加运算的两个数,如果两个数相应位的值都是1,则该位的结果值为1,否则为0。即:0 & 0 =0;0 & 1 =0;1 & 0 =0;1 & 1 =1。

# include<stdio.h>

int main(){

	int a = 11;
	//二进制表示:0000 0000 0000 0000 0000 0000 0000 1011
	int b = 21;
	//二进制表示:0000 0000 0000 0000 0000 0000 0001 0101

	//a&b:       0000 0000 0000 0000 0000 0000 0000 0001
	printf("%d\n",a&b);
	return 0;
}

输出结果:

1
请按任意键继续. . .

2、按位或(|)

运算规则:参加运算的两个数,如果两个数相应位的值至少有一个为1,则该位的结果值为1,否则为0。即:0 | 0 =0;0 | 1 =1;1 | 0 =1;1 | 1 =1。

# include<stdio.h>

int main(){

	int a = 11;
	//二进制表示:0000 0000 0000 0000 0000 0000 0000 1011
	int b = 21;
	//二进制表示:0000 0000 0000 0000 0000 0000 0001 0101

	//a|b:        0000 0000 0000 0000 0000 0000 0001 1111
	printf("%d\n",a|b);
	return 0;
}

代码结果:

31
请按任意键继续. . .

3、按位异或(^)

运算规则:参加运算的两个数,如果两个数相应位的值不同,则该位的结果值为1,否则为0。即:0 | 0 =0;0 | 1 =1;1 | 0 =1;1 | 1 =0。

# include<stdio.h>

int main(){

	int a = 11;
	//二进制表示:0000 0000 0000 0000 0000 0000 0000 1011
	int b = 21;
	//二进制表示:0000 0000 0000 0000 0000 0000 0001 0101

	//a^b:        0000 0000 0000 0000 0000 0000 0001 1110
	printf("%d\n",a^b);
	return 0;
}

代码结果:

30
请按任意键继续. . .

4、取反(~)

运算规则:参加运算数,如果该数相应位为0,则该位的结果值为1,如果该数相应位为1,则该位的结果值为0。即:~ 0 =1;~ 1 =0。

# include<stdio.h>

int main(){

	int a = 11;
	//二进制表示(补码):0000 0000 0000 0000 0000 0000 0000 1011

	//~a(补码):         1 111 1111 1111 1111 1111 1111 1111 0100
	//~a(反码=补码-1):  1 111 1111 1111 1111 1111 1111 1111 0011
	//~a(源码 = 反码符号位不变其他位取反):         
	//                    1 000 0000 0000 0000 0000 0000 0000 1100  = -12

	printf("%d\n",~a);
	return 0;
}

代码结果

-12
请按任意键继续. . .

5、左移操作(<<)

运算规则:对运算符<<左边的运算量的每一位全部左移右边运算量表示的位数,右边空出的位补0。左移一位变为原来二倍。

# include<stdio.h>

int main(){

	int a = 11;
	//二进制表示(补码):0000 0000 0000 0000 0000 0000 0000 1011
	//a<<1:               0000 0000 0000 0000 0000 0000 0001 0110
	int b = -11;
	//原码:              1000 0000 0000 0000 0000 0000 0000 1011
	//反码:               1111 1111 1111 1111 1111 1111 1111 0100
	//补码:              1111 1111 1111 1111 1111 1111 1111 0101
	//b<<1的补码          1111 1111 1111 1111 1111 1111 1110 1010
	//b<<1的反码          1111 1111 1111 1111 1111 1111 1110 1001
	//b<<1的原码          1000 0000 0000 0000 0000 0000 0001 0110  = -22

	printf("%d\n%d\n",a<<1,b<<1);
	return 0;
}

代码结果

22
-22
请按任意键继续. . .

6、右移操作(>>)

运算规则:对运算符>>左边的运算量的每一位全部右移右边运算量表示的位数,右边低位被移出去舍弃掉,空出的高位补0还是补1,分两种情况:

(1)对无符号数进行右移时,空出的高位补0。这种右移称为逻辑右移。

(2)对带符号数进行右移时,空出的高位全部以符号位填补。即正数补0,负数补1。这种右移称为算术右移。

注意:

  • 如果被移位的对象长度是n位,那么移位计数必须大于或等于0,且严格小于n。因此,不可能做到在单次操作中将某个数值中的所有位都移出。例如:若一个int型整数是32位,n是一个int型整数,那么n<<31和n<<0这样写是合法的,而n<<32和n<<-1这样写是非法的。
  • 当需要对某整数(浮点数不能进行移位操作)进行除以2的n次幂的操作时,最好用右移n位进行替换,因为在计算机中计算除法更“费劲”,右移运算符更高效一点。
# include<stdio.h>

int main(){

	int a = 11;
	//二进制表示(补码):0000 0000 0000 0000 0000 0000 0000 1011
	//a>>1:               0000 0000 0000 0000 0000 0000 0000 0101  = 5
	int b = -11;
	//原码:              1000 0000 0000 0000 0000 0000 0000 1011
	//反码:               1111 1111 1111 1111 1111 1111 1111 0100
	//补码:              1111 1111 1111 1111 1111 1111 1111 0101
	//b>>1的补码          1111 1111 1111 1111 1111 1111 1111 1010
	//b>>1的反码          1111 1111 1111 1111 1111 1111 1111 1001
	//b>>1的原码          1000 0000 0000 0000 0000 0000 0000 0110  = -6

	unsigned int c = 6;
	//补码:              0000 0000 0000 0000 0000 0000 0000 0110
	//c>>1的补码          0000 0000 0000 0000 0000 0000 0000 0011  = 3

	printf("%d\n%d\n%d\n",a>>1,b>>1,c>>1);
	return 0;
}

代码结果:

5
-6
3
请按任意键继续. . .

三、常识与技巧

1、判断某一位是0还是1

# include<stdio.h>

int main(){
	//判断第M位是0还是1
	int M = 3;
	int a = 11;
	//补码:              0000 0000 0000 0000 0000 0000 0000 1011
	//1<<M                0000 0000 0000 0000 0000 0000 0000 1000
	//1<<M & a            0000 0000 0000 0000 0000 0000 0000 1000
	if (1<<M & a){
		printf("a的从左向右第M位是1。\n");
	}
	else{
		printf("a的从左向右第M位不是1。\n");
	}
	return 0;
}

代码结果:

a的从左向右第M位是1。
请按任意键继续. . .

2、任意数与0异或保持不变

# include<stdio.h>

int main(){

	int a = 11;
	//补码:              0000 0000 0000 0000 0000 0000 0000 1011
	int b = 0;
	//原码:              0000 0000 0000 0000 0000 0000 0000 0000

	// a^b = b^a          0000 0000 0000 0000 0000 0000 0000 1011  = 11

	printf("%d\n",a^b);
}

代码结果:

11
请按任意键继续. . .

3、任意数与本身异或为0

# include<stdio.h>

int main(){

	int a = 11;
	//补码:              0000 0000 0000 0000 0000 0000 0000 1011

	// a^a                0000 0000 0000 0000 0000 0000 0000 0000  = 0

	printf("%d\n",a^a);
}

代码结果:

0
请按任意键继续. . .

4、将二进制位最后一位1变成0

对于任意整数 x,令 x=x & (x−1),该运算将 x 的二进制表示的最后一个 1 变成 0。

# include <stdio.h>

int main(){
	int x = 10;
	//x:      0000 0000 0000 0000 0000 0000 0000 1010
	//x-1:     0000 0000 0000 0000 0000 0000 0000 1001
	//x&(x-1): 0000 0000 0000 0000 0000 0000 0000 1000

	return 0;
}

四、数据结构练习题

1、消失的数字

数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

示例:

输入:[3,0,1]
输出:2
  • 首先我们需要知道:1、任何数与0异或不变;2、相同数异或为0;
  • 数组 nums中有 n 个数,在这 n个数的后面添加从 0 到 n 的每个整数,则添加了 n+1 个整数,共有 2n+1 个整数;

  • 在2n+1 个整数中,消失的数字只在后面 n+1 个整数中出现一次,其余的数字在前面 n 个整数中(即数组中)和后面 n+1 个整数中各出现一次,即其余的数字都出现了两次;

  • 对这2n+1个整数进行异或,出现两次的异或没了,只剩下出现一次的数字(即消失的数字);

int missingNumber(int* nums, int numsSize){
    int i;
    int res = 0;
    for(i = 0; i < numsSize;i++){
        res ^= nums[i];
    } 
    for(i = 0; i <= numsSize; i++){
        res ^= i;
    }
    return res;

}

2、数组中数字出现的次数

剑指offer56:

一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。

示例1:

输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
  • 设出现一次的两个数字是a 和b,那么所有数字异或的结果就等于 a 和 b 异或的结果,我们记为 x。如果我们把 x写成二进制的形式 {x_{32}x_{31}...x_{k}...x_{1}}, x_{i}=0/1,i=1...32;
  • 我们现在思考一个问题,x_{k}=1意味着什么?意味着ab他们的第k位不相同;

  • 还有一个重要信息;除了ab其他数字两两相同。也就表示他们第k位相同;

  • 我们根据第k位区分数据有两个作用,1、ab肯定区分开;2、相同的数字分到同一组;

  • 对两个组分别异或就可以得到ab

# include<stdio.h>
# include<stdlib.h>


int* singleNumbers(int* nums, int numsSize, int* returnSize){

    //假设出现一次的是a,b
    int res = 0;
    int res1 = 0;
    int res2 = 0;
    int i;
    int M=0;
    // 首先求出a^b
    for(i=0;i<numsSize;i++){
        res ^= nums[i];
    }
    //把第一次出现1的位置保存为M
    while(M<32){
       if (1<<M & res){
           break;
       }
       else{
           M++;
       }
    }

    //分组
    for (i = 0;i<numsSize;i++){
        if (nums[i] & 1<<M){
            res1 ^= nums[i];
        }
        else{
            res2 ^= nums[i];
        }
    }

    int* result = (int*)malloc(sizeof(int)*2);
    result[0] = res1;
    result[1] = res2;

    *returnSize = 2;
    return result;
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一 杯 清 酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值