零基础学Java|第五篇:进制转换与位运算、原码反码补码

你好,欢迎继续 Java 零基础学习!

在上一篇中,我们学习了变量、数据类型和运算符,知道了计算机内部所有数据最终都是以二进制 形式存储的。今天我们就深入这个"底层世界",彻底搞懂 进制转换 (二进制、八进制、十六进制)、位运算 (直接操作二进制位的运算符)以及 原码、反码、补码(整数在计算机中的编码方式)。掌握了这些,你就能理解很多底层原理,写出更高效的代码。


1. 为什么需要不同的进制?

我们日常生活中最熟悉的是十进制 (0-9,逢十进一),但计算机只认识二进制 (0 和 1,逢二进一)。二进制虽然简单,但对人来说读写太长,容易出错。于是引入了八进制 (0-7,逢八进一)和十六进制(0-9 和 A-F,逢十六进一)作为二进制的"缩写",方便程序员查看和书写。

例如,二进制 1010 1100 写成十六进制是 AC,简洁多了。


2. 进制转换

2.1 二进制、八进制、十六进制 → 十进制

方法 :按权展开求和。

所谓"权",就是每一位的"单位",从右向左依次是 基数^0、基数^1、基数^2......

示例 :二进制 1011 转十进制

  • 从右向左:第0位:1×2⁰ = 1
  • 第1位:1×2¹ = 2
  • 第2位:0×2² = 0
  • 第3位:1×2³ = 8
  • 总和:1+2+0+8 = 11

示例 :十六进制 2F 转十进制

  • 第0位:F(即15)×16⁰ = 15
  • 第1位:2×16¹ = 32
  • 总和:15+32 = 47

2.2 十进制 → 二进制、八进制、十六进制

方法 :除基取余法(整数部分)。

不断除以目标进制,取余数,直到商为 0,将余数从下往上排列。

示例:十进制 25 转二进制

  • 25 ÷ 2 = 12 余 1
  • 12 ÷ 2 = 6 余 0
  • 6 ÷ 2 = 3 余 0
  • 3 ÷ 2 = 1 余 1
  • 1 ÷ 2 = 0 余 1
    从下往上:11001,即 25₁₀ = 11001₂

示例:十进制 47 转十六进制

  • 47 ÷ 16 = 2 余 15(即 F)
  • 2 ÷ 16 = 0 余 2
    从下往上:2F,即 47₁₀ = 2F₁₆

2.3 二进制 ↔ 八进制、十六进制

因为 2³ = 8,2⁴ = 16,所以可以每3位二进制对应1位八进制,每4位对应1位十六进制。

二进制 → 八进制 :从右向左每3位一组,不足补0,每组转成0-7的数字。
示例 :二进制 1011010 分组:1 011 010(从右向左:010、011、1→001),即 001 011 010 → 1 3 2(八进制 132)。

二进制 → 十六进制 :从右向左每4位一组,不足补0,每组转成0-9或A-F。
示例 :同上 1011010 分组:101 1010 → 0101 1010 → 5 A(十六进制 5A)。

反过来,八进制或十六进制每一位转成对应的3位或4位二进制即可。


3. 原码、反码、补码

在计算机中,整数是以补码 形式存储的。为什么要有这些编码?因为它们解决了 正负数表示减法运算统一 的问题。

3.1 机器数与真值

  • 机器数:一个数在计算机中的二进制表示,通常最高位为符号位(0 正,1 负)。
  • 真值:带符号的实际数值。

例如,用 8 位表示:

+5 的机器数:0000 0101,真值 +5。

-5 的机器数:不能直接用 1000 0101 表示,因为这种"原码"会导致运算复杂。

对于 -5 这样的负数,其补码表示是通过以下步骤得到的(以 8 位为例): 1. 先写出 5 的二进制原码:0000 0101。 2. 求反码:将每一位取反(0 变 1,1 变 0),得到 1111 1010。 3. 反码加 1,得到补码:1111 1011

因此,-5 在计算机中的机器数(8 位)就是 1111 1011

之所以不用原码 1000 0101,是因为原码会导致加减运算复杂(需要判断符号位、处理正负),而补码可以将减法统一为加法(例如 5 - 5 可转为 5 + (-5),用补码计算正好得到 0),并且 0 的表示唯一,硬件实现简单。这就是现代计算机采用补码的原因。

3.2 原码

  • 最高位为符号位,其余位表示绝对值。
  • 例如 8 位原码:
    • +5:0000 0101
    • -5:1000 0101
  • 缺点 :0 有两种表示(0000 00001000 0000),且加减运算复杂(需判断符号)。

3.3 反码

  • 正数的反码 = 原码。
  • 负数的反码 = 符号位不变,其余位按位取反。
  • 例如 8 位反码:
    • +5:0000 0101
    • -5:1111 1010
  • 缺点 :0 仍有两种表示(0000 00001111 1111),且加法可能需循环进位。

