进制转换在C/C++/Java/Kotlin中的应用(详细版)(下)

简介: 进制转换在C/C++/Java/Kotlin中的应用(详细版)

进制转换在C/C++/Java/Kotlin中的应用(详细版)(上)+https://developer.aliyun.com/article/1489622


位运算和进制

位运算符的介绍(&,|,^,~,>>,<<)

位运算是指对二进制数的每一位进行逻辑或算术运算。位运算可以提高程序的执行效率,因为它们直接操作计算机的最小单位。在C/C++/Java/Kotlin等编程语言中,可以使用以下的位运算符:

进制位运算主要在二进制上进行,以下是常见的位运算符及其描述:

运算符 名称 描述 示例 (假设 A = 60, B = 13) 结果 (二进制表示)
& 与 (AND) 如果两个相应的二进制位都为1,则结果为1,否则为0 A & B 0000 1100
| 或 (OR) 如果两个相应的二进制位有一个为1,则结果为1,否则为0 A | B 0011 1101
^ 异或 (XOR) 如果两个相应的二进制位一个为0另一个为1,则结果为1,否则为0 A ^ B 0011 0001
~ 非 (NOT) 反转二进制位,0变为1,1变为0 ~A 1100 0011
<< 左移 将二进制数的所有位向左移指定的位数 A << 2 1111 0000
>> 右移 将二进制数的所有位向右移指定的位数 A >> 2 0000 1111
  • &:按位与(bitwise AND),对两个二进制数的每一位进行逻辑与运算,如果两个位都是1,则结果为1,否则为0。例如:
#include <stdio.h>
int main()
{
    int a = 10; // 十进制数10,二进制数1010
    int b = 12; // 十进制数12,二进制数1100
    int c = a & b; // 按位与运算,结果为8,二进制数1000
    printf("%d\n", c); // 输出8
    return 0;
}
  • |:按位或(bitwise OR),对两个二进制数的每一位进行逻辑或运算,如果两个位中有一个是1,则结果为1,否则为0。例如:
#include <stdio.h>
int main()
{
    int a = 10; // 十进制数10,二进制数1010
    int b = 12; // 十进制数12,二进制数1100
    int c = a | b; // 按位或运算,结果为14,二进制数1110
    printf("%d\n", c); // 输出14
    return 0;
}
  • ^:按位异或(bitwise XOR),对两个二进制数的每一位进行逻辑异或运算,如果两个位不同,则结果为1,否则为0。例如:
#include <stdio.h>
int main()
{
    int a = 10; // 十进制数10,二进制数1010
    int b = 12; // 十进制数12,二进制数1100
    int c = a ^ b; // 按位异或运算,结果为6,二进制数0110
    printf("%d\n", c); // 输出6
    return 0;
}
  • ~:按位取反(bitwise NOT),对一个二进制数的每一位进行逻辑非运算,如果该位是1,则结果为0,否则为1。例如:
#include <stdio.h>
int main()
{
    int a = 10; // 十进制数10,二进制数1010
    int b = ~a; // 按位取反运算,结果为-11,二进制数11111111111111111111111111110101(补码表示)
    printf("%d\n", b); // 输出-11
    return 0;
}

注意,在C/C++/Java/Kotlin等编程语言中,整型(int)通常用32位来

  • >>:右移(right shift),将一个二进制数的每一位向右移动指定的位数,左边空出的位用0填充,右边溢出的位舍弃。例如:
#include <stdio.h>
int main()
{
    int a = 10; // 十进制数10,二进制数1010
    int b = a >> 2; // 右移2位,结果为2,二进制数0010
    printf("%d\n", b); // 输出2
    return 0;
}
  • <<:左移(left shift),将一个二进制数的每一位向左移动指定的位数,右边空出的位用0填充,左边溢出的位舍弃。例如:
#include <stdio.h>
int main()
{
    int a = 10; // 十进制数10,二进制数1010
    int b = a << 2; // 左移2位,结果为40,二进制数101000
    printf("%d\n", b); // 输出40
    return 0;
}

注意:在上述示例中,A和B的值是以十进制表示的,但结果是以二进制表示的。例如,A = 60的二进制表示为0011 1100,B = 13的二进制表示为0000 1101。

