进制转换&源码、反码、补码&操作符

引子

本文章为零碎知识点总结。

1、进制转换

我们听到的2进制、8进制、10进制、16进制,其实是数值的不同表现形式。

例如,用不同进制形式表示10进制下的15:

15的2进制:1111
15的8进制:17
15的10进制:15
15的16进制:F

//16进制的数值之前写:0x
//8进制的数值之前写:0

1.1、10进制

我们应该是非常熟悉10进制数了。那我们看到的10进制数,为什么是这个数呢?

原来,10进制中的每一位数都有它的 权重 。个位、十位、百位……依次对应的权重是10^0、10^1、10^2…… 权重值乘以位,再相加,得到10进制数 

如下表。以123为例

百位十位个位
10进制的位123
权重10^210^110^0
权重值100101
求值1*100+2*10+3*1=123

1.2、2进制

二进制的特点:

1、2进制中 满2进1 

2、2进制的数字每一位都是 0~1 的数字组成。

1.2.1、2进制转10进制

2进制与10进制是类似的,只不过二进制的每一位权重,变成了2^0、2^1、2^2……

以2进制的1101转化为10进制为例,如下表。

2进制的位1101
权重2^32^22^12^0
权重值8421
求值1*8+1*4+0*2+1*1=13

1.2.2、10进制转2进制

2进制转10进制的思路是:将十进制数 / 2,得到的余数(1或0)记录下,取出商,进行重复操作;直至商为0。

以10进制的125为例。

10进制的125转换的2进制:1011111。

1.2.3、2进制转8进制

8进制中的数,包括的数字是0~7。进制转换要做的事,就是如何用2进制的数,表示每一位8进制的数。

不难发现,8进制的“7”,表示成2进制是:111。所以我们 用三位2进制数,表示一位8进制数,刚好满足要求 

例如,2进制的01101011,换成8进制。

将2进制数,从右往左,每次取三位,计算方法同2进制转换为10进制,得出每一位数。如果剩下的2进制数位数不够,则按剩下的计算。

最后在左边加上0 。0开头的数字,会被当作8进制。所以最终结果是:0153。

1.2.4、2进制转16进制

2进制转16进制,又与2进制转8进制类似。

16进制中的数,包括的是0~9、a~f。

“ f ”相当于“ 15 ”,而“ 15 ”用2进制数表示为:1111。所以我们用四位2进制数,表示一位16进制数,刚好也满足要求。

还是上述2进制的01101011,我们来把它换成16进制数。

将2进制数,从右往左,每次取四位,计算方法同2进制转换为10进制,得出每一位数。如果剩下的2进制数位数不够,则按剩下的计算。

最后在左边加上0x 。0x开头的数字,会被当作16进制。所以最终结果是:0x6b。

以上就是一些常见的进制转换。如果想实现逆转换,那你可以倒过来考虑。

2、原码、反码、补码

整数的2进制表示方法有三种,即 原码、反码、补码 。

有符号整数的三种表示方法均有符号位和数值位两部分,2进制序列中,最高的1位是被当作 符号位 ,剩余的数都是 数值位 。符号位 0为正,1为负

我们看向整数类型int,int 在内存中占有4个字节,也就是32个比特位。以 int a = -10 为例。既然是负十,原码中最高位就是1,而(10进制)10 ---> (2进制)1010,所以中间27位全为0;反码就是除符号位外的所有位上的数的1变0,0变1;补码就是反码+1;补码取反,后加1,也得到原码。

//以下仅为文字说明

int a = -10

原码:10000000000000000000000000001010

反码:11111111111111111111111111110101

补码:11111111111111111111111111110110

补码取反:10000000000000000000000000001001
+1:10000000000000000000000000001010   //刚好是源码

而 int a = 10 ,原码、反码、补码,都是:00000000000000000000000000001010。

所以

//以下仅为文字说明

正整数的原、反、补码都相同。
负整数的三种表示方法各不相同。

原码:直接将数值按照正负数的形式翻译成2进制得到的就是原码。
反码:将源码的符号位不变,其他位依次按位取反就可以得到反码。
补码:反码+1就得到补码。

