🚀前言
大家好啊😉!承接之前的操作符上篇,今天阿辉将介绍剩下的操作符,包括移位操作符,位操作符以及单目操作符,持续输出干货中,关注阿辉不迷路哦 😘 ,内容干货满满😋,接下来就跟着阿辉一起学习吧👊
🚀整型的存储以及原、反、补码
介绍移位操作符和位操作符前得先了解整型在内存中的存储以及原反补码,阿辉上篇文章上篇文章整型的存储里面有详细的讲解(点击直接跳转哦 😘)
🚀移位操作符
移位操作符有何作用?
移位操作符移动的是数据在内存中存储的补码的二进制位,其中移位操作符的“位”字就是指二进制位
移位操作符有左移操作符和右移操作符两种
✈️左移操作符(<<)
左移操作符怎么使用呢?
左操作数 << 右操作数 左操作数是要被左移位的数 右操作数是要左移移动的位数
左移操作符的规则就是左边丢弃,右边补0
我们来看一个🌰栗子
int main() { int a = 10; 0000 0000 0000 0000 0000 0000 0000 1010 ->a原码 0000 0000 0000 0000 0000 0000 0000 1010 ->a反码 0000 0000 0000 0000 0000 0000 0000 1010 ->a补码 int b = a << 2; 0000 0000 0000 0000 0000 0000 0010 1000 ->b补码 b也就是40 return 0; }
是不是有点懵😏,别急阿辉用图演示👇
现在是不是对左移操作符用法清晰多了😁 ,上述演示仅仅使用了正数,不过负数同样可以,负数仅仅是多了原反补码之间的转换
✈️右移操作符(>>)
右移操作符的使用
左操作数 >> 右操作数 左操作数是要被右移位的数 右操作数是要右移移动的位数
右移操作符的规则
- 算术右移:左边补符号位,右边丢弃
- 逻辑右移:左边补0,右边丢弃
对于使用逻辑右移还是算术右移从语言并未明确规定,但大部分编译器都使用算术右移,对于无符号数使用逻辑右移
算术右移:左移用正数,咱们这次用负数演示👊
int main() { int a = -10; 1000 0000 0000 0000 0000 0000 0000 1010->a原码 1111 1111 1111 1111 1111 1111 1111 0101->a反码 1111 1111 1111 1111 1111 1111 1111 0110->a补码 int b = a >> 2; 1111 1111 1111 1111 1111 1111 1111 1101->b补码 1000 0000 0000 0000 0000 0000 0000 0010 1000 0000 0000 0000 0000 0000 0000 0011->b原码 也就是-3 return 0; }
逻辑右移:逻辑右移也与上述算数右移的运算是类似的,不过逻辑右移不管你是正数还是负数,左边通通补0
注意
- 移位操作符的操作数只能为整型
- 对于移位运算符,不要移动负数位,这个是标准未定义的
a>>1
或a<<1
这两个都不会改变a
的值,就像a+1
这样并不会改变a
的值
🚀位操作符
位操作符分类
& 按位与 | 按位或 ^ 按位异或 ~ 按位取反 & | ^ 这三个有两个操作数 ~ 只有一个操作数
这里的位同样指的是二进制位,它们操作的依然是内存中的补码
&按位与: 两操作数内存中的补码两相同二进制位均为1
就为1
,其中一个0
就为0
🌰栗子
int main() { int a = 3; 0000 0000 0000 0000 0000 0000 0000 0011 -> 3的原反补码 int b = -5; 1000 0000 0000 0000 0000 0000 0000 0101 -> -5的原码 1111 1111 1111 1111 1111 1111 1111 1010 -> -5的反码 1111 1111 1111 1111 1111 1111 1111 1011 -> -5的补码 int c = a & b; 1111 1111 1111 1111 1111 1111 1111 1011 -> -5的补码 0000 0000 0000 0000 0000 0000 0000 0011 -> 3的补码 0000 0000 0000 0000 0000 0000 0000 0011 -> 按位与结果 符号位为0,原反补码相同,值为3 return 0; }
| 按位或: 两操作数内存中的补码两相同二进制位均为0
就为0
,其中一个1
就为1
|
与&
的用法类似这里就不举例子了
^按位异或: 两操作数内存中的补码两相同二进制位相同为0
,相异为1
🌰栗子
int main() { int a = 3; 0000 0000 0000 0000 0000 0000 0000 0011 ->3原反补码 int b = 3; int c = a ^ b; 0000 0000 0000 0000 0000 0000 0000 0011 ->3原反补码 0000 0000 0000 0000 0000 0000 0000 0011 ->3原反补码 0000 0000 0000 0000 0000 0000 0000 0000 异或结果 int d = c ^ a; 0000 0000 0000 0000 0000 0000 0000 0000 ->c 0000 0000 0000 0000 0000 0000 0000 0011 ->a 0000 0000 0000 0000 0000 0000 0000 0011 异或结果 int e = a ^ a ^ b; int f = a ^ b ^ a; return 0; }
由上面的例子,我们可以得到以下结论👇
相同数字异或结果为0即
a ^ a = 0
,0与任何数字异或仍为该数字即0 ^ a = a
并且
^
支持交换律与结合律即a ^ b ^ c = a ^ c ^ b
~按位取反: 操作数在内存中的补码按二进制位0
改为1
,1
改为0
🌰栗子
int main() { int a = 0; 0000 0000 0000 0000 0000 0000 0000 0000 ->0的原反补码 1111 1111 1111 1111 1111 1111 1111 1111 ->按位取反结果 1000 0000 0000 0000 0000 0000 0000 0001 ->原码值为-1 int c = ~a; return 0; }
注意:他们的操作数必须为整数
✈️一道变态面试题
不创建新变量交换两个整数
int main() { int a = 10; int b = 12; a = a ^ b; b = a ^ b; a = b ^ a; return 0; }
上面代码是不是很懵 😆,第一次我也很懵逼,我们接着看👇
a = a ^ b
这时a
的值不在是10
而是10 ^ 12
,然后b = a ^ b
实际上是b = 10 ^ 12 ^12
,由上面我们知道的^
的特点可知10 ^ 12 ^ 12 = 10
,这时b = 10
,在看这句a = a ^ b
实际上是a = 10 ^ 12 ^ 10
,而10 ^ 12 ^ 10 = 12
这时a = 12
所以a
和b
的值完成了交换
🚀单目操作符
单目操作符的分类:
! 逻辑反操作 - 负值 + 正值 & 取地址 sizeof 操作数的类型长度(以字节为单位) ~ 对一个数的二进制按位取反 -- 前置、后置-- ++ 前置、后置++ * 间接访问操作符(解引用操作符) (类型) 强制类型转换
✈️ 逻辑反操作(!)
🌰栗子
int main() { int a = 0; int b = 4; if (!(b == 4)) printf("真"); printf("%d ", !a); printf("%d\n", !b); return 0; }
逻辑反操作即把0 (假) 改为1 (真) ,非0 (真) 改为0 (假)
✈️sizeof
sizeof这个操作符,sizeof是由来计算变量(类型)所占空间的大小,不关注存储的内容,单位是字节
int main() { int a = 2; int b[4] = { 0 }; char c = '1'; printf("%u\n", sizeof(b)); printf("%u\n", sizeof(b[0])); printf("%u\n", sizeof(a)); printf("%u\n", sizeof(c)); printf("%u\n", sizeof(int)); return 0; }
sizeof的操作数为数组名时计算的整个数组的大小
🚀剩下的操作符
其中有一些过于简单相信大家都掌握了,至于**结构体成员访问操作符(.)和(->)**会在后续结构体章节详细讲到, **解引用操作符(*)和取地址操作符(&)**会在后续数组篇中讲解
🚀复杂的表达式如何计算
C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序
✈️优先级
相邻操作符的优先级高的先算
int main() { int a = 3; int b = 4; a - b * a; }
上述代码*
乘的优先级更高先算b * a
而不是先算a - b
✈️结合性
当相邻两操作符优先级相同就要看结合性了,左结合从左向右计算,右结合从右向左计算
int main() { int a = 3; int b = 4; a / b * a; }
上述代码中/
和*
的优先级相同都是左结合,因此先算a / b
下面是一张操作符优先级与结核性的表
虽然我们知道了操作符的结合性与优先级,但这并不能保证表达式具有唯一计算路径,因此尽量不要写过于复杂的表达式很容易出bug
到这里,阿辉今天对于C语言中操作符的分享就结束了,希望这篇博客能让大家有所收获, 如果觉得阿辉写得不错的话,记得给个赞呗,你们的支持是我创作的最大动力🌹