☀️光天化日学C语言☀️(18)- 位运算 << 的应用 | 左移的一些高端用法

☀️光天化日学C语言☀️(18)- 位运算 << 的应用 | 左移的一些高端用法

一、前言

本文作者是从 2007 年开始学 C语言 的,不久又接触了C++,基本就是 C/C++ 技术栈写了 14 年的样子,不算精通,但也算差强人意。著有《夜深人静写算法》系列,且承诺会持续更新,直到所有算法都学完。主要专攻 高中 OI 、大学 ACM、 职场 LeetCode 的全领域算法。由于文章中采用 C/C++ 的语法,于是就有不少读者朋友反馈语言层面就被劝退了,更何况是算法。 于是,2021 年 06 月 12 日,《光天化日学C语言》 应运而生。这个系列文章主要服务于高中生、大学生以及职场上想入坑C语言的志同道合之人,希望能给祖国引入更多编程方面的人才,并且让自己的青春不留遗憾! 这一章的主要内容是左移运算符的应用。

二、人物简介

第一位登场的就是今后会一直教我们C语言的老师 —— 光天。

第二位登场的则是今后会和大家一起学习C语言的没什么资质的小白程序猿 —— 化日。

三、左移运算符

1、左移的二进制形态

左移运算符是一个二元的位运算符,也就是有两个操作数,表示为x << y。其中x和y均为整数。

x << y念作:“将

x

x

x 左移

y

y

y 位”,这里的位当然就是二进制位了,那么它表示的意思也就是:先将

x

x

x 用二进制表示,然后再左移

y

y

y 位,并且在尾部添上

y

y

y 个零。

举个例子:对于二进制数

2

3

10

=

(

10111

)

2

23_{10} = (10111)_2

2310​=(10111)2​ 左移

y

y

y 位的结果就是:

(

10111

0...0

y

)

2

(10111\underbrace{0...0}_{\rm y})_2

(10111y

0...0​​)2​

2、左移的执行结果

x << y的执行结果等价于:

x

×

2

y

x \times 2^y

x×2y

如下代码:

#include

int main() {

int x = 3;

int y = 5;

printf("%d\n", x << y);

return 0;

}

输出结果为:

96

正好符合这个左移运算符的实际含义:

96

=

3

×

2

5

96 = 3 \times 2^5

96=3×25

最常用的就是当

x

=

1

x = 1

x=1 时,1 << y代表的就是

2

y

2^y

2y,即 2 的幂。

3、负数左移的执行结果

所谓负数左移,就是x << y中,当x为负数的情况,代码如下:

#include

int main() {

printf("%d\n", -1 << 1);

return 0;

}

它的输出如下:

-2

我们发现同样是满足

x

×

2

y

x \times 2^y

x×2y 的,这个可以用补码来解释,-1的补码为:

11111111

11111111

11111111

11111111

11111111 \ 11111111 \ 11111111 \ 11111111

11111111 11111111 11111111 11111111

左移一位后,最高位的 1 就没了,低位补上 0,得到:

11111111

11111111

11111111

11111110

11111111 \ 11111111 \ 11111111 \ 11111110

11111111 11111111 11111111 11111110

而这,正好是 -2的补码,同样,继续左移 1 位,得到:

11111111

11111111

11111111

11111100

11111111 \ 11111111 \ 11111111 \ 11111100

11111111 11111111 11111111 11111100

这是-4的补码,以此类推,所以负整数的左移结果同样也是

x

×

2

y

x \times 2^y

x×2y。

可以理解成 - (x << y)和(-x) << y是等价的。

4、左移负数位是什么情况

刚才我们讨论了

x

<

0

x < 0

x<0 的情况,那么接下来,我们试下

y

<

0

y < 0

y<0 的情况会是如何?

是否同样满足:

x

×

2

y

x \times 2^y

x×2y 呢?

如果还是满足,那么两个整数的左移就有可能产生小数了。

看个例子:

#include

int main() {

printf("%d\n", 32 << -1); // 16

printf("%d\n", 32 << -2); // 8

printf("%d\n", 32 << -3); // 4

printf("%d\n", 32 << -4); // 2

printf("%d\n", 32 << -5); // 1

printf("%d\n", 32 << -6); // 0

printf("%d\n", 32 << -7); // 0

return 0;

}

虽然能够正常运行,但是结果好像不是我们期望的,而且会报警告如下:

[Warning] left shift count is negative [-Wshift-count-negative]

实际上,编辑器告诉我们尽量不用左移的时候用负数,但是它的执行结果不能算错误,起码例子里面对了,结果不会出现小数,而是取整了。

左移负数位其实效果和右移对应正数数值位一致,右移相关的内容,我们会在 光天化日学C语言(19)- 位运算 >> 的应用 中讲到。

5、左移时溢出会如何

我们知道,int类型的数都是 32 位的,最高位代表符号位,那么假设最高位为 1,次高位为 0,左移以后,符号位会变成 0,会产生什么问题呢?

举个例子,对于

2

31

+

1

-2^{31}+1

−231+1 的二进制表示为:最高位和最低位为1,其余为零。

#include

int main() {

int x = 0b10000000000000000000000000000001;

printf("%d\n", x); // -2147483647

return 0;

}