进制中的位操作不仅仅包括基本的位运算符,工作中还有一些涉及到一些高级概念,如高位、低位、清除某位等。以下是这些概念的详细表格:

概念名称 描述 示例 (假设 A = 60, 即二进制为 0011 1100) 结果 (二进制表示)
高位 (High Bit) 二进制数中的左侧位。在固定长度的二进制数中,高位是最重要的位。 高位(A, 8位) 00
低位 (Low Bit) 二进制数中的右侧位。在固定长度的二进制数中,低位是最不重要的位。 低位(A, 2位) 00
清除某位 使用位运算符将特定位置的位设置为0。 A & ~(1 << 3) (清除第3位) 0001 1100
设置某位 使用位运算符将特定位置的位设置为1。 A | (1 << 3) (设置第3位) 0011 1100
切换某位 使用位运算符切换特定位置的位值。 A ^ (1 << 3) (切换第3位) 0011 0100
检查某位 使用位运算符检查特定位置的位是0还是1。 (A & (1 << 3)) != 0 (检查第3位) True (因为第3位为1)
设置高n位 使用位运算符设置最高的n位为1。 A | ((1 << n) - 1) << (位数 - n) 例如,设置最高2位: 1100 1100
清除高n位 使用位运算符将最高的n位设置为0。 A & ~(((1 << n) - 1) << (位数 - n)) 例如,清除最高2位: 0011 1100
设置低n位 使用位运算符设置最低的n位为1。 A | (1 << n) - 1 例如,设置最低2位: 0011 1101
清除低n位 使用位运算符将最低的n位设置为0。 A & ~((1 << n) - 1) 例如,清除最低2位: 0011 1100
#include <stdio.h>
#include <stdlib.h>
// 将二进制字符串转换为长整型
long binToLong(const char *binStr) {
    return strtol(binStr, NULL, 2);
}
// 将十六进制字符串转换为长整型
long hexToLong(const char *hexStr) {
    return strtol(hexStr, NULL, 16);
}
// 设置高n位为1
long setHighNBits(long num, int totalBits, int n) {
    return num | ((1 << n) - 1) << (totalBits - n);
}
// 清除高n位
long clearHighNBits(long num, int totalBits, int n) {
    return num & ~(((1 << n) - 1) << (totalBits - n));
}
// 设置低n位为1
long setLowNBits(long num, int n) {
    return num | (1 << n) - 1;
}
// 清除低n位
long clearLowNBits(long num, int n) {
    return num & ~((1 << n) - 1);
}
int main() {
    char *s1 = "1010"; 
    char *s2 = "1011";
    char *s3 = "FFFF"; 
    long n1 = binToLong(s1);
    long n2 = binToLong(s2);
    long n3 = n1 & n2;
    long n4 = n1 | n2;
    long n5 = n1 ^ n2;
    long n6 = hexToLong(s3);
  printf("二进制 1010 = %ld\n", n1);  // 10
  printf("二进制 1011 = %ld\n", n2);  // 11
  printf("1010 & 1011 = %ld  (与操作)\n", n3);  // 10
  printf("1010 | 1011 = %ld  (或操作)\n", n4);  // 11
  printf("1010 ^ 1011 = %ld  (异或操作)\n", n5);  // 1
  printf("1011 << 2 = %ld  (左移2位)\n", n2 << 2);  // 44
  printf("1011 >> 2 = %ld  (右移2位)\n", n2 >> 2);  // 2
  printf("65,535 & ~(1 << 3) (清除第3位) = %ld\n", (n6 & ~(1 << 3)));  // 65527
  printf("65527 | (1 << 3) (设置第3位) = %ld\n", (65527 | (1 << 3)));  // 65535
  printf("65527 ^ (1 << 3) (切换第3位) = %ld\n", (65527 ^ (1 << 3)));  // 65535
  printf("65535 ^ (1 << 3) (切换第3位) = %ld\n", (65535 ^ (1 << 3)));  // 65527
  printf("(65535 & (1 << 3)) != 0 (检查第3位) = %d\n", ((65535 & (1 << 3)) != 0));  // 1
  printf("(65527 & (1 << 3)) != 0 (检查第3位) = %d\n", ((65527 & (1 << 3)) != 0));  // 0
  // 高级操作
  printf("设置16383的最高2位为1 = %ld\n", setHighNBits(16383, 16, 2));  // 65535
  printf("清除65535的最高2位 = %ld\n", clearHighNBits(65535, 16, 2));  // 16383
  printf("设置65532的最低2位为1 = %ld\n", setLowNBits(65532, 2));  // 65535
  printf("清除65535的最低2位 = %ld\n", clearLowNBits(65535, 2));  // 65532
    return 0;
}

