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. 将1转换为二进制,得到00000001(假设使用8位二进制表示)
  2. 取反操作:将每一位的0变为1,1变为0,得到 11111110
  3. 加1:将上一步得到的结果加1,得到 11111111

-2可以用11111110表示

  1. 将2转换为二进制,得到00000010(假设使用8位二进制表示)
  2. 取反操作:将每一位的0变为1,1变为0,得到 11111101
  3. 加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结果
000
010
100
111

例子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表达式的值
000
0非0非0
非00非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表达式的值
000
0非0非0
非00非0
非0非00

如果操作数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 的值。

需要注意的是,取反运算符 ~ 对于有符号类型的整数,其操作结果取决于编译器的实现方式。通常情况下,编译器会将有符号整数的二进制表示按照补码进行解释,并对其进行取反操作,但也有可能采用其他方式。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