C++入门——位操作

位操作

一些有关位操作的知识

  1. 常用的等式:-n = ~(n - 1) = ~n + 1

  2. 获取整数n的二进制中最后一个1:n & (-n)或者n & ~(n - 1)

  3. 去掉整数n的二进制中最后一个1:n & (n - 1)

结构声明中的冒号和数字

C语言的结构体可以实现位段(也称位域),它的定义形式是在一个定义的结构体成员后加上冒号,然后是该成员所占的位数。位段的结构体成员必须是int或unsigned int类型,不能是其他类型。位段在内存中的存储方式是由具体的编译器决定的

  1. 定义位段的长度不能大于存储单元的长度

  2. 一个位段如果不能放在一个存储单元里,那么它会把这个存储单元中剩余空间闲置,而从下一个存储单元开始存储下一个位段,即一个位段不能跨越两个存储单元,位段在一个存储单元中的存储是紧凑的

  3. 位段名缺省时称作无名位段,无名位段的存储空间通常不用,而位段长度为0位表示下一个位段存储在一个新的存储单元中。位段长度为0的时候,位段名必须缺省

  4. 一个结构体中既可以定义位段成员,也可以同时定义一般的结构体成员。这时,一般成员不和位段存储在同一个单元

举例:

#include <stdio.h>
typedef struct
{
    int a:2;
    int b:2;
    int c:1;
}test;

int main()
{
    test t;
    t.a = 1;
    t.b = 3;
    t.c = 1;
    printf("%d\n%d\n%d\n", t.a, t.b, t.c);
    return 0;
}

由于a占两位,而a被赋值为1,二进制就是01,因此%d输出的时候输出1;b也占两位,赋值为3,二进制是11,由于使用了%d输出,表示的是将这个b作为有符号int型来输出,这样的话二进制的11将会有一位被认为是符号位,并且两位的b也会被扩展为int类型,也就是4字节。

其实a也做了这种扩展,只是扩展符号位的时候,由于数字在计算机中存储都是补码形式,因此扩展符号位的时候正数用0填充高位,负数用1填充高位。因此,对于a来说,输出的时候被扩展为00000000 00000000 00000000 00000001,而b扩展为11111111 11111111 11111111 11111111,也就是-1。同理,t.c的输出也是-1

利用位运算计算数的绝对值

在计算机中,数字都是以补码的形式存在,求负数的绝对值,应该是不管符号位,所有位执行按位取反,末位加1操作即可

对于一个负数,将其右移31位后会变成0xffffffff,即-1;而对于一个正数而言,右移31位则为0x00000000,即0。所以,利用异或操作即可实现这一功能:

int MyAbs(int x)
{
    int y;
    y = x >> 31;
    return (x^y)-y;
}

求整型数的二进制表示中1的个数

方法一

判断数的二进制表示中每一位是否为1,如果为1,就在count上加1,而循环的次数就是n的位数,程序如下:

int func(unsigned int n)
{
    int count = 0;
    while(n)
    {
        count += n & 0x1u;
        n >>= 1;
    }
    return count;
}

但该方法在1比较稀疏时效率较低

方法二

当一个数被减1时,它最右边的那个值为1的位将会变成0,同时其右边的所有位都会变成1。因此,我们只需不断的执行x & (x - 1),就可以把二进制数中的最后一位1逐个去掉,当循环到x等于0的时候,循环的次数就是x对应的二进制数中1的个数,程序如下:

int func(int x)
{
    int count = 0;
    while(x)
    {
        count ++;
        x = x & (x - 1);
    }
    return count;
}

不使用sizeof,如何判断操作系统是16位,还是32位

如果没有强调不许用sizeof,如果在32位机器上,sizeof(int) = 4,如果在16位机器上,sizeof(int) = 2

方法一

一般而言,机器位数不同,其表示的数字的最大值也不同。在16位机器上,int可以表示-32768到+32767之间的数,也就是说,它能表示的最大值为32767,如果再加1,将会溢出为-32768。但是,在32位机器上,int能表示的最大值为2147483647,因此32767+1不会溢出。可以根据这个特性,判断系统是16位,还是32位

方法二

对类型为unsigned int的0取反,不同位数下的0值取反,其结果不一样。例如,在32位机器上,执行按位取反运算,结果为429496727295,。而在16位机器上,取反的结果值为65535,可以根据这个特性来判断系统是16位还是32位

大端与小端

采用小端模式的CPU对操作数的存放方式是从低字节到高字节,而大端模式对操作数的存放方式是从高字节到低字节。

80x86机是小端模式,单片机一般为大端模式。Intel处理器一般都是小端模式。

如何判断计算机处理器是大端还是小端
方法一

联合体union的存放顺序是所有成员都从低地址开始存放。

程序如下:

int checkCPU()
{
    {
        union w
        {
            int a;
            char b;
        }c;
        c.a = 1;
        return (c.b == 1);
    }
}
方法二

还可以通过指针地址来判断,由于在32位计算机系统中,short占两个字节,char占1个字节,所以可以采用如下方式判断:

int checkCPU()
{
    unsigned short usData = 0x1122;
    unsigned char *pucData = (unsigned char*)&usData;
    return (*pucData == 0x22);
}