3.4 补码

  • 正数的补码 = 原码。
  • 负数的补码 = 反码 + 1。
  • 例如 8 位补码:
    • +5:0000 0101
    • -5:1111 1011
  • 0 的补码只有一种:0000 0000
  • 利用补码,可以将减法转化为加法,统一了加减运算。

为什么补码能统一加减?

因为对任意整数 a,其负数 -a 的补码等于 2ⁿ - a(n 为位数)。这样 a + (-a) = 2ⁿ,在 n 位范围内溢出后正好为 0。所以计算机只需加法器即可完成减法。

3.5 补码的取值范围(以 8 位为例)

  • 最大正数:0111 1111 = 127
  • 最小负数:1000 0000 = -128(因为 -128 的补码是 1000 0000,没有对应的原码和反码)
  • 范围:-128 ~ 127

3.6 已知补码求真值

  • 若符号位为 0,直接转十进制。
  • 若符号位为 1,则先减 1 得反码,再取反得原码(符号位不变),或者直接用公式:真值 = -(2ⁿ - 数值部分的值)。
    例如补码 1111 1011,数值部分(不含符号位)为 123(即 1111011₂),则真值 = -(256 - 123) = -133?不对,256-123=133,取负得 -133,但应该是 -5。错误。正确公式:真值 = -(2ⁿ - 补码表示的整数(当作无符号))。补码 1111 1011 当作无符号整数是 251,2⁸=256,256-251=5,所以真值 -5。或者按步骤:减1得反码 1111 1010,取反得原码 1000 0101,即 -5。

4. 位运算

位运算是直接对整数的二进制位进行操作的运算符,效率极高。Java 中位运算符适用于整数类型(byte, short, int, long, char)。

4.1 按位与 &

  • 规则:两位都为 1 时结果为 1,否则为 0。
  • 用途:清零指定位、取指定位。

示例:

java 复制代码
int a = 5;      // 二进制 0101
int b = 3;      // 二进制 0011
int c = a & b;  // 0001 = 1

4.2 按位或 |

  • 规则:两位只要有一个为 1 结果为 1。
  • 用途:置位(将某些位设为 1)。

示例:

java 复制代码
int a = 5;      // 0101
int b = 3;      // 0011
int c = a | b;  // 0111 = 7

4.3 按位异或 ^

  • 规则:两位相同为 0,不同为 1。
  • 性质:a ^ a = 0;a ^ 0 = a;异或可用于交换变量值(不用临时变量)。

示例:

java 复制代码
int a = 5;      // 0101
int b = 3;      // 0011
int c = a ^ b;  // 0110 = 6

4.4 按位取反 ~

  • 规则:0 变 1,1 变 0(包括符号位)。
  • 注意:~a = -a - 1(因为补码特性)。

示例:

java 复制代码
int a = 5;      // 00000000 00000000 00000000 00000101
int b = ~a;     // 11111111 11111111 11111111 11111010 = -6

4.5 左移 <<

  • 规则:将二进制位全部左移若干位,高位丢弃,低位补 0。
  • 效果:每左移一位,相当于乘以 2(在不溢出的情况下)。

示例:

java 复制代码
int a = 5;      // 0101
int b = a << 1; // 1010 = 10
int c = a << 2; // 10100 = 20

4.6 右移 >>(带符号右移)

  • 规则:将二进制位全部右移若干位,低位丢弃,高位补符号位(正数补0,负数补1)。
  • 效果:每右移一位,相当于除以 2 并向下取整(向负无穷方向)。

示例:

java 复制代码
int a = 10;     // 1010
int b = a >> 1; // 0101 = 5
int c = a >> 2; // 0010 = 2

int neg = -10;  // 11111111 11111111 11111111 11110110 (补码)
int d = neg >> 1; // 11111111 11111111 11111111 11111011 = -5

4.7 无符号右移 >>>

  • 规则:右移后,高位始终补 0,不管原来是正是负。
  • 用途:对二进制位进行逻辑右移,不考虑符号。

示例:

java 复制代码
int neg = -10;
int e = neg >>> 1;  // 01111111 11111111 11111111 11111011 = 2147483643 (很大的正数)

5. 位运算的实际应用

5.1 判断奇偶性

(n & 1) == 1 判断奇数,比 n % 2 == 1 效率高(但现代编译器会优化,差别不大)。

java 复制代码
int n = 5;
if ((n & 1) == 1) {
    System.out.println("奇数");
}

5.2 交换两个数(不用临时变量)

java 复制代码
int a = 3, b = 5;
a = a ^ b;
b = a ^ b;  // b = (a ^ b) ^ b = a
a = a ^ b;  // a = (a ^ b) ^ a = b

5.3 求 2 的幂次