注意,在C/C++/Java/Kotlin等编程语言中,整型(int)通常用32位来表示,所以左移或右移超过32位的操作是无效的。另外,对于有符号的整型(signed int),右移操作可能会保留符号位(最高位),这称为算术右移(arithmetic right shift)。对于无符号的整型(unsigned int),右移操作不会保留符号位,这称为逻辑右移(logical right shift)。因此,在进行右移操作时,需要注意整型的符号和长度。

位运算在进制转换中的应用

位运算可以用来实现一些简单的进制转换。例如:

  • 将一个十六进制数转换为二进制数:可以将该数分成四个一组的数字,然后将每个数字转换为四位的二进制数,再拼接起来。例如,十六进制数ABC可以分成A,B,C三个数字,然后分别转换为1010,1011,1100三个四位的二进制数,再拼接起来得到101010111100。
  • 将一个二进制数转换为十六进制数:可以将该数分成四个一组的位,然后将每个四位的二进制数转换为一个十六进制数字,再拼接起来。例如,二进制数101010111100可以分成1010,1011,1100三个四位的二进制数,然后分别转换为A,B,C三个十六进制数字,再拼接起来得到ABC。
  • 将一个八进制数转换为二进制数:可以将该数分成三个一组的数字,然后将每个数字转换为三位的二进制数,再拼接起来。例如,八进制数123可以分成1,2,3三个数字,然后分别转换为001,010,011三个三位的二进制数,再拼接起来得到001010011。
  • 将一个二进制数转换为八进制数:可以将该数分成三个一组的位,然后将每个三位的二进制数转换为一个八进制数字,再拼接起来。例如,二进制数001010011可以分成001,010,011三个三位的二进制数,然后分别转换为1,2,3三个八进制数字,再拼接起来得到123。

这些方法都可以用位运算来实现。例如,在C/C++中:

#include <stdio.h>
int main()
{
    int a = 0xABC; // 十六进制数ABC
    int b = 0b101010111100; // 二进制数101010111100
    int c = 0123; // 八进制数123
    int d = 0b001010011; // 二进制数001010011
    // 将十六进制数转换为二进制数
    printf("0x%X = ", a); // 输出0xABC =
    for (int i = 12; i > 0; i -= 4) { // 每次右移4位,取最低4位
        int x = (a >> (i - 4)) & 0xF; // 取最低4位的十六进制数字
        printf("%04b", x); // 输出对应的四位二进制数
    }
    printf("\n"); // 输出换行符
    // 将二进制数转换为十六进制数
    printf("0b%b = ", b); // 输出0b101010111100 =
    for (int i = 12; i > 0; i -= 4) { // 每次右移4位,取最低4位
        int x = (b >> (i - 4)) & 0xF; // 取最低4位的二进制数
        printf("%X", x); // 输出对应的十六进制数字
    }
    printf("\n"); // 输出换行符
    // 将八进制数转换为二进制数
    printf("0%o = ", c); // 输出0123 =
    for (int i = 9; i > 0; i -= 3) { // 每次右移3位,取最低3位
        int x = (c >> (i - 3)) & 0x7; // 取最低3位的八进制数字
        printf("%03b", x); // 输出对应的三位二进制数
    }
    printf("\n"); // 输出换行符
    // 将二进制数转换为八进制数
    printf("0b%b = ", d); // 输出0b001010011 =
    for (int i = 9; i > 0; i -= 3) { // 每次右移3位,取最低3位
        int x = (d >> (i - 3)) & 0x7; // 取最低3位的二进制数
        printf("%o", x); // 输出对应的八进制数字
    }
    printf("\n"); // 输出换行符
    return 0;
}

输出结果为:

0xABC = 101010111100
0b101010111100 = ABC
0123 = 001010011
0b001010011 = 123

进制转换的实际应用

LED控制在嵌入式系统中的应用

在嵌入式系统中,LED灯常用作指示灯,显示系统的状态或者其他信息。为了控制LED灯的亮、灭、闪烁等状态,需要使用位操作来控制硬件寄存器。这是因为硬件寄存器通常是按位来定义的,每一位都有特定的功能。例如,某个寄存器的第0位控制LED1的亮灭,第1位控制LED2的亮灭,以此类推。

#include <stdio.h>
// 假设我们有一个8位的寄存器,用于控制8个LED灯的亮灭
// 我们使用一个字节(8位)来模拟这个寄存器
typedef unsigned char Register;
// LED控制函数
void turnOnLED(Register *reg, int ledNum) {
    *reg |= (1 << ledNum); // 设置指定位为1,亮起LED灯
}
void turnOffLED(Register *reg, int ledNum) {
    *reg &= ~(1 << ledNum); // 清除指定位,熄灭LED灯
}
void toggleLED(Register *reg, int ledNum) {
    *reg ^= (1 << ledNum); // 切换指定位,如果LED灯亮则熄灭,如果熄灭则亮起
}
int isLEDOn(Register reg, int ledNum) {
    return (reg & (1 << ledNum)) != 0; // 检查指定位是否为1
}
int main() {
    Register LEDRegister = 0; // 初始状态,所有LED灯都熄灭
    turnOnLED(&LEDRegister, 2); // 亮起第2个LED灯
    printf("LED 2状态: %s\n", isLEDOn(LEDRegister, 2) ? "亮" : "灭");//LED 2状态: 亮
    toggleLED(&LEDRegister, 2); // 切换第2个LED灯的状态
    printf("LED 2状态: %s\n", isLEDOn(LEDRegister, 2) ? "亮" : "灭");//LED 2状态: 灭
    turnOffLED(&LEDRegister, 2); // 熄灭第2个LED灯
    printf("LED 2状态: %s\n", isLEDOn(LEDRegister, 2) ? "亮" : "灭");//LED 2状态: 灭
    return 0;
}

定义了一个名为Register的类型来模拟硬件寄存器,并使用了位操作来控制LED灯的状态。这种方法在实际的嵌入式系统中非常常见,因为它可以直接操作硬件寄存器,实现对LED灯的精确控制。

解析CAN的标准帧和扩展帧的应用

CAN协议定义了两种帧格式:标准帧和扩展帧。标准帧使用11位的ID,而扩展帧使用29位的ID。为了区分这两种帧格式,CAN协议在帧ID的高位定义了一些标志位

在我写CAN JNI接口实际应用中,需要对CAN帧进行解析,以确定它是标准帧还是扩展帧,并获取其ID。为了实现这一目的,通常需要使用位操作来处理帧ID。

当时很不理解, 上位机发送扩展至0x18121001 为什么native拿到的frame.can_id是0x98121001 , 后面学完位运算符 才理解原来是29位ID 需要清除高3位 才能拿到真正的值。

#include <stdio.h>
#include <stdint.h>
// 假设我们有一个32位的can_id(扩展帧ID),其中高3位用于标志位,低29位用于ID
typedef uint32_t CAN_ID;
// CAN帧结构体
typedef struct {
    CAN_ID can_id;
    // 其他字段,如数据、DLC等
} CAN_Frame;
// 清除标志位
CAN_ID cleanCANID(CAN_ID id) {
    return id & 0x1FFFFFFF; // 清除高3位
}
// 检查是否为扩展帧
int isExtendedFrame(CAN_ID id) {
    return (id & 0x80000000) != 0; // 检查最高位
}
int main() {
    CAN_Frame frame;
    frame.can_id = 0x98121001; // 假设这是一个扩展帧的ID
    printf("原始CAN ID: 0x%X\n", frame.can_id);//原始CAN ID: 0x98121001
    printf("清除标志位后的CAN ID: 0x%X\n", cleanCANID(frame.can_id));//清除标志位后的CAN ID: 0x18121001
    printf("是否为扩展帧: %s\n", isExtendedFrame(frame.can_id) ? "是" : "否");//是否为扩展帧: 是
    return 0;
}