补充:补码取反,+1后也可以得到原码。

原码、反码、补码关系简图如下

对于 整型 来说: 数据存放内存中其实存放的是补码 。运算用的也是补码。而输出在屏幕上看到的数字,则是用原码翻译而来的。

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时加法和减法也可以统一处理(CPU只有加法器)。此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

例如,我们要进行 1 - 1 的操作,可以看作 1 + (-1) 。如果直接用原码进行计算,会导致错误;而利用补码计算,再将结果转换为原码,则得到正确答案。

//以下文字仅为说明

(2进制)1的原、反、补码:00000000000000000000000000000001

(2进制)-1的原码:10000000000000000000000000000001
(2进制)-1的反码:11111111111111111111111111111110
(2进制)-1的补码:11111111111111111111111111111111

//原码直接相加
结果为:10000000000000000000000000000010   //明显是错误答案

//补码相加
结果为:100000000000000000000000000000000
//第33位的1丢失,剩下的为0的原、反、补码:
00000000000000000000000000000000

3、移位操作符

//以下文字仅为说明

<<:左移操作符
>>:右移操作符

注:移位操作符的操作数只能是整数。

3.1、左移操作符

移位规则: 左边抛弃、右边补0

以下为左移操作符在上面代码中的演示。

3.1、右移操作符

移位规则有两种:

1.逻辑右移:左边用0填充,右边丢弃。但是这种规则不常见

2.算数右移: 左边用原该值的符号位填充,右边丢弃

以下为算术右移在上面代码中的演示。

警告:对于移位运算符, 不要移动负数位 ,这个是标准未定义的。

4、位操作符

//以下文字仅为说明

位操作符有:
&:按位与
|:按位或
^:按位异或
~:按位取反

注:交换两操作数,结果不变。

4.1、&

&(按位与)的逻辑是将两个整型的数的 补码 进行比较计算。32位的补码, 对应的每一位上,只要有 0 ,对应这一位的结果就是 0 ;全是 1 则为 1 。

辅助记忆:与,联合体,不管有多少真,只要有一个是假的,整个联合体都得崩塌。

例如 5 & (-3)

//以下文字仅为说明

(2进制)5的补码:00000000000000000000000000000101
(2进制)(-3)的补码:11111111111111111111111111111101

5 & (-3)的补码:00000000000000000000000000000101

结果显然为 5 。

上代码。

4.2、|

|(按位或)的逻辑是将两个整型的数的 补码 进行比较计算。 对应的每一位上,只要有 1 ,对应这一位的结果就是 1 ;全是 0 则为 0 。

还是 5 | (-3)

//以下仅作说明

(2进制)5的补码:00000000000000000000000000000101
(2进制)(-3)的补码:11111111111111111111111111111101

5 | (-3)的补码:11111111111111111111111111111101
5 | (-3)的原码:10000000000000000000000000000011

结果显然为 -3 。

上代码

4.3、^

^(按位异或)的逻辑是将两个整型的数的 补码 进行比较计算。 对应的每一位上,相同为0,不同为1。

5 ^ (-3)

//说明

(2进制)5的补码:00000000000000000000000000000101
(2进制)(-3)的补码:11111111111111111111111111111101

5 ^ (-3)的补码:11111111111111111111111111111000
5 ^ (-3)的原码:10000000000000000000000000001000

结果显然为 -8 。

4.4、~

~(按位取反)的逻辑是对单个数进行操作。 对应的每一位上,1换成0,0换成1。

//说明,下同。若有特殊情况,将特别标出。

(2进制)0的补码:00000000000000000000000000000000

~0的补码:11111111111111111111111111111111

显然为 -1 。

下面是一些练习。

5、练习

5.1、不能创建临时变量(第三个变量),实现两个整数的交换

方法一:定义一个新变量 tmp ,将 tmp 赋上 a 值, a 赋上 b 值, b 赋上 tmp 值。但这种方法显然是不允许的。

