【C】浅析 运算符

目录

1. 引言

2. 查看反汇编

2.1 VS2008 下查看反汇编

2.2 VC++6.0 下查看反汇编

3. 算术运算符

4. 移位操作符

4.1 左移操作符

4.2 右移操作符

5. 位运算符

5.1 位与运算(&)的典型应用

5.1.1 清零

5.1.2 获取某数字的指定位

5.1.3 判断一个数是不是 2 的幂次或者判断一个数的二进制位中有多少个 1

5.2 位或运算(|)的典型应用

5.2.1 设定一个数据的指定位

5.3 位异或运算(^)的典型应用

5.3.1 定位翻转

5.3.2 数值交换

5.3.3 求平均值

6. 赋值运算符

7. 复合运算符

8. 单目运算符

9. 关系运算符

10. 逻辑操作符

11. 条件操作符

12. 逗号表达式

13. 下标引用、函数调用和结构成员

14. 来自《C和指针》的操作符优先级列表


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 的个数。位运算的一些思考icon-default.png?t=LBL2https://blog.csdn.net/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); 具体可参见利用位运算求平均值icon-default.png?t=LBL2https://blog.csdn.net/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和指针》的操作符优先级列表

注意:该表格的 操作符优先级从上到下,依次降低

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值