今天我们来谈谈按位取反这件事。
简单来说,按位取反就是先将一个数写成其二进制表达形式,然后1变0,0变1
。下面就让我们展开深入地讨论吧!
文章目录
- 一、预备知识:
- 1. 原码:
- 定义:
- 优缺点:
- 2. 补码:
- 定义:
- 优缺点:
- 二、按位取反:
- 三、拓展应用:
- 1. 巧求相反数:(不用`-`)
- 2. 代替减法(不用`-`实现减法)
- 3. 代替加法(不用`+`实现加法)
一、预备知识:
1. 原码:
定义:
✨原码是一种用二进制表示有符号整数的编码方式。其中,最高位表示符号位
,0为正
,1为负
。其余位是数值位
,表示数值的绝对值。
- 举个“栗子”来说:
- 1的原码是:001 ;
- -1的原码是:101 ;
✨我们可以看出,互为相反数的两个数的原码,除了第一位的符号位不一样以外,其余位都相同。(那么,可能有聪明的小伙伴们就想到了0,是不是0也有两种表达方式呢?果然聪明!在原码中,0
和-0
的表示是不一样的。)
优缺点:
- ❤️优点:简单直观,易于理解和计算。
- 💔缺点:加减法运算复杂,需要考虑符号位的处理。-0多占了一个数的表达位置,导致原码也可表示的总数减少了1个。
✨那可能又有小伙伴要问了,一个数很多吗?哈哈,一个-0
单单看起来不多,但是一群-0
不就多了吗?(这就告诉了我们"人多力量大"的道理,一个人可以走得很快,但一群人才能走得更远)。另外,-0
的存在还会带来一些其他的问题:比如1️⃣(0)+(-0)=?2️⃣ 0和-0哪个大?
😢怎么一个原码事这么多?烦呐!下面就让我们来看看更为合适的编码方式–补码。
2. 补码:
❤️计算机中存储数据时,通常采用补码编码方式。
定义:
✨补码也是一种用于表示有符号整数的编码方式。其中,最高位(最左侧位)是符号位
,0表示正数,1表示负数
。其余位是数值位
。
- 对于正数,补码就是其二进制原码本身。(看来不讲原码还不能开补码),如:数值+3的补码就是0011。
- 对于负数,补码是其对应正数的原码的各位取反,末位加一,符号位取1。
如:-3的补码可以通过三步得到:(因为+3的原码是0011)
1️⃣各位取反:1100
2️⃣末位加一:1101
3️⃣符号位取1:1101
优缺点:
-
❤️优点:
1️⃣加减法运算可以直接按位运算,无需特殊处理符号位。(相对原码来说更加方便)
2️⃣解决了原码中存在的正零和负零的问题。(-0的位置让给了最小的数)
3️⃣表示范围更加均衡。(补码的最小值的绝对值比最大值的绝对值多1) -
💔缺点:
1️⃣加减法时可能溢出。
2️⃣对称不明显。(看吧,上帝为你打开一扇窗的同时,也会为你关闭一扇门)
🐒当然,补码的知识宝库偌大无比,我现在只不过是揭开了其冰山一角,但对我们解决一般的问题来说,足够了。
二、按位取反:
✨将一个数的二进制表达的每一位
取反,即0变为1
,1变为0
。取反符号用 "~" 表示
。(从最低位(最右侧位)开始,逐位进行取反操作。)
例如,对于一个8位的二进制数10101010,按位取反后的结果是01010101。(注意,这是补码的取反哦)
下面来看几个具体的代码:
1️⃣1的按位取反:
#include<stdio.h>int main(void)
{int a = 0b1;printf("%d", ~a);return 0;
}
😄猜猜它的输出是多少? 输出0是不是?哈哈哈,是0你就上当了!
咦❓为什么❓为什么不是0❓ 1->0 没有问题啊❗️到底是哪里出错了❓会是哪里错了呢❓
阁下莫慌,我们慢慢来解释。
- 首先,a是一个
int
类型,一般是4个字节(Byte
),而一个字节是8位(8个bit
),所以总共是32位。0b1
是1的二进制表达,具体可以写为:00000000 00000000 00000000 00000001
. - 下面我们对它进行取反操作,得:
11111111 11111111 11111111 11111110
,但它是一个补码,我们以%d
的形式输出就要得到它的十进制表达(可以理解为真值),那么问题来了,补码怎么转换成真值呢?考虑到具体的定义过于枯燥乏味,所以我直接告诉你转换的方法:可以先将补码转换为机器数,而计算机中的机器数一般用原码来表示,所以问题就转化为了补码->原码。 - 将补码的符号位不变,其余各位取反,末位加一。(这好像和原码转补码有点类似啊!)也就是:
10000000 00000000 00000000 00000001+1---> 10000000 00000000 00000000 00000010
,再转换成十进制整数就是-2。
2️⃣0的按位取反:(%d的形式输出)
#include<stdio.h>int main(void)
{printf("%d", ~0);return 0;
}
2️⃣0的按位取反:(%u的形式输出)
#include<stdio.h>int main(void)
{printf("%u\n", ~0);printf("%lld", ((long long)1 << 32) - 1);return 0;
}
咦❓为啥同样是0,但取反后的输出结果不一样呢?
%d
:有符号十进制整数的输出 (signed int):-2 ^ 31 -- 2 ^ 31 - 1
%u
:无符号十进制整数的输出 (unsigned int) :0 -- 2 ^ 32 - 1
这个有无符号具体就体现在是否有符号位,有符号位就少一位数值位,没有符号位就都是数值位。
- 0的补码:
00000000 00000000 00000000 00000000
- ~0的补码:
11111111 11111111 11111111 11111111
✨对于有符号整数来说,有正有负,且第一位是符号位。~0第一位是1,所以要转换成原码再输出:
即10000000 00000000 00000000 00000001
,其值为-1;
✨对于无符号整数来说,没有符号位,32个位都是数值位,所以可以直接计算。因此,两者输出不同。
三、拓展应用:
1. 巧求相反数:(不用-
)
✨相反数相必大家都知道,1的相反数是-1,0的相反数是0,简单来说一个数的相反数就是再其前面加个-
那么,不用这种方法还可以求相反数吗?
哈哈😄,其实这个问题我们上面已经说过了,直接利用补码的定义,求一个数的相反数,就是各位取反,末位加一。即-x=~x+1
#include<stdio.h>
int main(void)
{int x = -18;printf("%d的相反数是%d\n",x, ~x + 1);int y = 18;printf("%d的相反数是%d",y, ~y + 1);return 0;
}
(当然了,要注意数据的溢出问题。)
2. 代替减法(不用-
实现减法)
✨现给定int类型的两个正数,不用-如何实现减法操作?
根据减法定义,减去一个数就等于加上这个数的相反数;
所以a - b = a + (-b) = a + (~b + 1)
;
#include<stdio.h>
int main(void)
{int a=1,b=2;printf("%d-%d=%d\n",a, b,a+~b+1);return 0;
}
3. 代替加法(不用+
实现加法)
✨给定两个int类型的正数,不用+实现加法。根据加法定义,加上一个数等于减去一个数的相反数。即:a + b = a - (-b) = a - (~b +1)=a - ~b - 1
;
#include<stdio.h>
int main(void)
{int a=1,b=2;printf("%d+%d=%d\n",a, b,a-(~b + 1));return 0;
}
好了,今天的讲解就到这里了,相信你也是收获满满吧!
终于肝完了,好累!!!