方法二:将 a + b 赋值给 a (设此时a为a1,a1=a+b),再将 a(a1) - b 赋值给 b(此时b=a),最后将 a(a1) - b(a)赋值给 a(此时a=b)。完成。

但是,当 a 与 b 不是很大,而 a + b 很大时,该方法就会出现错误。

方法三:利用按位异或(^)。

通过以上对按位异或知识的讲解,不难得出:

a ^ a = 0
a ^ 0 = a

其中,a 为任意整型数。

可以先让 a = a ^ b ,然后让 b = a ^ b(其实是a ^ b ^ b),完成 a 值传递给 b ;再让 a = b ^ a(其实是a ^ a ^ b),完成 b 值传递给 a 。以避免 a + b 很大时出现错误的情况。

5.2、求一个存储在内存中的2进制中1的个数

方法一:

我们知道,正整数的补码与原码相同。而10进制转换为2进制,是通过不断除以2得到余数并记录下得到的,而余数只有0和1两种情况。所以,我们可以对给出的数,不断做“ %2、/2 ”的操作。

但是,这份代码有缺陷,针对负整数就没辙了。此时可以在 int num 前加 unsigned ,这样一来,在计算过程中,所有整数都是用补码进行计算。

还有没有其他方法?

方法二:将该数的补码依次右移(32次),并与1的补码进行按位与操作,若结果为1,记录一次。

或者你也可以将1的补码依次右移,并与该数的补码进行按位与操作。

但是,在这一份代码中,我们必须要循环32次。还有没有更好的办法?

方法三:

利用 n = n & (n - 1)

以15为例,进行讲解。

令 n = 15

(2进制)n = 1111
(2进制)(n-1) = 1110
(2进制)n & (n-1) = 1110
将 1110 传给 n,并记录一次

(2进制)n = 1110
(2进制)(n-1) = 1101
(2进制)n & (n-1) = 1100
将 1100 传给 n,并记录一次

(2进制)n = 1100
(2进制)(n-1) = 1011
(2进制)n & (n-1) = 1000
将 1000 传给 n,并记录一次

(2进制)n = 1000
(2进制)(n-1) = 0111
(2进制)n & (n-1) = 0000
将 0000 传给 n,并记录一次

此时 n = 0,结束

我们发现,每进行一次 n = n & (n - 1) ,该数对应的补码最右边的1恰好变成了0。

5.3、判断是否为2的次方数

本题思路很简单,只需知道,2的次方数,作为正整数,其补码中只有一个0。所以本题解法可以利用 5.2 题:在方法一、方法二中,只需判断 count 是否为 1;在方法三中,只需判断 n & (n - 1) 是否为 0 。

5.4、将13的2进制序列的第5位修改为1,然后再改回0

13的2进制序列:00000000000000000000000000001101

使第五位0变1,其余不变。则设计一个数,其二进制序列只有第五位为1,其余全0,再与13的2进制序列按位或。可将“ 1 ”左移4位。

此时:00000000000000000000000000011101 (29)

又要使第五位1变0,其余不变。则设计一个数,其二进制序列只有第五位为0,其余全1,再与29的2进制序列按位与。可将“ 1 ”左移4位,再取反。

6、单目操作符

有:!、++、--、&、*、+、-、~、sizeof

7、逗号表达式

逗号表达式, 用逗号隔开的多个表达式,从左往右依次执行,结果为最后一个表达式的结果。

a = get_val();
count_val(a);
while (a > 0)
{
    //业务处理
    //...
    a = get_val();
    count_val(a);
}

如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
    //业务处理
}

8、下标访问[ ]

[ ] 下标引⽤操作符
操作数:⼀个数组名 + ⼀个索引值(下标)
int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
[ ]的两个操作数是arr和9。

9、函数调用操作符

接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
    printf("hehe\n");
}
void test2(const char *str)
{
    printf("%s\n", str);
}
int main()
{
    test1(); //这⾥的()就是作为函数调⽤操作符。
    test2("hello bit.");//这⾥的()就是函数调⽤操作符。

    return 0;
}

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值