原码、反码、补码底层原理、类型转换原理、进制基础——计算机存储数据值原理(Java数据类型理解

目录

#有只想看原反补码的底层原理的可以直接点击目录移步观看(可能有搜过来只想看这个的大学生,可以直接跳到“什么是补码”,也可以从原码开始看起)

目录

一、进制基础

二、原码、反码、补码

1.什么是原码

2.什么是反码

3.什么是补码

让我们再更深一点点

三、类型转换

1.隐式类型转换

2.显式类型转换(强制转换)

特殊情况

四、补码与类型转换

总结


 一、进制基础

进制就是进位制,是人们规定的一种进位方法,二进制逢2进1,八进制是逢8进1,十进制逢10进1,十六进制逢16进1。

不同进制形式:

二进制:0b或0B开头,由0和1组成
八进制:0开头,由0、1...6、7组成
十进制:常见整数,由0、1...8、9组成
十六进制:0x或0X开头,由0、1...8、9、a、b、c、d、e、f组成,大小写不区分

public class Test{
    public static void main(String[] args) {
        byte b1 = 0b01100010; // 二进制
        byte b2 = 98; // 十进制
        byte b3 = 0142; // 八进制
        byte b4 = 0x62; // 十六进制
        // 打印出来结果全是98,为什么?
        System.out.println(b1);
        System.out.println(b2);
        System.out.println(b3);
        System.out.println(b4);
    }
}

上述案例中,在计算机底层存储时,都是按二进制存储的,其值按照十进制表示,都是98。

注意:不论什么类型的数据值,在计算机的底层存储时,统一按照二进制形式存储! 


二、原码、反码、补码

学习这个知识点之前,我们先来看一个题目:写出10的二进制形式

0b 0 0(23个) 0000 1010

10对应的类型为int,在计算机底层占4字节,需要32个比特位表示 其中最高位为符号位,0表示正数,1表示负数,剩下的31位,其中23位都为0,低8位为0000 1010 = 8 + 2 = 10 连到一起,结果为正整数10

思考:-10的二进制形式如何表示?

如果要表示负整数的二进制形式,则必须学习原码、反码、补码。

相信很多小伙伴都知道原码反码补码的计算方式,不知道也没关系,这就来了`(*∩_∩*)′

原码:十进制数据的二进制表现形式,最左边符号位,0为正,1为负

10的原码:  0000 1010
-10的原码: 1000 1010

反码正数的反码是其本身负数的反码是符号位保持不变其余位取反

10的反码:跟10的原码相同  0000 1010
-10的反码: 拿到-10的原码,保留符号位其他位取反  1111 0101

补码正数的补码是其本身负数的补码是在其反码的基础上+1

10的补码: 跟10的原码相同     0000 1010
-10的补码:拿到-10的反码,    1111 0101
                    在反码基础上加1    1111 0110

那具体为啥要有反码、补码,反码为什么是原码取反;为啥要有补码,补码为什么是+1不是加3  (๑•ี_เ•ี๑)?

我先告诉你,数据在计算机底层进行存储或运算,以补码方式进行,而接下来的内容会告诉你为什么。


1.什么是原码

在计算机当中,一个0或者一个1,我们称之为bit,中文名叫做比特位,8个bit叫做一个字节,也就是计算机当中最小的储存单元。

可是还有负数怎么办,那没办法,计算机只能识别0和1,所以我们将一个bit位用来表示正负,0为正,1为负。

那么这样,在原码当中,一个字节最大值即为 0111 1111 ,也就是 +127,最小值也就是 1111 1111,就是-127(并不是 1000 0000,这样是 -0,也就还是0,是比负数大的)

就这样,利用原码对正数计算并不会出问题,0000 0001 (1)加上 0010 0001 (33)等于 0010 0010 (34)
可是负数的话,就有点问题了,1000 0000是 -0,也就是0,0+1=1,可是在原码这里变成了 1000 0001,变成了 -1;再继续加1呢,-1加1是0,在原码这里,1000 0001 加1变成了 1000 0010,实际上变成了-2

原码的弊端就体现出来了,利用原码对负数计算,结果会出错,实际结果与预期结果相反。

其实是因为符号位的限制,只要符号位是负的,加1就变成了减1,所以结果才会一直是相反的


2.什么是反码

为了解决原码不能计算负数的问题,反码出现了

正数不变,负数符号位保持不变,其余位取反,1变0,0变1

举个栗子(๑•̀ㅁ•́ฅ)

-10的反码是 1111 0101,加1,然后变成了 1111 0110,是 -118 ? 诶,可不要忘了变回去哦,1000 1001,这下变成 -9了,完美,这个问题解决了🤔

吗?既然有补码就说明还是有一丢丢小问题的

当你用 -1的反码 1111 1110 加1时候,会变成 1111 1111,是 -0的反码,-0也是0,对的

再加1呢,变成了0000 0000(超范围了,不过我们现在是一个字节,所以不管了),这样变成了0的反码(符号位为0,正数,反码就是原码),怎么还是0,嗯🤯?

0这个祸害,导致只要是跨0的反码运算,都被偷走了一位(诡计多端的0🤗)

十进制
数字
原码反码
20000 00100000 0010
10000 00010000 0001
00000 00000000 0000
-00000 00001111 1111
-11000 00011111 1110
-21000 00101111 1101

这个表格一目了然,只要是涉及到跨0,就相当于跨了两个台阶,那么聪明的你应该也因此想到了补码的用途。


3.什么是补码

如果你是直接跳到这的,那答案就是,它是 在为了解决符号位导致的负数计算错误而创造出来的反码基础上,做了改进解决了跨0少1的问题而出现的一个最终存储方案,即补码

拿表格看就十分清晰了

十进制
数字
原码反码补码
20000 00100000 00100000 0010
10000 00010000 00010000 0001
00000 00000000 00000000 0000
-00000 00001111 11110000 0000
-11000 00011111 11101111 1111
-21000 00101111 11011111 1110

它相当于把 0 的两种形式给屏蔽掉了(千万要注意这里的 +1 只有负数才加哦,都加就相当于啥都没干了(;¬_¬)

总结一下:
正数的原、反、补码都是一样的,是它二进制本身,符号位为0;
负数第一位符号位为1,反码保留符号位其他位取反,补码是反码+1

补码小细节这一块:-127 的补码是 1000 0001,这样就空出了一位,也就是1000 0000,这就是-128(这个也很好理解,因为 0 原来在反码中有两种表现形式),在一个字节中,它无原码、无反码,当然这并不碍事,计算机中本来也是以补码存储,所以我们说,一个字节它的取值范围是 -128 ~ 127,这才是真正的 音乐 原因


让我们再更深一点点

基本数据类型

byte类型的10    1个字节  0000 1010

short类型的10   2个字节  0000 0000 0000 1010

int类型的10       4个字节  0000 0000 0000 0000 0000 0000 0000 1010

long类型的10    8个字节 太长了就不写了(๑•̀ω•́๑)

模拟计算机底层进行运算:-10 + 10

计算机底层通过补码进行运算
先获取-10补码:1 1(23) 1111 0110     +     再获取 10补码:0 0(23) 0000 1010     =     结果: 1【0 0(23) 0000 0000】
结果分析:两个int类型数据相加后结果值类型仍旧是int,其中int类型表示范围为4字节32个比特位,所以上述结果中第33位的那个1被自动抛弃,只保留低32位数值, 0 0(23) 0000 0000,即 0。 所以:-10 + 10 == 0


三、类型转换

基本数据类型表示范围大小排序: 

在变量赋值及算术运算的过程中,经常会用到数据类型转换,其分为两类:

  • 隐式类型转换
  • 显式类型转换  

1.隐式类型转换

情形1:赋值过程中,小数据类型值或变量可以直接赋值给大类型变量,类型会自动进行转换

public class Test {
    public static void main(String[] args) {    
        // int类型值 赋值给 long类型变量 
        long num = 10;
        System.out.println(num);
        // float类型值 赋值给 double类型变量 
        double price = 8.0F;
        System.out.println(price);
        char c = 'a';
        // char 赋值给 int
        int t = c;
        System.out.println(t);
        // 下面会编译报错
        //float pi = 3.14;
        //int size = 123L;
        //int length = 178.5;
   }
}

常量补充

整形数后面加'L'或'l',就表示long类型字面值常量

小数后面加'F'或'f',就表示float类型字面值常量

整形字面值,不论是二进制、八进制还是十进制、十六进制,默认都是int类型常量。

我个人建议大写L和F,因为这样明显,l 和 1 在编译器中还是比较难辨识的罒ω罒

 情形2:byte、short、char类型的数据在进行算术运算时,会先自动提升为 int,然后再进行运算

public class Test {
    public static void main(String[] args) {
        byte b = 10;
        short s = 5;
        // (byte -> int) + (short -> int)
        // int   +   int  
        // 结果为   int
        int sum = b + s;
        // 下一行编译报错,int 无法自动转换为 short进行赋值
        // short sum2 = b + s;
        System.out.println(sum);
    }
}

情形3:其他类型相互运算时,表示范围小的会自动提升为范围大的,然后再运算

public class Test {
    public static void main(String[] args) {
        byte b = 10;
        short s = 5;
        double d = 2.3;
        // (byte10->int10 - 5) * (short->int5) -> 5 * 5 = 25
        // int25   + double2.3
        // double25.0 + double2.3
        // 结果:double 27.3,必须用double变量来接收该值
        double t = (b - 5) * s + d;
        // double赋值给float,编译报错
        // float f = (b - 5) * s + d;
        System.out.println(t);
    }
}

2.显式类型转换(强制转换)

赋值过程中,大类型数据赋值给小类型变量,编译会报错,此时必须通过强制类型转换实现。

固定格式:
                 数据类型 变量名 = (目标数据类型)(数值或变量或表达式);

public class Test082_ExplicitTrans {
    public static void main(String[] args) {
        // 1.数据赋值 强制类型转换
        float f1 = (float)3.14;
        System.out.println(f1); //3.14
        // 1.数据赋值 强制类型转换
        int size = (int)123L;
        System.out.println(size); //123
        double len = 178.5;
        // 2.变量赋值 强制类型转换
        int length = (int)len;
        System.out.println(length); //178
        byte b = 10;
        short s = 5;
        double d = 2.3;
        // 3.表达式赋值 强制类型转换
        float f2 = (float)((b - 5) * s + d);
        System.out.println(f2); //27.3
   }
}

特殊情况

观察下面代码,思考为什么编译能够成功?

public class Test083_SpecialTrans {
    public static void main(String[] args) {
        // 为什么 int -> byte 成功了?
        byte b = 10;
        System.out.println(b);
        // 为什么 int -> short 成功了?
        short s = 20;
        System.out.println(s);
        // 为什么 int -> char 成功了?
        char c = 97;
        System.out.println(c);
        // 为什么 b2赋值成功,b3却失败了?
        byte b2 = 127;
        System.out.println(b2);
        //byte b3 = 128;
        //System.out.println(b3);
        // 为什么 s2赋值成功,s3却失败了?
        short s2 = -32768;
        System.out.println(s2);
        //short s3 = -32769;
        //System.out.println(s3);
        // 为什么 c2赋值成功,c3却失败了?
        char c2 = 65535;
        System.out.println("c2: " + c2);
        //char c3 = 65536;
        //System.out.println(c3);
   }
}

整形常量优化机制

    使用整形字面值常量(默认int类型)赋值给其他类型(byte、char、short)变量时,系统会先判断该字面值是否超出赋值类型的取值范围,如果没有超过范围则赋值成功,如果超出则赋值失败。

public class Test {
    public static void main(String[] args) {
        // 为什么 int -> byte 成功了?
        // 字面值常量10,在赋值给b的时候,常量优化机制会起作用:
        // 系统会先判断10是否超出byte的取值范围,如果没有,则赋值成功,如果超出(比如128),则赋值失败
        byte b = 10;
        System.out.println(b);
        // 为什么 int -> short 成功了?
        // 系统会先判断20是否超出short的取值范围,结果是没有,则赋值成功
        short s = 20;
        System.out.println(s);
        // 如果使用-32769赋值,超出short类型取值范围,则赋值失败
        // short s3 = -32769;
        // System.out.println(s3);
    }
}

注意事项:
    常量优化机制只适用于常量,如果是变量,则不可以该优化机制只针对int类型常量,对于long、float、double等常量,则不可以。

public class Test {
    public static void main(String[] args) {
        // 下面3个赋值语句都 编译失败
        byte b = 10L;
        short s = 3.14F;
        int n = 2.3;
        // 下面2个赋值语句 ok
        byte b1 = 2;
        short s2 = 3;
        // 变量赋值或表达式赋值,都编译失败
        byte b3 = s2;
        byte b4 = b1 + s2;
    }
}

四、补码与类型转换

补码作为数据类型存储的方法,我们知道其原理就可以解释类型转换了

隐式转换如 byte 类型的 10(0000 1010)转成 int 类型就是前面补零(0000 0000 0000 0000 0000 0000 0000 1010)

强制转换如 int 类型的 300(0000 0000 0000 0000 0000 0001 0010 1100)转成 byte 类型就是去掉前面的3 * 8 =24 个bit位:0000 0000 0000 0000 0000 0001 0010 1100,变成了44(0010 1100)这就是强制类型数据精度丢失或数据截断的原因(๑•̀ㅁ•́๑)✧

如 int 类型的200(0000 0000 0000 0000 0000 0000 1100 1000)变成 byte 类型,这下可不是直接变成200哦,也不是去掉符号位的 -72,要记住计算机是补码存储,所以这个时候应该转成原码(先变成反码 1100 0111,然后变成 1011 1000,所以是 -56)

其他的运算符

运算符含义运算规则
&逻辑与0为false,1为true
|逻辑或0为false,1为true
<<左移向左移动,低位补0
>>右移向右移动,高位补0或1
>>>无符号右移向右移动,高位补0

我们一个一个来

&:

public class Test {
    public static void main(String[] args) {
        int a = 200;
        int b = 10;
        System.out.println(a & b);
    }
}

  0000 0000 0000 0000 0000 0000 1100 1000(a)
&0000 0000 0000 0000 0000 0000 0000 1010(b)
——————————————————————
   0000 0000 0000 0000 0000 0000 0000 1000(&都是true才是true,也就是都是1才是1)

一个一个对比比特位

转换为十进制为8


|:

public class Test {
    public static void main(String[] args) {
        int a = 200;
        int b = 10;
        System.out.println(a | b);
    }
}

   0000 0000 0000 0000 0000 0000 1100 1000(a)
&0000 0000 0000 0000 0000 0000 0000 1010(b)
——————————————————————
   0000 0000 0000 0000 0000 0000 1100 1010(|有true就是true,有1就是1)

转换为十进制为202


<<:就是补码向左移动,低位补0

public class Test {
    public static void main(String[] args) {
        int a = 200;
        System.out.println(a << 2);//左移两位
    }
}

0000 0000 0000 0000 0000 0000 1100 1000

0000 0000 0000 0000 0000 0011 0010 0000

变成了800(左移一次就是*2,因为是二进制)


>>:就是补码向右移动,原来是正数,高位补0,负数补1

public class Test {
    public static void main(String[] args) {
        int a = 200;
        System.out.println(a >> 2);//右移两位
    }
}

0000 0000 0000 0000 0000 0000 1100 1000

0000 0000 0000 0000 0000 0000 0011 0010

变成了50(右移一次就是/2)

>>>:就是补码向右移动,不管符号直接补0


总结

文章系统讲解了计算机中的进制转换、原码/反码/补码原理及类型转换。通过补码解决负数运算问题,详细分析原码、反码、补码的转换规则及其必要性,并由此解释了Java中的隐式与显式类型转换机制、补码在类型转换中的应用原理,特别说明,byte/short/char的常量优化机制也很关键哦。
我通过代码示例演示了不同进制赋值、位运算等操作,并解释了计算机底层以补码形式存储数据的本质特性,大家看完也可以自己尝试一下🥳

之后的文章我要依次介绍运算符,流程控制与方法,大家敬请期待吧(๑`^´๑)

感谢点赞,感谢收藏,感谢关注,希望能帮到你,如果有错误可以在评论区指出。最后求关注,我会持续更新的(๑>︶<)و

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值