【c语言tips】-位运算符(<< >> & | ~ ^)
1. 左移(<<)1.1 概念在C语言中,左移操作符(<<)会将被操作的数向左移动指定的位数,右侧补0,得到一个新的结果。左移操作符的一般语法如下:result = value << n;其中,value是要进行左移操作的数,n是要左移的位数,result是左移后的结果。左移操作符可以用于快速计算2的幂次方,例如2的n次方可以用左移n位的方式进行计算。左移操作符
1. 左移(<<
)
1.1 概念
在C语言中,左移操作符(<<)会将被操作的数向左移动指定的位数,右侧补0,得到一个新的结果。左移操作符的一般语法如下:
result = value << n;
其中,value
是要进行左移操作的数,n
是要左移的位数,result
是左移后的结果。
左移操作符可以用于快速计算2的幂次方,例如2的n次方可以用左移n位的方式进行计算。左移操作符也可以用于对比特位进行操作。
需要注意的是,如果左移的位数超出了该类型所能表示的位数范围,或者左移后得到的值超出了该类型的范围,将会导致未定义行为和错误的结果。此外,在对有符号整数进行左移操作时,需要注意符号位的变化,因为左移操作不会保留符号位。因此,对于有符号整数,需要将其先转换为无符号整数再进行左移操作,以避免符号位的变化。
1.2 例子1
m << 2
--> m * 4
m << n
–> m * 2^n
1.3 例子2
4的二进制位00000100
4 << 1 ==> 8的二进制位00001000
即1向左移动了一位
1.4 例子3
int a = b*32 <=> int a = b << 5
1.5 例子4
-1 * 2 = -2
-1用二进制可以表示为11111111,可表示为全高电平
- 将1转换为二进制,得到00000001(假设使用8位二进制表示)
- 取反操作:将每一位的0变为1,1变为0,得到 11111110
- 加1:将上一步得到的结果加1,得到 11111111
-2可以用11111110表示
- 将2转换为二进制,得到00000010(假设使用8位二进制表示)
- 取反操作:将每一位的0变为1,1变为0,得到 11111101
- 加1:将上一步得到的结果加1,得到 11111110
11111111左移一位=>11111110,假设我们存储就八位,左移一位需要把最后面的那一位补0
2. 右移(>>
)
2.1 概念
在C语言中,右移操作符会将被操作的数向右移动指定的位数,并且根据移动后最高位的值(即符号位)来决定移动后的结果。
-
如果移动的数为正数,那么右移时在左侧补0;
-
如果移动的数为负数,那么右移时在左侧补1。
需要注意的是,在右移操作符中,如果移动的数为负数,则有可能会出现所谓的“算术右移”(也称为“符号扩展”)的情况,即在左侧补1的同时,保留符号位,以保证移动后的结果仍为负数。例如,对于-8进行右移操作,假设移动了3位,则根据补码规则,补1后得到的结果为11111000,而保留符号位后得到的结果则为11111100,即-2。
因此,在使用右移操作时,如果移动的数为负数,则需要考虑符号位的扩展问题,以避免出现错误的结果。可以使用类型转换或逻辑右移操作符来避免这种情况。
2.2 例子1
当对有符号数进行右移操作时,需要考虑符号位的影响。一种常用的方法是将有符号数的二进制表示图形化,以便更好地理解符号位的变化。
例如,假设有一个8位的有符号数-10
,其二进制表示为10000110
。如果将其向右移动2位,则根据右移操作符的定义,在左侧补上符号位即可。因为符号位为1,所以在左侧补1,得到新的二进制表示为11100001
。这个新的二进制表示对应的十进制数为-3,即-10右移2位得到的结果为-3。
原始二进制表示:10000110
右移两位后: 11100001
上面的图示表示了将有符号数-10右移两位后的二进制表示,其中在右移操作中保留符号位的值不变,并在左侧补充了1,以得到正确的结果。
2.3 例子2
对于无符号数的右移操作,在右侧补零即可。因为无符号数没有符号位,所以在右移操作时不需要考虑符号位的变化。
例如,假设有一个8位的无符号数26,其二进制表示为00011010。如果将其向右移动3位,则在左侧补零即可,得到新的二进制表示为00000011。这个新的二进制表示对应的十进制数为3,即26右移3位得到的结果为3。
以下是将无符号数26右移3位的图示:
原始二进制表示:00011010
右移三位后: 00000011
上面的图示表示了将无符号数26右移三位后的二进制表示,其中在右移操作中在左侧补零,以得到正确的结果。
2.4 例子3
假设a为有符号的负数,我们在循环中一直对它做右移操作,但是它一直会在首位补1,就不直不会为0,循环将无限进行下去
int a = -10;
while (a)
{
a = a >> 1
}
printf("--------------------------")
3. 与(&
)
3.1 概念
C语言中的逻辑与操作用符号“&”表示,其作用是对两个操作数进行逐位与运算。逻辑与操作的规则是,只有当两个操作数的相应位都为1时,结果的相应位才为1,否则为0。下面是逻辑与操作的真值表:
操作数1 | 操作数2 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
例子1:
例如,假设有两个无符号整数a和b,其二进制表示分别为10110100和01011010。对这两个数进行逻辑与操作,得到的结果为00010000。具体计算过程如下:
10110100
& 01011010
--------
00010000
因为在两个数的二进制表示中,只有第4位是同时为1的,因此在逻辑与操作中,只有第4位为1,其它位都为0,所以得到的结果是00010000。
例子2:屏蔽清零作用
int a = 0x1234;
a & 0xff00; //屏蔽低8位
4. 或(|
)
C语言中的或运算符用符号"||"表示,它是一种逻辑运算符。它的作用是在两个条件表达式之间进行逻辑或运算,当任意一个条件表达式为真(非零)时,整个表达式的值就为真(非零),否则为假(0)。
或运算符的真值表如下:
操作数1 | 操作数2 | 表达式的值 |
---|---|---|
0 | 0 | 0 |
0 | 非0 | 非0 |
非0 | 0 | 非0 |
非0 | 非0 | 非0 |
如果操作数1和操作数2都为0,那么表达式的值为0,否则表达式的值为非0。只要有一个操作数为非0,那么表达式的值就是非0。
例子1:设置为高电平
A | 1 = 1
设置一个资源的第五位为高电平,其他位不变
int a;
a = a|100000; // 比较直观的方式
a = a | (1 << 5) // 更为优雅的方法,程序员写的更简单
清除一个资源的第五位为低电平,其他位不变
int a;
a = a & (~(1 << 5))
搞嵌入式驱动或者做寄存器配置的时候这两种方法非常重要!!!!!!!!!!!!!!!!!面试必考!!!!!!!!!!!
5. 异或(^
)
C语言中的异或运算符用符号"^"表示,它也是一种逻辑运算符。它的作用是在两个条件表达式之间进行逻辑异或运算,当条件表达式不同时,整个表达式的值为真(非零),否则为假(0)。
异或运算符的真值表如下:
操作数1 | 操作数2 | 表达式的值 |
---|---|---|
0 | 0 | 0 |
0 | 非0 | 非0 |
非0 | 0 | 非0 |
非0 | 非0 | 0 |
如果操作数1和操作数2相等,那么表达式的值为0,否则表达式的值为非0。
交换数值的骚操作
在 C 语言中,交换两个变量的值是一个常见的操作。通常情况下,我们会使用一个中间变量来完成交换:
int a = 5, b = 10;
int temp = a;
a = b;
b = temp;
// 现在 a = 10,b = 5
但是,使用中间变量的方法会占用额外的内存空间,有时候可能不是很方便。此时,我们可以使用异或运算符 ^
来完成交换,而不需要额外的变量。
假设有两个变量 a 和 b,它们的值分别为 x 和 y。我们可以使用以下代码来完成交换:
int a = 5, b = 10;
a ^= b;
b ^= a;
a ^= b;
// 现在 a = 10,b = 5
这个方法的原理基于异或运算的性质。假设有两个二进制数 a 和 b,它们的某一位分别为 x 和 y。则有:
- 当 x 和 y 相同时,异或的结果为 0;
- 当 x 和 y 不同时,异或的结果为 1。
因此,当我们将 a 和 b 分别异或一次后,得到的结果是:
- a ^= b 得到了 a 和 b 的某些位上的不同之处;
- b ^= a 得到了 b 的值,但同时将 a 的值也存储在了 b 中;
- a ^= b 得到了 a 的值,但同时将 b 的值也存储在了 a 中。
最终,a 和 b 的值就完成了交换。
需要注意的是,这种方法仅适用于整数类型的变量。对于其他类型的变量,如浮点数或字符类型,不能使用异或运算来完成交换。
假设有两个变量 a 和 b,它们的值分别为 5 和 10。我们可以用二进制表示它们的值:
a = 0b00000101
b = 0b00001010
接下来,我们可以使用异或运算符 ^
来完成交换:
a ^= b;
b ^= a;
a ^= b;
执行第一步操作 a ^= b
后,a 的值变成了:
a = 0b00001111
这是因为 a ^= b
实际上是对 a 和 b 的二进制表示中每一位进行异或运算。在这个例子中,a 和 b 的二进制表示中的每一位如下:
a: 0 0 0 0 0 1 0 1
b: 0 0 0 0 1 0 1 0
因此,a ^= b 的结果是:
a: 0 0 0 0 1 1 1 1
接下来,我们执行 b ^= a
操作。由于 a 的值已经改变了,因此 b ^= a 的结果将 b 的值改为了 a ^ b 的值:
a: 0 0 0 0 1 1 1 1
b: 0 0 0 0 1 1 0 1
最后,我们执行 a ^= b
操作,得到:
a: 0 0 0 0 0 1 0 1
b: 0 0 0 0 1 1 0 1
现在,a 和 b 的值已经完成了交换,a 的值为 10,b 的值为 5。
这个方法的实现原理其实很简单:通过异或运算,将 a 和 b 的不同之处保存在 a 和 b 中,再进行一次异或运算,将原先保存的 a 和 b 的值分别存储到 b 和 a 中,完成了交换。这样做不仅可以避免使用额外的变量,还可以让代码更加简洁高效。
判断两个数的符号是否相同
int x = 10, y = -20;
if ((x ^ y) < 0) {
printf("x 和 y 的符号不同\n");
} else {
printf("x 和 y 的符号相同\n");
}
将一个整数的某些位取反
int n = 0b11001100;
int mask = 0b00001111; // 取反低 4 位
n ^= mask;
// 现在 n = 0b11000011
6. 非(~
)
C语言中,取反运算符 ~
是一个一元运算符,用于对一个整数的每一位进行取反操作,即将 0 变为 1,将 1 变为 0。
下面是一些使用取反运算符的例子:
unsigned char a = 0b10101010;
unsigned char b = ~a; // b 的值为 0b01010101
在这个例子中,~
运算符将 a 的值每一位进行取反操作,得到了 b 的值。
需要注意的是,取反运算符 ~
对于有符号类型的整数,其操作结果取决于编译器的实现方式。通常情况下,编译器会将有符号整数的二进制表示按照补码进行解释,并对其进行取反操作,但也有可能采用其他方式。
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)