zoukankan      html  css  js  c++  java
  • [知识点] 6.1 位运算与进位制

    总目录 > 6 数学 > 6.1 位运算与进位制

    前言

    开始新的一部分。。。暑假开始要进行一些线上练习赛,数学向来也是涉猎不多也不擅长的部分,现在还是要花点时间好好补一下。

    子目录列表

    1、位运算

    2、进位制 

    6.1 位运算与进位制

    1、位运算

    ① 概念

    在 1.2 C++ 基础知识 中,介绍了常用的各种运算符,其中一类被称为位运算符。位运算是基于整数,但以二进制来表示的运算。那么为什么要费尽周折把简单的十进制转换成二进制表示?因为计算机内部是以二进制存储数据,从计算机角度而言,二进制运算才是效率最高的,所以如果希望提升程序运行效率,肯定只能是使用十进制的人类去迁就二进制的计算机啦。

    ② 与、或、异或

    不同于加减乘除 —— 十进制的四则基本运算,二进制有其不同的基本运算符,它们分别是与、或、异或。这个学期的《计算机组成与结构》、《数字电子技术》、《汇编语言》几门课反反复复地提到了这些概念,足以见得在二进制运算中举足轻重的地位。

    手算时,需要将数转换成二进制,并逐位进行计算,最后将计算结果再转换成十进制。

    > 与

    符号:a & b

    含义:对应位均为 1 时结果为 1

    结果:1 & 1 = 1, 1 & 0 = 0 & 1 = 0 & 0 = 0

    举例:5 & 6 = (101)2 & (110)2 = (100)2 = 4

    > 或

    符号:a | b

    含义:对应位存在一个 1 时结果为 1

    结果:1 | 1 = 1 | 0 = 0 | 1 = 1, 0 | 0 = 0

    举例:5 | 6 = (101)2 | (110)2 = (111)2 = 7

    > 异或

    符号:a ^ b

    含义:两个对应位不同时结果为 1

    结果:1 ^ 0 = 0 ^ 1 = 1, 1 ^ 1 = 0 ^ 0 = 0

    举例:5 ^ 6 = (101)2 ^ (110)2 = (011)2 = 3

    ③ 取反

    符号:~num

    含义:对num 的每一位取反

    备注:实际使用并不多,同时牵涉到补码的概念,不多说

    ④ 左移和右移

    > 左移

    符号:num << i

    含义:将 num 的二进制表示向左移动 i 位

    举例:5 << 1 = (101)2 << 1 = (1010)2 = 10, 4 << 3 = (100)2 << 3 = (100000)2 = 32

    从举例中不难看出,num 左移 i 位其实等价于 num 与 2 的 i 次方相乘。又已知位运算效率最高,所以相比 num * 2,num << 1 是更优的写法。

    > 右移

    符号:num >> i

    含义:将 num 的二进制表示向左移动 i 位

    举例:6 >> 1 = (110)2 >> 1 = (11)2 = 3, 9 >> 2 = (1001)2 >> 2 = (10)2 = 2

    和左移相对,num 右移 i 位等价于 num 除以 2 的 i 次方并向下取整。

    关于左移右移的诸多未定义情况,在 C++ 的不同版本里均不同,此处暂略。

    ⑤ 应用

    上面多次提到,位运算效率高于其他运算,所以在以追求运算速度的需求下,可以将许多常规的写法替换为位运算写法,下面提供诸多常用位运算实现的功能:

    > 取绝对值

    int abs(int o) {
        return (o ^ (o >> 31)) - (o >> 31);
        // return o > 0 ? o : -o; 常规写法 
    }

    > 判断符号是否相同

    bool isSameSign(int x, int y) {  // 有 0 的情况例外
        return (x ^ y) >= 0;
    }

    > 交换两个整数

    void swap(int &a, int &b) { 
        a ^= b ^= a ^= b; 
        /* 常规写法 
        int tmp = a;
        a = b, b = tmp;
        */
    }

    > 获取二进制数的某一位

    int getBit(int a, int b) { 
        return (a >> b) & 1; 
    }

    > 求两个数的最大值 / 最小值

    int max(int a, int b) { 
        return b & ((a - b) >> 31) | a & (~(a - b) >> 31); 
        // return a > b ? a : b; 常规写法
    }
    int min(int a, int b) { 
        return a & ((a - b) >> 31) | b & (~(a - b) >> 31); 
        // return a < b ? a : b; 常规写法
    }

    位运算还可以用来进行状态压缩(请参见:<施工中>),或者题目本身需要位运算,比如快速幂(请参见:6.2 快速幂)。

    2、进位制

    在计算机中,二进制是内部运算所采用的进制,除此之外,八进制十六进制也使用的较多。

    这里进制之间的转换就不再多说了,提一下在 C++ 中如何表示这两种进制。

    对于八进制,在数之前加上 "ox",例如八进制数 (123)8,在 C++ 中的书写形式为 "ox123";

    对于十六进制,加上 "0x",例如十六进制数 (ABC)16,在 C++ 中的书写形式为 "0xABC"(大小写均可)。

    那么平时在什么时候可能需要用到这些进制呢?

    众所周知 int 的数据范围为 2 ^ 63 - 1,用十六进制表示为 0x7fffffff。许多时候我们需要使用到一个“无穷大”的概念,但显然计算机并不接受这种说法,毕竟它并非一个具体数值。这时候,我们可以将数据类型范围最大值近似等价为无穷大,即 0x7fffffff。但这并非最好的数值。尽管数据不会超过这个范围,但有时难免出现使用这个“无穷大”与另一个数相加的情况,而一旦出现,则直接溢出导致错误,所以我们退而求其次,选择一个相对较小的数来充当这个角色:

    0x3f3f3f3f

    它在算法竞赛中使用广泛,因为确实太好用了。它的优势在于:

    ① 十进制表示为 1061109567,属于 10 ^ 9 数量级,且刚好大于许多题目规定的数据范围 10 ^ 9;

    ② 它的 2 倍数为 2122219134,刚好小于 int 范围最大值,所以倘若出现“无穷大”相加的情况,同样不会溢出;

    ③ 使用 memset 初始化数组时,可以直接 memset(a, 0x3f, sizeof(a)),因为它的每个字节都是重复的 —— 0x3f。

    一般情况下,double 类型的数据也可以使用上述十六进制数。

    1 << 30 同样适用于表示 int 范围的无穷大,但不支持浮点类型

  • 相关阅读:
    javascipt加强对类的理解
    PHP(http协议)相关应用知识
    javascipt对象成员函数
    PHP(http协议)防盗链技术(小练习)
    javasricpt二维数组矩形转置
    PHP二维数组矩形转置
    javascipt冒泡排序
    用vim解压各种格式
    转载:【菜鸟专用】使用LaTeX轻松撰写精美个人简历
    Ruby的gets和gets.chmop
  • 原文地址:https://www.cnblogs.com/jinkun113/p/13326460.html
Copyright © 2011-2022 走看看