目录
5.1.3 判断一个数是不是 2 的幂次或者判断一个数的二进制位中有多少个 1
1. 引言
先来看看下面这段代码输出什么?
#include <stdio.h>
int main(void) {
int a = 1;
int ret = 0;
ret = (++a) + (++a) + (++a);
printf("%d\n", ret);
return 0;
}
在 VS2008 中运行上述代码,输出结果为 12。
在 VC++6.0 中运行上述代码,输出结果为 10。
同样的代码输出了不一样的结果,这是为什么呢?
通过查阅资料得知,复杂表达式的求值是由三个因素决定的:操作符的优先级、操作符的结合性 以及 操作符是否控制求值顺序。
2. 查看反汇编
接下来我们思考下 上述代码是如何执行的呢?
2.1 VS2008 下查看反汇编
首先在 VS2008 中按下 F10 逐过程调试,然后鼠标右击转到反汇编。
我们可以看到,a 中存放 1,ret 中存放 0,首先把 a 分配给 eax,然后 eax 自加 1 得到的值存在 eax 中,此时 eax 的值是 2,然后 eax 把 2 赋给 a,接着 a 把 2 赋给ecx,然后 ecx 自加 1 得到的值存放在 ecx 中,此时 ecx 的值为 3,然后 ecx 把 3 赋给 a,再次是 a 把 3 赋给 edx,edx 自加 1 后得到的值存在 edx 中,此时 edx 的值为 4,edx 把 4 赋给 a,然后 a 再把 4 赋给 eax,此时 eax 加 a 得到的值为 8 存放在 eax 中,紧接着 eax 再加上 a 得到 12 存放在 eax 中,最后 eax 把 12 赋给 ret,程序结束。
2.2 VC++6.0 下查看反汇编
在 VC++6.0 中按下 F10 逐过程调试,然后鼠标右击转到反汇编。
我们可以看到,ebp-4 中存放 1,ebp-8 中存放 0,首先把 ebp-4 的值分配给 eax,然后 eax 自加 1 得到的值存在 ebp-4 中,此时 ebp-4 的值是 2,然后 ebp-4 把 2 赋给 ecx,然后 ecx 自加 1 得到的值存放在 ebp-4 中,此时 ebp-4 的值为 3,然后 ebp-4 把 3 赋给 edx,紧接着 ebp-4 的值(3)加上 edx 的值(3)等于 6 先存放在 edx 中,ebp-4 又把 3 赋给 eax,eax 自加 1 后得到的值存在 eax 中,此时 eax 的值为 4,eax 把 4 赋给 ebp-4,此时 edx 加上 ebp-4 得到的值为(6+4=10)存放在 edx 中,最后edx 把 10 赋给 ebp-8,也就是赋给 ret,程序结束。
通过上述分析,可以发现同一段代码的求值顺序不同,输出结果也有所不同,
切记万万不可写出这样的代码,因为这样的表达式是不可移植的。
由此可见,C 语言中的操作符是多么的重要。接下来为了方便解释,我按照操作符的功能对其进行了分类。它们分别是:算术操作符、移位操作符、位操作符、赋值操作符、单目操作符、关系操作符、逻辑操作符、条件操作符、逗号表达式以及下标引用、函数调用、结构成员等等。
3. 算术运算符
+ - * / %
注意:% 操作符不能作用于浮点数。
4. 移位操作符
<< 左移操作符 >> 右移操作符
4.1 左移操作符
移位规则:左边丢弃,右边补 0
4.2 右移操作符
- 逻辑右移
右边丢弃,左边补
0
- 算术右移
右边丢弃,左边补
原符号位
注意:移位运算符不能移动负数位,这是标准未定义的。
5. 位运算符
& | ^
注意:位运算符的操作数必须是整数。
5.1 位与运算(&)的典型应用
5.1.1 清零
将原来数据中为 1 的位更新为 0,假设该数字为 5,那么执行清零操作只需要 0x5 & 0x0 即可。
5.1.2 获取某数字的指定位
例如获取数字 num 的低八位的操作为 num = num & 0xff。
5.1.3 判断一个数是不是 2 的幂次或者判断一个数的二进制位中有多少个 1
bitnum = x & (x - 1)
让 x 的二进制位中最右侧的 1 置为 0,如果 bitnum 为 0,那么表示该数字是 2 的幂次,如果 bitnum 不为 0,计数器加 1,继续执行该语句,直到 bitnum 为 0,终止循环,此时计数器的值就是二进制位中 1 的个数。位运算的一些思考
https://blog.youkuaiyun.com/sustzc/article/details/79774007
5.2 位或运算(|)的典型应用
5.2.1 设定一个数据的指定位
例如将数字 value 的低八位置为 1 的操作为 value = value | 0xff。
5.3 位异或运算(^)的典型应用
5.3.1 定位翻转
指定位的翻转,例如将数字 a 低八位翻位的操作为 a = a ^ 0xff。
5.3.2 数值交换
a = a ^ b; b = a ^ b; a = a ^ b;
5.3.3 求平均值
avg = (num1 & num2) + ((num1 ^ num2) >> 1); 具体可参见利用位运算求平均值
https://blog.youkuaiyun.com/sustzc/article/details/79615022
6. 赋值运算符
=
7. 复合运算符
+= -= *= /= %= <<= >>= &= |=
8. 单目运算符
! - + & sizeof ~ -- ++ * (类型)
注意:
- sizeof() 内部的表达式是不参与运算的;
- sizeof(数组名) 中数组名表示整个数组,整个表达式求取的是整个数组的大小;
- &数组名,这个取地址操作取出的是整个数组的大小;
- 除此之外,其他遇到的数组名都指的是 数组首元素的地址。
9. 关系运算符
> >= < <= == !=
注意:切记不要把 == 与 = 搞混淆。
10. 逻辑操作符
&& ||
- &&
如果左操作数的值为假,那么右操作数便不再进行求值,因为后边的表达式的值逻辑与 0 必然是假的,其右操作数的值已无关紧要。
- ||
首先对左操作数进行求值,如果它的值为真,右操作数便不再求值,因为后边的表达式的值逻辑或上 1 必然是真的,其右操作数的值已无关紧要。
注意:逻辑操作符具有 “短路” 性,这两个操作符会对表达式的求值顺序施加控制。
11. 条件操作符
exp1 ? exp2 : exp3
接受三个参数,它会对表达式的求值顺序加以控制。
12. 逗号表达式
exp1, exp2, exp3, ……, expN
逗号表达式,从左到右依次执行,整个表达式的结果就是 最后一个表达式的结果。
13. 下标引用、函数调用和结构成员
- []
操作数:一个数组名 + 一个索引值
- ()
接受一个或者多个操作数,第一个操作数是函数名,剩余的操作数是传递给函数的参数。
- 访问一个结构体的成员
- 结构体变量名.成员名;
- 结构体指针变量名->成员名。
14. 来自《C和指针》的操作符优先级列表
注意:该表格的 操作符优先级从上到下,依次降低。