移位操作符和位操作符

玩转C语言!从移位操作符和位操作符开始,让你的代码更加高效!」

@[TOC]

移位操作符、位操作符、二进制(C语言)

大家好,今天给大家带来的是移位操作符、位操作符、二进制详解。
(本章花费3.5小时,若对你有帮助,还请点个免费的赞)
我是黎明_CL,那接下来我们进入主题!

一、移位操作符

  • “<<” 左移操作符
  • “>>” 右移操作符
    核心:移位操作流程为:将数写为原码—>反码—>补码—移位—补码—>反码—>原码——数
  • 计算机能识别的是二进制信息 0 1
    • 使用位移操作符对整型的二进制序列进行有效数位的左右移动;
      移动的是计算机的二进制位,所以接下来我们先讨论整数的二进制表现形式:
      首先先了解下二进制的相关知识

1. 二进制知识补充

<1>二进制的书写:

由于我们熟悉10进制,所以我们根据10进制来学习二进制

  • 10进制
    每位都是有权重的,例如对于22:
    7cf728f7f8c8eeae3451c8381995a9ea_MD5

<2>二进制码的三种表现形式:

  1. 原码
  2. 反码
  3. 补码——能被机器识别的
    不管是正负都可以写出其原码,根据正负直接写出的二进制序列就是原码
  • 正整数的原码、反码、补码是相同的
  • 负整数的原码、反码、补码是要计算的:
    • 反码符号位不变,其它位按位取反
    • 补码=反码+1
      接下来给大家举个例子:
1
2
3
4
5
6
7
8
9
10
//正数
int a = 23;
//00000000000000000000000000010111 - 原码
//00000000000000000000000000010111 - 反码
//00000000000000000000000000010111 - 补码
//负数
int b = -22;
//10000000000000000000000000010111 - 原码
//11111111111111111111111111111101000 - 反码(符号位不变,其他位按位取反)
//11111111111111111111111111111100001 - 补码(=反码+1)

整数在内存中存储的是补码,计算时也使用补码
相信聪明的你一定学fei了。

2. 移位操作符

<1> “>>”

  1. 算数右移(右边丢弃,左边补原来的符号位)
  2. 逻辑右移(右边丢弃,左边直接补0)
    接下来开始验证当前编译器是算数右移还是逻辑右移?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
int a = -23;
//10000000000000000000000000010111 - 原码
//11111111111111111111111111101000 - 反码
//11111111111111111111111111101001 - 补码
//算数右移
int b = a >> 1;
//11111111111111111111111111110100 - 补码 右移一位得
//11111111111111111111111111110011 - 反码(=补码-1)
//10000000000000000000000000001100 - 原码 -12
printf("%d\n", a); //-23
printf("%d\n",b); //-12
return 0;
}
  • 若当前编译器采用逻辑右移,即往首位补0,则以下结果应为负数;

  • 因此可判断VS编译器为算数右移
    ac12f3405c380f28c3f9279d3fc4e0ac_MD5

  • 对于以下结果,我们可以理解将正数右移一位可看为将其向下取整;但负数则不行。
    48ed5ee3462fc3a6f96765509367d3d2_MD5

<2> “<<”

  • 相较于右移操作符,左移操作符更为简单,话不多说,我们直接上代码:
1
2
3
4
5
6
7
8
9
int main()
{
int a = 9;
//00000000000000000000000000000101
int b = a << 1;
//【00000000000000000000000000001010】
printf("%d", b); //18
return 0;
}

ad6c261f0688fe7494014a62751b5526_MD5

  • 二进制
1
2
//原码就是2进制,整形,4个字节,32比特位。正数原码,反码,补码相同。负数反码由原码的符号位不变,其他位按位取反得到,然后反码+1就是补码。整数在内存中存放的是补码,计算的也是补码
//

位操作符—也是操作二进制位
原码
反码
补码

二、位操作符(操作数必须为整数)

1. ‘ & ‘ 按位与

  • 规则:对应(二进制)位 有0则为0,同时为1则为1;
  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