输出结果为:

-2147483647

那么,将它进行左移一位以后,得到的结果是什么呢?

#include

int main() {

int x = 0b10000000000000000000000000000001;

printf("%d\n", x << 1);

return 0;

}

我们盲猜一下,最高位的 1 被移出去,最低位补上 0,结果应该是0b10。

实际输出的结果,的确是:

2

但是如果按照

x

×

2

y

x \times 2^y

x×2y 答案应该是 $$(-2^{31}+1) \times 2 = -2^{32}+2$$

这里又回到了补码的问题上,事实上,在计算机中,int整型其实是一个环,溢出以后又会回来,而环的长度正好是

2

32

2^{32}

232,所以

2

32

+

2

=

2

-2^{32}+2 = 2

−232+2=2,这个就有点像同余的概念,这两个数是模

2

32

2^{32}

232 同余的。更多关于同余的知识,可以参考我的算法系列文章:夜深人静写算法(三)- 初等数论入门(学生党记得找我开试读)。

四、左移运算符的应用

1、取模转化成位运算

对于

x

x

x 模上一个 2 的次幂的数

y

y

y,我们可以转换成位与上

2

y

1

2^y-1

2y−1。

即在数学上的:

x

m

o

d

2

y

x \ mod \ 2^y

x mod 2y

在计算机中就可以用一行代码表示:x & ((1 << y) - 1)。

2、生成标记码

我们可以用左移运算符来实现标记码,即1 << k作为第

k

k

k 个标记位的标记码,这样就可以通过一句话,实现对标记位置 0、置 1、取反等操作。

1)标记位置1

【例题1】对于

x

x

x 这个数,我们希望对它二进制位的第

k

k

k 位(从0开始,从低到高数)置为 1。

置 1 操作,让我们联想到了 位或 运算。

它的特点是:位或上 1,结果为 1;位或上0,结果不变。

所以我们对标记码的要求是:第

k

k

k 位为 1,其它位为 0,正好是(1 << k),那么将 第

k

k

k 位 置为 1 的语句可以写成:x | (1 << k)。

有关位或运算的更多内容,可以参考:光天化日学C语言(15)- 位运算 | 的应用。

2)标记位置0

【例题2】对于

x

x

x 这个数,我们希望对它二进制位的第

k

k

k 位(从0开始,从低到高数)置为 0。

置 0 操作,让我们联想到了 位与 运算。

它的特点是:位与上 0,结果为 0;位与上 1,结果不变。

所以在我们对标记码的要求是:第

k

k

k 位为 0,其它位为 1,我们需要的是(~(1 << k)),那么将 第

k

k

k 位 置为 0 的语句可以写成:x & (~(1 << k))。

有关位与运算的更多内容,可以参考:光天化日学C语言(14)- 位运算 & 的应用。

有关 按位取反 运算的更多内容,可以参考:光天化日学C语言(17)- 位运算 ~ 的应用。

3)标记位取反

【例题3】对于

x

x

x 这个数,我们希望对它二进制位的第

k

k

k 位(从0开始,从低到高数)取反。

取反操作,联想到的是 异或 运算。

它的特点是:异或上 1,结果取反;异或上 0,结果不变。

所以我们对标记码的要求是:第

k

k

k 位为1,其余位为 0,其值为(1 << k)。那么将 第

k

k

k 位 取反的语句可以写成:x ^ (1 << k)。

有关 异或 运算的更多内容,可以参考:光天化日学C语言(16)- 位运算 ^ 的应用。

3、生成掩码

同样,我们可以用左移来生成一个掩码,完成对某个数的二进制末

k

k

k 位执行一些操作。

对于(1 << k)的二进制表示为:1 加上 k 个 0,那么 (1 << k) - 1的二进制则代表

k

k

k 个 1。

把末尾的

k

k

k 位都变成 1,可以写成:x | ((1 << k) - 1)。

把末尾的

k

k

k 为都变成 0,可以写成:x & ~((1 << k) - 1)。

把末尾的

k

k

k 位都取反,可以写成:x ^ ((1 << k) - 1)。

通过这一章,我们学会了: 1)位运算 << 的用法; 2)用 << 来生成标记位; 3)用 << 来生成掩码;

希望对你有帮助哦 ~ 祝大家早日成为 C 语言大神!

课后习题

【第43题】左移的应用 | 一句话判断一个数是否是 2 的幂

📢博客主页:https://blog.csdn.net/WhereIsHeroFrom📢欢迎各位 👍点赞 ⭐收藏 📝评论,如有错误请留言指正,非常感谢!📢本文由 英雄哪里出来 原创,转载请注明出处,首发于 🙉 CSDN 🙉作者的专栏: 👉C语言基础专栏《光天化日学C语言》 👉C语言基础配套试题详解《C语言入门100例》 👉算法进阶专栏《夜深人静写算法》

🎨 相关创意作品

世界杯推荐 西班牙VS哥斯达黎加 西班牙爆大冷?哥斯达隐藏黑马
海康威视智能锁怎么样
完美体育365

海康威视智能锁怎么样

📅 11-02 👁️ 1696
如何采摘和保存铁皮石斛花
bst365app

如何采摘和保存铁皮石斛花

📅 09-06 👁️ 4057