左移 1 << n 得到 2ⁿ。

java 复制代码
int power = 1 << 4;  // 16

5.4 掩码操作

例如,取一个 int 的低 8 位:value & 0xFF

5.5 权限管理

用二进制位表示权限(如读、写、执行),通过位运算设置和检查。

java 复制代码
// 权限定义
int READ = 1 << 0;   // 001
int WRITE = 1 << 1;  // 010
int EXEC = 1 << 2;   // 100

// 赋予权限:读+写
int perm = READ | WRITE;  // 011

// 检查是否有读权限
if ((perm & READ) != 0) { ... }

6. 综合示例:进制与位运算

下面编写一个程序,演示进制转换、补码表示和位运算。

java 复制代码
/**
 * 演示进制转换、原码反码补码概念、位运算
 */
public class BitDemo {

    public static void main(String[] args) {
        // 进制转换:十进制转二进制、十六进制
        int num = 47;
        System.out.println(num + " 的二进制:" + Integer.toBinaryString(num));
        System.out.println(num + " 的八进制:" + Integer.toOctalString(num));
        System.out.println(num + " 的十六进制:" + Integer.toHexString(num));

        // 补码表示(以 byte 为例,-5)
        byte b = -5;
        System.out.println("-5 的二进制补码:" + Integer.toBinaryString(b & 0xFF)); // 只取低8位

        // 位运算示例
        int a = 5;  // 0101
        int c = 3;  // 0011
        System.out.println("a & c = " + (a & c));   // 1
        System.out.println("a | c = " + (a | c));   // 7
        System.out.println("a ^ c = " + (a ^ c));   // 6
        System.out.println("~a = " + (~a));         // -6

        // 移位
        System.out.println("a << 1 = " + (a << 1)); // 10
        System.out.println("a >> 1 = " + (a >> 1)); // 2
        System.out.println("-5 >> 1 = " + (-5 >> 1)); // -3
        System.out.println("-5 >>> 1 = " + (-5 >>> 1)); // 2147483645

        // 应用:判断奇偶
        int n = 7;
        System.out.println(n + " 是奇数? " + ((n & 1) == 1));

        // 应用:交换变量
        int x = 10, y = 20;
        System.out.println("交换前:x=" + x + ", y=" + y);
        x = x ^ y;
        y = x ^ y;
        x = x ^ y;
        System.out.println("交换后:x=" + x + ", y=" + y);
    }
}

运行结果:

7. 常见问题与提示

  • 补码的 -128 没有原码和反码:8 位时,-128 的补码是 1000 0000,其原码和反码不存在(因为无法用 8 位表示 +128)。
  • 位运算只适用于整数:不能用于浮点数、布尔值。
  • 移位运算的位数超过类型位数时,会取模:例如对 int 左移 33 位相当于左移 33 % 32 = 1 位。
  • 无符号右移 >>> 对于正数效果同 >>,对于负数会产生大正数

8. 总结与下期预告

今天我们深入了计算机的底层世界:

  • 进制转换:不同进制间的转换方法,特别是二进制与十六进制的便捷转换。
  • 原码、反码、补码:理解了为什么整数用补码存储,以及如何转换。
  • 位运算:7 种位运算符及其应用场景,如掩码、权限、高效计算。

这些知识虽然抽象,但它们是理解计算机工作原理的基础。下一篇文章我们将学习 流程控制语句(if、switch、循环),让程序拥有判断和重复执行的能力,真正写出有逻辑的程序。

动手实践

  1. 练习十进制 100、255、-128 转二进制、八进制、十六进制。
  2. 写出 +7 和 -7 的 8 位原码、反码、补码。
  3. 用位运算实现一个程序,打印一个整数的二进制形式,并统计其中 1 的个数。

如果你在学习中遇到困惑,欢迎留言交流。我们下期见! 🚀

相关推荐
开心就好20252 小时前
免 Xcode 的 iOS 开发新选择?聊聊一款更轻量的 iOS 开发 IDE kxapp 快蝎
后端·ios
Java编程爱好者2 小时前
为什么国内大厂纷纷”弃坑”MySQL,转投PostgreSQL阵营?
后端
神奇小汤圆3 小时前
金三银四Java面试题及答案汇总(2026持续更新)
后端
颜酱3 小时前
理解二叉树最近公共祖先(LCA):从基础到变种解析
javascript·后端·算法
神奇小汤圆3 小时前
加了 limit 1,查询竟然变慢了?
后端
Java水解3 小时前
SpringBoot3全栈开发实战:从入门到精通的完整指南
spring boot·后端
Java水解3 小时前
Java 中间件:Dubbo 服务降级(Mock 机制)
java·后端
千寻girling3 小时前
一份不可多得的 《 Python 》语言教程
人工智能·后端·python
南风9993 小时前
Claude code安装使用保姆级教程
后端