位运算“|”、“&”、“^”、“~”、“>>”、“<<”

本文详细解释了整型在内存中的补码存储机制,介绍了位运算符如按位取反、左移、右移等,并展示了位运算在解决编程问题如数组操作、整数转换和查找缺失数字中的实际应用。
摘要由CSDN通过智能技术生成

一、整型的存储

        有符号整型在内存中是以二进制的补码的形式存储,负数和正数的存储方式又有所不同。

        例如int 型的 -3

        原码:1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        反码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0

        补码: 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1

        在原码中最高位表示符号位,“1”表示是负数,“0”表示是正数,原码的符号位不变,其它位按位取反就是反码,反码加一就是补码。但是正数(包括0)的原码、反码、补码都是和原码一样的。

二、位运算符

        1、“~”:按位取反,“0”按位取反就是“1”,“1”按位取反就是“0”。“~”符号没有改变变量原本的值,操作完的值可以用来给其他变量赋值。例如:b=~a,a的值不会被改变。

        2、“<<”:左移操作符,将被操作数的二进制补码左移一个bit位。

        例如int 3:3<<1

        补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

       左移后: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0

        由于int类型的变量只有32个比特位,左移后会丢失最高位,变成31个比特位,于是在最低位补上0,左移操作符可以达到乘以2的效果,但a的值不会被改变。

        3、“>>”:右移操作符,将被操作数的二进制补码右移一个bit位。

        例如int 3:3>>1

        补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        右移后: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

        由于int类型的变量只有32个比特位,左移后会丢失最低位,于是在最低位补上与符号位相同的数字,a的值不会被改变

        4、“|”:按位或符号,双目操作符,将两个数的二进制补码的每一位按照有“1”则“1”,无“1”则“0”的规则获得一个新的二进制序列。

        例如int 3和int -2

        3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        -2的补码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1

        3|-2的补码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1

        5、“&”:按位与符号,双目操作符,将两个数的二进制补码的每一位按照有“0”则“0”,无“0”则“1”的规则获得一个新的二进制序列。

        6、“^”:按位异或操作符,双目操作符,将两个数的二进制补码的每一位按照相同为“0”,想异为“1”的规则获得一个新的二进制序列。

        例如int 3和int -2

       3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        -2的补码:1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1

        3^-2的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1

二、位运算的应用

       1、 给定一个长度为 n 的非降序数组和一个非负数整数 k ,要求统计 k 在数组中出现的次数 数据范围: 0≤n≤1000 , 0≤k≤100 ,数组中每个元素的值满足 0≤val≤100 数字在升序数组中出现的次数_牛客题霸_牛客网【牛客网题号: JZ53 数字在升序数组中出现的次数】【难度:简单】

        我们要在数组中找和k相同的数字,我们可以通过遍历数组的方式来统计。

        第一个方法是直接判断 arr[i] 是否等于 k ,第二个方法是用按位异或,

        3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        3^3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

        可以发现两个相同的数按位异或后的值等于0

        int find(int* nums,int numsLen,int k)
        {
            int count = 0;
            for (int i = 0; i < numsLene; i++)
            {
                if ((nums[i] ^ k) == 0)
                    count++;
            }
            return count;
        }

2、给定一个包含 [0, n] 中 n 个数的数组 nums ,找出 [0, n] 这个范围内没有出现在数组中的那个数。力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

        我们要找这个没有出现的数字,可以先对数组进行排序,再遍历这个数组,找到这个缺失的数。时间复杂度为O(nlogn),或者用0~n的总和减去数组总和,时间复杂度为O(n)。

        我们还可以用按位异或的方式来找,先用0和0到n的每一个数字都按位异或一次,再和数组nums的每一个元素都按位异或一次,得出的结果就是缺失的数。(按位异或满足交换律)

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

3、整数转换。编写一个函数,确定需要改变几个位才能将整数 A 转成整数 B 。 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台【 leetcode 题号:面试题 05.06. 整数转换】【难度:简单】  

示例: 输入:A = 29 (或者0b11101), B = 15(或者0b01111)

输入:A = 1,B = 2 输出:2 输出:2

我们首先就要找到这两个数的不同位,要用到按位异或(相同为“0”,想异为“1”),用一个数来接收这个值,这个值的二进制补码中的1就是不同位,再统计他的1的个数,让他和1按位与,如果和1按位与后的值为1则说,最低位为1,反之则为0,再右移一个比特位,再与1按位与,重复这个过程,直到右移后为值为0.