上面的代码定义了一个CAN_Frame结构体来模拟CAN帧,并使用了位操作来处理帧ID。

疑问和解答

问题1: 0x98121001 & 0x1FFFFFFF 和clearHighNBits(0x98121001, 32, 3)有什么区别?

0x98121001 & 0x1FFFFFFF 这个操作是将0x98121001的高3位清零,得到的结果是0x18121001

使用clearHighNBits函数,如果希望清除高3位,那么totalBits应该是32(因为处理的是一个32位的数),n应该是3。函数内部的操作是生成一个掩码,该掩码的高3位是0,其余位是1,然后与输入的数进行与操作。这与上述直接的位操作是相同的。

0x98121001 & 0x1FFFFFFFclearHighNBits(0x98121001, 32, 3) 是等价的。

问题2:(0x98121001 & 0x80000000) != 0;和((0x98121001 & (1 << 31 )) != 0) 有什么区别?

(0x98121001 & 0x80000000) != 0 这个操作是检查0x98121001的最高位是否为1。如果是,则返回true,否则返回false

对于((0x98121001 & (1 << 31)) != 0),这个操作也是检查0x98121001的最高位是否为1。如果是,则返回true,否则返回false

所以(0x98121001 & 0x80000000) != 0((0x98121001 & (1 << 31)) != 0) 是等价的。两者都是检查32位数的最高位。


希望这篇文章能对您有所帮助。如果还有其他问题或建议,请留言与私信。

相关文章
|
1天前
|
存储 算法 Java
性能优化:Java垃圾回收机制深度解析 - 让你的应用飞起来!
Java垃圾回收自动管理内存,防止泄漏,提升性能。GC分为标记-清除、复制、标记-整理和分代收集等算法。JVM内存分为堆、方法区等区域。常见垃圾回收器有Serial、Parallel、CMS和G1。调优涉及选择合适的GC、调整内存大小和使用参数。了解和优化GC能提升应用性能。
11 3
|
3天前
|
缓存 并行计算 Java
重温JAVA线程池精髓:Executor、ExecutorService及Executors的源码剖析与应用指南
重温JAVA线程池精髓:Executor、ExecutorService及Executors的源码剖析与应用指南
|
3天前
|
存储 安全 算法
Java中的LinkedBlockingQueue:原理、应用与性能深入剖析
Java中的LinkedBlockingQueue:原理、应用与性能深入剖析
|
3天前
|
Java 编译器 开发者
Java中的Lambda表达式及其应用
在现代Java编程中,Lambda表达式作为函数式编程的重要特性之一,极大地简化了代码的书写和理解。本文将深入探讨Lambda表达式的定义、语法结构以及在实际开发中的应用场景,帮助读者更好地理解和运用这一强大的编程工具。
|
3天前
|
存储 并行计算 算法
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
深入解析Java并发库(JUC)中的Phaser:原理、应用与源码分析
|
3天前
|
设计模式 安全 Java
Java中的动态代理:原理与应用
Java中的动态代理:原理与应用
|
2天前
|
存储 缓存 Java
深入剖析Java并发库(JUC)之StampedLock的应用与原理
深入剖析Java并发库(JUC)之StampedLock的应用与原理
深入剖析Java并发库(JUC)之StampedLock的应用与原理
|
3天前
|
存储 安全 Java
Java内省(Introspector)机制:深入理解与应用
Java内省(Introspector)机制:深入理解与应用
|
19小时前
|
监控 Java UED
Java并发编程:深入理解线程池的设计与应用
本文旨在通过数据导向和科学严谨的方式,深入探讨Java并发编程中的关键组件——线程池。文章首先概述了线程池的基本概念与重要性,随后详细解读了线程池的核心参数及其对性能的影响,并通过实验数据支持分析结果。此外,文中还将介绍如何根据不同的应用场景选择或设计合适的线程池,以及如何避免常见的并发问题。最后,通过案例研究,展示线程池在实际应用中的优化效果,为开发人员提供实践指导。
7 0
|
1天前
|
监控 Java 测试技术
提高Java应用稳定性的部署实践
提高Java应用稳定性的部署实践