不用除法操作符实现两个正整数的除法

方法一

可以根据除法运算的原理进行减法操作,用除数循环减被除数,减一次结果加1,知道刚好减为0或余数小于被除数为止

int div(int a, int b)
{
    int result = 0;
    if (b == 0)
    {
        printf("除数不能为0\n");
        return result;
    }
    while (a >= b)
    {
        result++;
        a = a - b;
    }
    return result;
}

当a很大,b很小时,这个算法的效率很低

方法二

翻倍比较法。对于方法一,如果每次采用将比较熟翻倍的比较方法,则算法效率能够得到极大优化

int MyDiv(int a, int b)
{
    int k = 0;
    int c = b;
    int res = 0;
    if (b == 0)
    {
        printf("除数不能为0\n");
        return 0;
    }
    if (a < b)
    {
        return 0;
    }
    for (; a >= c; c <<= 1; k ++)
    {
        if (a - c < b)
        {
            return 1 << k;
        }
    }
    return MyDiv(a - (c >> 1), b) + (1 << (k - 1));
}
方法三

采用移位操作实现:

int div(const int x, const int y)
{
    int left_num = x;
    int result = 0;
    while (left_num >= y)
    {
        int multi = 1;
        while (y * multi <= (left_num >> 1)
        {
            multi = multi << 1;
        }
        result += multi;
        left_num -= y * multi;
    }
    return result;
}

只用逻辑运算实现加法

对于二进制加法而言,1 + 1 = 01 + 0 = 10 + 1 = 10 + 0 = 0,与位运算中的异或类似,而且0 + 0的进位是0,1 + 0的进位是0,只有1 + 1的进位有效,这又与&运算相似,即只有1 & 1 = 1,又由于进位是进位到高一位,与<<运算相似。同时,num1和num2相&后,如果结果为0,那么不存在进位,运算完成。用递归实现:

int add(int num1, int num2)
{
    if (0 == num2)
    {
        return num1;
    }
    int sumTemp = num1 ^ num2;
    int carray = (num1 & num2) << 1;
    return add(sumTemp, carry);
}

非递归实现:

int add(int num1, int num2)
{
    int sum = 0;
    int num3 = 0;
    int num4 = 0;
    while ((num1 & num2) > 0)
    {
        num3 = num1 ^ num2;
        num4 = num1 & num2;
        num1 = num3;
        num2 = num4 << 1;
    }
    sum = num1 ^ num2;
    return sum;
}

用逻辑运算实现乘法

对于二进制数乘法:1011 * 1010,可以将表达式拆分为两个运算,1011*00101011*1000的和,而对于二进制的运算,左移1位,等价于乘以0010,左移3位,等价于乘以1000,所以,两者的乘积为101101011000的和

所以,乘法可以通过一些列移位和加法完成。最后一个1可通过b&~(b-1)求得,可通过b&(b-1)去掉,为了高效地得到左移的位数,可以提前计算一个map:

int multiply(int a, int b)
{
    bool neg = (b < 0);
    if (neg)
    {
        b = -b;
    }

    int sum = 0;
    map<int, int> bit_map;
    for (int i = 0; i < 32; i++)
    {
        bit_map.insert(pair<int, int>(1 << i, i));
    }
    while (b > 0)
    {
        int last_bit = bit_map[b & ~(b - 1)];
        sum += (a << last_bit);
        b &= b - 1;
    }

    if (neg)
    {
        sum = -sum;
    }
    return sum;
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
贪心算法是一种问题求解方法,它在每一步总是做出当前情况下的最优选择,以期望获得最优解。而"最大整数"同样可以使用贪心算法来求解。 对于"最大整数"的问题,我们可以考虑如下的贪心策略:从高位开始,尽可能选择较大的数字。具体步骤如下: 1. 对于给定的整数,我们首先将其转化为一个数组,其中每个元素表示整数的一个位数。 2. 从最高位(最左侧)开始,遍历数组。 3. 对于当前位上的数字,从9开始递减,找到第一个小于等于当前数字的最大数字。 4. 如果找到了符合条件的最大数字,将其放在当前位。否则,不做任何操作。 5. 继续向下遍历,重复步骤3-4。 6. 最终,得到的数组即为满足条件的最大整数。 以一个具体的例子说明上述算法:假设给定的整数为5372。 1. 将整数转化为数组[5, 3, 7, 2]。 2. 从最高位开始遍历。 3. 对于第一位5,从9开始递减,找到第一个小于等于5的数字,为7。 4. 将7放在第一位,得到[7, 3, 7, 2]。 5. 对于第二位3,从9开始递减,找到第一个小于等于3的数字,为3(与当前数字相等)。 6. 不做任何操作,得到[7, 3, 7, 2]。 7. 对于第三位7,从9开始递减,找到第一个小于等于7的数字,为7。 8. 将7放在第三位,得到[7, 3, 7, 2]。 9. 对于第四位2,从9开始递减,找到第一个小于等于2的数字,为2。 10. 将2放在第四位,得到[7, 3, 7, 2]。 11. 遍历结束,最终得到的数组为[7, 3, 7, 2],转化为整数为7372。 通过上述贪心算法,我们得到了满足条件的最大整数7372。证明了贪心算法在"最大整数"问题中的有效性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值