int f(int A, int B)
{
    int x = A ^ B, i = 0;
    if (x < 0)//负数要特殊处理,因为负数的补码的求法特殊,也不要改成正数,这样会改变他的二进制序列。
    {
        i = 1;
    }
    int j = 0;
    while (x != 0 && j<31)
    {
        if ((x & 1) == 1)
            i++;
        x = x >> 1;

    }
    return i;
}

4、写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。 数据范围:两个数都满足 0≤n≤1000 不用加减乘除做加法_牛客题霸_牛客网

        加法操作的是补码

        3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

        3+3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0

        可以发现有的需要进位,有的不需要,那我们就要找出需要进为的每一位(记为数值x),用按位与,再找到不用进位的每一位(记为数值y),当我们找到x和y就要把他们每一位的1合并,用按位或,但是合并过程也有可能有进位,所以我们要用一个循环,直到不用进位,即x的数值为0.

#include<stdio.h>
int main()
{
    int a = -99, b =111,z=0;
    while (b != 0)
    {
        z = a;
        a = a ^ b;//不进位
        b = z & b;//进位
        b = b << 1;//为什么要左移,因为我们得到的1是需要进位的后一位。
    }
    printf("%d",a|b);
    return 0;
}

5、集合 s 包含从 1 到 n 的整数。不幸的是,因为数据错误,导致集合里面某一个数字复制了成了集合里面的另 外一个数字的值,导致集合 丢失了一个数字 并且 有一个数字重复 。 给定一个数组 nums 代表了集合 S 发生错误后的结果。 请你找出重复出现的整数,再找到丢失的整数,将它们以数组的形式返回。力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

可以用排序。

位运算:假设重复数字为x,缺失为y。我们知道两个相同的数按位异或为0,那我们用一个变量把1~n的数字和数组的每个元素都按位异或一次,这样我们得到了x和y的按位异或的值z。

再看一个东西

3的补码:     0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

-3的补码:    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1

-3&3的补码:0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 

-3的补码是3的补码取反再加1,这可以找到最低位的1,因为正数的补码的0在其相反数的反码中会变成1,而反码加1变成补码,这个加1会从低位开始跳过(进位)正数补码中的0,直到正数补码的1才停下。而z又是x^y得到的,所以我们找到了x和y的补码中的最低不同位(记这个值为a)。这可以让我们分开x和y。

再将a与数组的每个元素按位异或得到0的元素记为数组num1的元素,再将a与1~n的每个数字按位异或得到0的数记为数组num2的元素,这时num1的元素有两种情况:一是有重复数字x,二是与num2相比缺少数字y。我们用一个数字0分别与num1和num2的元素都按位异或一次,这样我们就得到了x或y其中一个数字。我们再将a与数组的每个元素按位异或得到1的元素记为数组num3的元素,再将a与1~n的每个数字按位异或得到1的数记为数组num4的元素,这时num3的元素有两种情况:一是有重复数字x,二是与num4相比缺少数字y。方法和上面一样得到另一个x或y。给我们的数组只有x,可以很容易区分哪个是x,哪个是y。

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

    int x = 0, y = 0, z = 0, k = 0;

    *returnSize=2;//tmd,我说这个是干什么的,返回数组的字节数不是恒等于8吗,还要我说,没有这个提交还一定是错的。

    int* arr = NULL;arr = (int*) malloc(8);

    for (int i = 0; i < numsSize; i++)

    {

        z = z ^ nums[i];

        z = z ^ (i + 1);

    }

    k = z & (-z);

    int num1 = 0, num2 = 0, num3 = 0, num4 = 0;

    for (int i = 0; i < numsSize; i++)

    {

        if ((k & nums[i]) == 0)

            num1 = num1 ^ nums[i];

        else

            num3 = num3 ^ nums[i];

    }

    for (int i = 1; i <= numsSize; i++)

    {

        if ((k & i) == 0)

            num2 = num2 ^ i;

        else

            num4 = num4 ^ i;

    }

    x = num1 ^ num2; y = num3 ^ num4;

    for (int i = 0; i < numsSize; i++)

    {

        if ((x ^ nums[i]) == 0)

        {

            arr[0] = x; arr[1] = y;

                return arr;

        }

    }

    arr[0] = y; arr[1] = x;

    return arr;

}

下一篇说结构体和链表。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值