[知识点] 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 范围的无穷大,但不支持浮点类型

posted @ 2020-07-16 23:21  jinkun113  阅读(798)  评论(0编辑  收藏  举报