| ~ ^ << >> 一堆符号,到底怎么记?
不就是与或非吗能难到哪去今天开始学位运算说实话翻开笔记之前我心里想的是与或非不就是逻辑运算那一套嘛true/false 换成了 1/0 而已有什么好学的。结果看到第一个例子就愣住了——不是判断真假是直接把两个十六进制数按位对齐一位一位地算。0x55 0x33算出来是 0x11。我停下来对着二进制数了半分钟才反应过来原来这里的“与”不是“条件 A 条件 B”是每个二进制位各自做与。这是两个完全不同的东西。后面再看到“清 0 用与置 1 用或”的时候我已经不敢大意了——你以为你懂了其实你只懂了字面意思。“清 0”和“置 1”到底在干什么学到这里我忍不住想了一个问题为什么要费这么大劲去动某一个二进制位直接重新赋个值不行吗然后翻到 51 单片机那部分看到 P1 口那 8 个引脚每个引脚对应寄存器里一位0 是低电平1 是高电平。你要控制其中一个引脚输出高电平不能把整个寄存器都覆盖掉——旁边那 7 个引脚还在干着别的活呢。这时候就理解为什么需要“只改某一位别的位不能动”了。与运算用来清 0 的逻辑是这样的想让某一位变 0就让那一位跟 0 做与。100000不管原来是啥结果都是 0。其他位呢跟 1 做与原来是什么还是什么。所以清 0 的口诀就是“拿 0 去与”。// 想把 a 的第 10 位清零其他位保持不动 int a 0xffffffff; a a ~(1 10); // 110 得到一个只有第 10 位是 1 的数 // ~ 取反之后只有第 10 位是 0其余全是 1 // 跟 a 做与第 10 位必然变 0其他位原样保留或运算置 1 是一个道理想让某一位变 1就拿 1 去或。1|011|11目标位不管原来是啥都给你变成 1。其他位跟 0 去或原来是啥还是啥。所以清 0 用与、置 1 用或背后其实都是同一个逻辑不动的位用中性值罩住与是 1或是 0要改的位用目标值去怼。我当时觉得这俩挺对称的挺好记结果后面一翻看到一行代码直接把我看傻了a a ~(110 | 112 | 113);一行干掉三个位。我才意识到之前学的那些“清 0 置 1”只是基础动作实际写代码的时候都是多个位一起处理的。先把要改的位拼出来再一次性操作。左移右移原来不是“往左挪一下”就完了左移还算好理解an 就是 a 乘以 2 的 n 次方。我在纸上画了一下二进制1 左移 1 位变成 2再移一位变成 4确实就是乘 2。当时还想这比直接写乘法快吗后来查了一下位运算确实比乘除法快很多在嵌入式这种资源紧张的地方是有实际意义的。右移就坑了。我开始以为左移乘 2右移就是除 2 呗——确实也对但笔记里突然冒出两个词逻辑右移和算术右移。我当时愣了一下右移不都一样吗区别在最高位。如果 a 是无符号类型右移之后最高位补 0这叫逻辑右移。如果 a 是有符号类型右移之后最高位补的是原来的符号位这叫算术右移。举个例子-8 用补码表示右移一位如果是逻辑右移最高位补 0就变成正数了这显然不对。算术右移补符号位负数还是负数。这个点我琢磨了好一会儿平时写代码不太留意有符号无符号但碰到右移的时候类型选错了结果能差出十万八千里。异或看起来最不起眼结果花样最多异或的规则就八个字相同为 0不同为 1。我当时扫了一眼就想翻页结果被两道题摁住了。第一道用异或交换两个数不用第三个变量。int a 0x55, b 0x33; a a ^ b; // 此时 a 变成了 a^bb 还是原来的 b b a ^ b; // b (a^b)^b ab 拿到了 a 原来的值 a a ^ b; // a (a^b)^a ba 拿到了 b 原来的值 // 就这么三行两个数交换完了不用临时变量我在纸上把每一步按位推了一遍推到第三行的时候突然就觉得这个东西挺妙的——利用了“一个数异或自己得 0异或 0 得自己”这两个性质绕了一圈就把值换过来了。第二道更有意思位运算还能做加密。明文用某个密钥异或一次得到密文密文再用同一个密钥异或一次明文就回来了。笔记里演示的就是这个效果0x55 异或 0x33 得到 0x660x66 再异或 0x33又变回 0x55。学到这儿我是真觉得位运算不像一开始以为的那么无聊了——跟之前学的那些基础语法完全不是一个感觉。两个小练习逼出了“组合拳”思维这种多个位一起操作的思路后面两个练习题里就用上了。笔记后面给了一个面试题统计一个 int 里有多少个 1。我的第一反应是一个一个数但一个 int 有 32 位循环 32 次也太蠢了。笔记里给的思路是“跟 1 做与判断最低位是不是 1然后右移一位继续”——我觉得这是最直观的方法虽然肯定有更高效的算法但目前先把这个搞懂就够了。还有一个“循环左移”的题目大概思路是先看最高位是 0 还是 1然后左移腾出最低位如果刚才最高位是 1 就把最低位或成 1。这个我还没自己敲但大概方向理解了——就是用 判断用 | 设置用 移动几个符号打配合。其实学到位运算这里我感觉它不像一个独立的知识点更像一种底层编程的手感——你要时刻意识到数据在内存里是一串二进制你要做的就是精确控制这串二进制里的某几位。这种感觉在之前学变量、循环、数组的时候从来没有过。情况为什么容易错把 当成 把 | 当成 ||脑子里还停留在逻辑运算模式忘记这里是一个一个位单独算右移时忘了区分有符号和无符号平时写代码不关注最高位补什么但右移的时候补 0 还是补符号位结果完全不同清 0 用 |置 1 用 两者操作逻辑相反刚学的时候非常容易记反写反了该清的没清、该置的没置以为 ~ 取反只是把 0 变 1、1 变 0 就完了忽略了 ~ 是按位取反整个数包括高位全变配合 用的时候容易算错范围异或交换两个数以为可以随便用浮点数不能做位运算用异或交换也只适用于整型而且两个数指向同一块内存的时候会出 bug左移以为就是简单往左挪左移超过类型宽度是未定义行为而且左移一位是乘 2 没错但不能无脑替代乘法今天学完位运算脑子有点发热。从几个小符号开始一路跑到操作寄存器、控制硬件、甚至看了 2048 的项目框架信息量大得有点撑。不过我很确定一点位运算不是一个背规则的东西得在纸上多画几遍二进制真的把每一位的变化看清楚心里才踏实。今天没来得及看 2048 的具体实现明天得把合并和移动的算法跑一遍——刚好顺便把 Day15 的结构体和数组用上。Day16 学习记录明天继续。