进制转换在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 & 0x1FFFFFFF
和 clearHighNBits(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位数的最高位。
希望这篇文章能对您有所帮助。如果还有其他问题或建议,请留言与私信。