🙉饭不食,水不饮,题必须刷🙉
C语言免费动漫教程,和我一起打卡! 🌞《光天化日学C语言》🌞
LeetCode 太难?先看简单题! 🧡《C语言入门100例》🧡
数据结构难?不存在的! 🌳《数据结构入门》🌳
LeetCode 太简单?算法学起来! 🌌《夜深人静写算法》🌌
文章目录
一、前言
本文作者是从 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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
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 <stdio.h>
int main() {
int x = 0b10000000000000000000000000000001;
printf("%d\n", x); // -2147483647
return 0;
}
- 输出结果为:
-2147483647
- 那么,将它进行左移一位以后,得到的结果是什么呢?
#include <stdio.h>
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 ) × 2 = − 2 32 + 2 (-2^{31}+1) \times 2 = -2^{32}+2 (−231+1)×2=−232+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 语言大神!
课后习题
📢博客主页:https://blog.csdn.net/WhereIsHeroFrom
📢欢迎各位 👍点赞 ⭐收藏 📝评论,如有错误请留言指正,非常感谢!
📢本文由 英雄哪里出来 原创,转载请注明出处,首发于 🙉 CSDN 🙉
作者的专栏:
👉C语言基础专栏《光天化日学C语言》
👉C语言基础配套试题详解《C语言入门100例》
👉算法进阶专栏《夜深人静写算法》