int a = 6;
//00000000000000000000000000000110 - 补码
int b = -5;
//10000000000000000000000000000101 -原
//11111111111111111111111111111010 -反
//11111111111111111111111111111011 -补
int c = a & b;
//00000000000000000000000000000110 - 补码
//11111111111111111111111111111011 -补
//00000000000000000000000000000010 -按位与 &
printf("%d\n", c); // c=2
return 0;
}
  • 结果测试正确c50f153bd48013d6dfcfe103b1a9c8cc_MD5

2. ‘ | ‘ 按位或

  • 规则:对应(二进制)位有1则为1,两个同时位0则为0
  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//按位或'|'
int main()
{
int a = 6;
//00000000000000000000000000000110 - 补码
int b = -5;
//10000000000000000000000000000101 -原
//11111111111111111111111111111010 -反
//11111111111111111111111111111011 -补
int c = a | b;
//00000000000000000000000000000110 - a补
//11111111111111111111111111111011 -b补
//11111111111111111111111111111111 -补 - (按位或 |)
//11111111111111111111111111111110 -反 -(补码-1)
//10000000000000000000000000000001 -原 -(按位取反)
printf("%d\n", c); //-1
return 0;
}
  • 结果测试正确af587593ff128ad284d0284c5bab27ca_MD5

3. ‘ ^ ‘ 按位异或

  • 规则:按(二进制)位异或 - 对应(二进制)位不同位1,相同为0;
  • 特点: 异或具有交换律,证明如下:
1
2
3
4
5
6
7
8
9
10
//a^a—>0
//a^0=a
//假设 a=3 b=5
// a 011
// b 101
// a^b 110
// ①a^b^a110 =5=b
//②a^a^b=b
//0^b=b=5
综上,具有交换率
由此得出异或是支持交换率的
  • 代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
int a = 6;
//00000000000000000000000000000110 - 补
int b = -5;
//10000000000000000000000000000101 -原
//11111111111111111111111111111010 -反
//11111111111111111111111111111011 -补
int c = a ^ b;
//00000000000000000000000000000110 - a补
//11111111111111111111111111111011 - b补
//11111111111111111111111111111101 -补
//11111111111111111111111111111100 -反 -(补码-1)
//10000000000000000000000000000011 -原 -(按位异或)
printf("%d\n", c); //c = -3
return 0;
}
  • 结果测试正确![[Pasted image 20230512180206.png]]

三、应用

1. 不创建临时变量,实现两数交换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
int a = 3;
int b = 7;
//1.创建临时变量
/*int temp = a;
a = b;
b = temp;*/
//2.不创建临时变量
/*a = a + b;
b = a - b;
a = a - b;*/
//通过计算可以得到两数交换的值,可以赋值来思考下实现过程
//但这种交换是有bug的,当值超过表示最大值范围时,会发生数据截断,导致数据错误;
//3.优化终极版本——按位异或实现
a = a ^ b;
b = a ^ b; //此处a等价于a^b,等式等价于b=a^b^b=a
a = a ^ b; //此处b为a,a为a^b,等式等价为a=a^b^a=b
//该交换未进行【进位】,故不会产生值溢出!
//虽然巧妙但缺点在于可读性不高,以后还是使用临时变量swap
printf("a=%d b=%d\n", a, b);
}

2. 求一个整数二进制数中1的个数

步骤如下:

  1. 整数有32个比特位
  2. 获取该数二进制的每一位
    • 对该整数按位与1获取每一位(若不明白可返回上文查看按位与相关概念)
    • 之后进行按位右移让二进制序列动起来
  3. 判断是否为1
  4. 是1则计数器+1
    实现代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//求一个整数二进制数中1的个数
int main()
{
int a = 0; //整数
int b = 1;
int i = 0; //循环变量
int count = 0;
scanf("%d", &a); //7
for (i = 0; i < 32; i++)
{
int c = a & b;
a = a >> 1;
if ( c== 1)
{
count++;
}
}
printf("%d\n", count); //3
return 0;
}

0004463a46c894c3a4925d432d08bf80_MD5

感谢阅读!创作不易,如果觉得其中有能帮助到您,还请关注我,一起进步。
🚀Live long and Prosper!🖖🏼