探秘嵌入式位运算:基础与高级技巧

目录

一、位运算基础知识

1.1. 位运算符

1.1.1. 与运算(&)

1.1.2. 或运算(|)

1.1.3. 异或运算(^)

1.1.4. 取反运算(~)

1.1.5. 双重按位取反运算符(~~)

1.1.6. 逻辑取反(!)

1.1.7. 双重逻辑非运算符 (!!)

1.2. 位移运算

1.2.1. 左移运算(<<)

1.2.2. 右移运算(>>)

二、嵌入式位运算基础应用

2.1. 设置标志位

2.2. 清除标志位

2.3. 检查标志位

2.4. 位域

2.5. 硬件控制

2.6. 内存优化

2.7. 加密和校验

三、嵌入式位运算高级技巧

3.1. 位运算优化

3.2. 位运算实现复杂逻辑

3.3. 位运算与硬件接口

3.4. 利用位运算实现快速判断奇偶性

3.5. 不使用临时变量交换两个数

3.6. 求一个数的二进制中1的个数

3.7. 判断一个数是否是2的幂次方

3.8. 利用位运算实现标志位的设置和清除

四、总结


在嵌入式系统开发中,位运算扮演着举足轻重的角色。它允许开发者直接对二进制位进行操作,从而实现数据的高效处理和系统性能的优化。本文深入探讨嵌入式位运算的基础知识与高级技巧,从基本的位操作如与、或、非、异或,到高级的位运算优化、复杂逻辑实现及硬件接口控制等,全面掌握并灵活应用这一关键技能,提升嵌入式系统的性能和稳定性。

一、位运算基础知识

1.1. 位运算符

1.1.1. 与运算(&)

  • 规则:当两个对应的二进制位都为1时,结果位才为1,否则为0。
  • 示例:5(二进制0101)与3(二进制0011)进行与运算,结果为0001,即1。
  • 应用场景
    • 屏蔽某些特定的位。例如,清除一个整数的低两位。
    • 检查某个位是否为1(通过与一个只有一个位为1的数进行与运算)。
  • 示例代码:
#include <stdio.h>int main() {int a = 5; // 二进制0101int b = 3; // 二进制0011int result = a & b; // 结果为0001,即1printf("a & b = %d\n", result);// 清除低两位int clear_low_two = a & 0b11111100; // 0b表示二进制,结果为0100,即4printf("清除低两位后的a = %d\n", clear_low_two);return 0;
}

运行结果: 

 

1.1.2. 或运算(|)

  • 规则:两个对应的二进制位只要有一个为1,结果位就为1。
  • 示例:5(二进制0101)或3(二进制0011)进行或运算,结果为0111,即7。
  • 应用场景
    • 设置某些特定的位。例如,将一个整数的低两位设置为1。
    • 合并两个数的位。
  • 示例代码:
#include <stdio.h>int main() {int a = 5; // 二进制0101int b = 3; // 二进制0011int result = a | b; // 结果为0111,即7printf("a | b = %d\n", result);// 设置低两位为1int set_low_two = a | 0b00000011; // 结果为0111,即7printf("设置低两位为1后的a = %d\n", set_low_two);return 0;
}

 

1.1.3. 异或运算(^)

  • 规则:两个对应的二进制位不同时,结果位为1,相同时为0。
  • 示例:5(二进制0101)异或3(二进制0011)进行异或运算,结果为0110,即6。
  • 应用场景
    • 交换两个变量的值而无需使用临时变量。
    • 检查两个数是否相等(如果异或结果为0,则两数相等)。
  • 示例代码:
#include <stdio.h>int main() {int a = 5; // 二进制0101int b = 3; // 二进制0011int result = a ^ b; // 结果为0110,即6printf("a ^ b = %d\n", result);// 交换a和b的值a = a ^ b; // a = 0111 (7)b = a ^ b; // b = 0101 (5), a的值已经传递给ba = a ^ b; // a = 0011 (3), b的值已经传递给aprintf("交换后a = %d, b = %d\n", a, b);return 0;
}

 

1.1.4. 取反运算(~)

  • 规则
    • 按位取反运算符~作用于一个整数的二进制表示。
    • 它将二进制表示中的每一位都进行取反操作,即0变为1,1变为0。
    • 结果通常是一个补码表示的整数。
  • 示例:~5(二进制0101)结果为1010,但在有符号整数中,最高位为符号位,因此结果为-6(补码表示)。
  • 应用场景
    • 位掩码操作:用于设置、清除或切换特定位。
    • 某些加密算法中:作为位操作的一部分。
    • 调试和测试:用于检查二进制表示的特定位是否设置。
  • 代码示例
#include <stdio.h>int main() {int a = 5; // 二进制0101int result = ~a; // 结果为1010,但在有符号整数中表示为-6(补码)printf("~a = %d\n", result);// 注意:在有符号整数中,取反后得到的是补码表示的负数// 如果需要得到无符号整数的结果,可以使用无符号类型unsigned int unsigned_a = 5; // 二进制0101unsigned int unsigned_result = ~unsigned_a; // 结果为11111111111111111111111111110101(32位系统),即4294967291(十进制)printf("~unsigned_a (unsigned) = %u\n", unsigned_result);return 0;
}

 

注意:在C语言中,整数的表示方式(有符号或无符号)会影响取反运算的结果。对于有符号整数,取反后得到的是补码表示的负数;对于无符号整数,取反后得到的是对应位数的全1减去该数的结果。

1.1.5. 双重按位取反运算符(~~)

~~在编程中是一种常用的位运算技巧,主要利用按位取反运算符“~”的特性进行两次取反操作。

  • 规则:
    • 双重按位取反运算符 “~~” 实际上是对一个值先进行一次按位取反操作(将每一位的 0 变为 1,1 变为 0),然后再进行一次按位取反操作。对于整数类型的值,这通常会将一些特殊的值转换为更 “正常” 的整数值。
    • 如果原始值为正整数,第一次取反后变为负数(最高位变为 1),再次取反则变为对应的正整数。如果原始值为负数,第一次取反后变为一个很大的正数(因为负数在计算机中以补码形式存储,取反后得到其绝对值对应的正数减 1 的值),再次取反则变为原来的负数。对于零值,取反两次后仍然是零。
  • 示例:

    • 假设原始值为 5,其二进制表示为 00000101。
      • 第一次取反变为 11111010。
      • 再次取反变为 00000101,即又变回了 5。
    • 若原始值为 -3,以 8 位二进制表示,原码为 10000011,补码为 11111101。
      • 第一次取反变为 00000010。
      • 再次取反变为 11111101,即 -3。
  • 应用场景:
    • 数值转换和规范化:在某些情况下,可能需要将一些特殊的整数值(如从外部输入的可能带有特殊标记的值)转换为正常的整数范围。例如,将可能为 -1 表示无效的值转换为 0 表示有效。可以使用 “~~” 运算符,如果输入值为 -1,取反两次后变为 0。
    • 简洁的布尔值转换:在某些情况下,可以将非零值转换为 1,零值转换为 0,类似于布尔值的真和假。虽然可以使用其他方式(如条件表达式)进行这种转换,但 “~~” 运算符可以提供一种简洁的方式。
    • 处理特殊标志值:在一些编程场景中,可能使用特定的整数值作为标志,通过双重按位取反可以将这些标志值转换为更易于处理的形式。
  • 代码示例:
#include <stdio.h>int main() {int value1 = 5;int result1 = ~~value1;printf("For value1 = %d, double bitwise NOT result is %d\n", value1, result1);int value2 = -3;int result2 = ~~value2;printf("For value2 = %d, double bitwise NOT result is %d\n", value2, result2);int value3 = -1;int result3 = ~~value3;printf("For value3 = %d, double bitwise NOT result is %d\n", value3, result3);return 0;
}

 

在这个示例中,展示了对不同值使用双重按位取反运算符的结果。程序会输出三个值经过双重按位取反后的结果,验证了上述规则和应用场景。 

1.1.6. 逻辑取反(!)

  • 规则
    • 逻辑取反运算符!作用于一个布尔值或可以转换为布尔值的表达式。
    • 它将非零值转换为0(假),将零值转换为1(真)。
  • 示例
    • !0 的结果是1(真),因为0是假。
    • !5 的结果是0(假),因为5是非零值,被视为真。
  • 应用场景
    • 条件判断:在if语句或其他需要布尔值的上下文中使用。
    • 逻辑运算:与&&(逻辑与)和||(逻辑或)结合使用,构建复杂的逻辑表达式。
  • C语言代码示例
#include <stdio.h>int main() {int a = 0;int b = 5;int result_a = !a; // 逻辑取反,0变为1(真)int result_b = !b; // 逻辑取反,非0值变为0(假)printf("!%d = %d, !%d = %d\n", a, result_a, b, result_b); // 输出!0 = 1, !5 = 0return 0;
}

 

1.1.7. 双重逻辑非运算符 (!!)

  • 规则

    • 第一个逻辑非 “!” 将操作数转换为布尔值并取反,即非零值变为 false,零值变为 true。
    • 第二个逻辑非再次取反,将 false 变为 true,true 变为 false,最终实现将非零值转换为 true,零值转换为 false。
  • 示例:如果有一个变量 x,值为 5。!!x 的结果为 true。如果 x 为 0,!!x 的结果为 false。

  • 应用场景

    • 在需要明确的布尔值的情况下,可以使用 “!!” 将可能是各种类型的值转换为布尔类型。例如,判断一个指针是否为 NULL,或者判断一个数组是否为空。
    • 在一些条件判断中,确保得到一个明确的布尔值可以使代码更加清晰和易于理解。
  • C 语言代码示例: 

int x = 5;
bool y =!!x;
if (y) {printf("x is non-zero.\n");
} else {printf("x is zero.\n");
}

在这个例子中,由于 x 非零,所以 y 的值为 true,程序将输出 “x is non-zero.”。 

1.2. 位移运算

位移运算分为左移运算和右移运算,它们分别将一个数的二进制表示向左或向右移动指定的位数。

1.2.1. 左移运算(<<)

  • 规则:将一个数的二进制位向左移动指定的位数,相当于乘以2的幂次方。左移会丢弃高位(对于有符号数,可能会导致符号位的丢失,从而改变数的符号),低位补0。
  • 示例:5(二进制0101)左移2位,结果为20(二进制10100)。
  • 应用场景
    • 快速实现乘以2的幂次方的操作。
    • 在某些算法中用于位操作和数据打包。
  • C语言代码示例
#include <stdio.h>int main() {int a = 5; // 二进制0101int result = a << 2; // 左移2位,结果为20(二进制10100)printf("%d << 2 = %d\n", a, result);// 验证左移相当于乘以2的幂次方int check_result = a * 4; // 5 * 4 = 20printf("%d * 4 = %d\n", a, check_result);return 0;
}

 

1.2.2. 右移运算(>>)

  • 规则
    • 对于有符号数,右移时会保留符号位(算术右移)。如果最高位(符号位)是1,则左边填充1;如果是0,则左边填充0。
    • 对于无符号数,右边用0填充(逻辑右移)。
  • 示例:5(二进制0101)右移1位,对于有符号数结果为2(二进制0010);对于无符号数结果也是2(但内部表示可能不同,取决于编译器和平台)。
  • 应用场景
    • 快速实现除以2的幂次方的操作(注意,对于负数结果可能不是简单的除法)。
    • 在某些算法中用于位操作和数据解包。
  • C语言代码示例(有符号数和无符号数的右移):
#include <stdio.h>int main() {int signed_a = 5; // 有符号数,二进制0101int signed_result = signed_a >> 1; // 右移1位,结果为2(二进制0010)printf("%d >> 1 (signed) = %d\n", signed_a, signed_result);unsigned int unsigned_a = 5; // 无符号数,二进制0101unsigned int unsigned_result = unsigned_a >> 1; // 右移1位,结果也为2(但内部表示可能不同)printf("%u >> 1 (unsigned) = %u\n", unsigned_a, unsigned_result);// 验证右移相当于除以2的幂次方int check_result = signed_a / 2; // 5 / 2 = 2printf("%d / 2 = %d\n", signed_a, check_result);// 注意:对于负数,右移的结果可能不是简单的除法int negative_a = -5; // 二进制(补码)11111111111111111111111111111011(32位系统)int negative_result = negative_a >> 1; // 右移1位,结果为-3(算术右移,保留符号位)printf("%d >> 1 (signed, negative) = %d\n", negative_a, negative_result);return 0;
}

 

注意

  • 对于有符号数的右移,C语言标准规定使用算术右移,即保留符号位。但是,具体的实现可能依赖于编译器和平台。
  • 对于无符号数的右移,C语言标准规定使用逻辑右移,即右边用0填充。
  • 在处理负数时,右移的结果可能不是简单的除法,因为算术右移会保留符号位并可能导致数值的“向下取整”(向更小的负数方向)。 

二、嵌入式位运算基础应用

在嵌入式系统中,位运算是一种高效且常用的操作手段,它允许开发者在不使用复杂数据结构或额外内存的情况下,对硬件寄存器、状态标志等进行精确控制。

2.1. 设置标志位

使用按位或运算(|)可以将一个数的特定位设置为1,而不影响其他位。这常用于设置硬件寄存器的某个功能位。例如,要设置某个寄存器的第n位为1,可以使用如下操作:寄存器 |= (1 << n)。

#define REGISTER_ADDRESS *((volatile uint32_t*)0x40000000) // 假设寄存器地址
#define BIT_N 3 // 要设置的位void setBitN() {REGISTER_ADDRESS |= (1 << BIT_N); // 设置第BIT_N位为1
}

2.2. 清除标志位

使用按位与运算(&)和取反运算(~)可以将一个数的特定位清零,而不影响其他位。这常用于关闭硬件寄存器的某个功能。例如,要清除某个寄存器的第n位,可以使用如下操作:寄存器 &= ~(1 << n)。

void clearBitN() {REGISTER_ADDRESS &= ~(1 << BIT_N); // 清除第BIT_N位
}

2.3. 检查标志位

使用按位与运算,可以检查一个数的特定位是否为1。例如,要检查某个寄存器的第n位是否为1,可以使用如下操作:if (寄存器 & (1 << n))。

int isBitNSet() {return (REGISTER_ADDRESS & (1 << BIT_N)) != 0; // 检查第BIT_N位是否为1
}

2.4. 位域

位域是一种将一个或多个字段打包到一个单一的机器字中的数据结构。位域可以有效地压缩存储空间,并且可以提高程序的执行效率。常用于控制寄存器、状态寄存器等。

struct {unsigned int bit0 : 1;unsigned int bit1 : 1;unsigned int bit2 : 1;unsigned int bit3 : 1;// 其他位域...
} bitField;// 假设bitField位于某个寄存器的地址上,通过指针访问
#define BITFIELD_ADDRESS *((volatile struct { /* 同上结构 */ }*)0x40000004)void setBitFieldBit0() {BITFIELD_ADDRESS.bit0 = 1; // 设置bit0为1
}int isBitFieldBit1Set() {return BITFIELD_ADDRESS.bit1 != 0; // 检查bit1是否为1
}

注意:直接使用位域访问硬件寄存器时,需要确保位域的结构与硬件寄存器的布局完全匹配,并且编译器不会引入额外的内存对齐或填充。

2.5. 硬件控制

通过位运算可以直接操作硬件寄存器的特定位,实现对硬件设备的控制。例如,设置GPIO引脚的输出状态、配置中断控制器等。 

// 假设有一个GPIO控制寄存器,其中第0位控制GPIO0的输出状态
#define GPIO_CONTROL_REGISTER *((volatile uint32_t*)0x40020000)void setGPIO0High() {GPIO_CONTROL_REGISTER |= (1 << 0); // 设置GPIO0为高电平
}void setGPIO0Low() {GPIO_CONTROL_REGISTER &= ~(1 << 0); // 设置GPIO0为低电平
}

2.6. 内存优化

位运算可以在不使用额外内存的情况下实现一些复杂的逻辑操作,从而节省内存资源。例如,可以使用位掩码来检查、设置或清除多个标志位。

2.7. 加密和校验

位运算可以用于一些简单的加密算法和数据校验算法中。例如,可以使用循环冗余校验(CRC)算法来检测数据传输中的错误。

注意:在实际应用中,加密和校验算法通常比这里提到的要复杂得多,并且需要使用专门的库或硬件加速器来实现。

位运算在嵌入式系统中具有广泛的应用,从硬件控制到内存优化,再到加密和校验,都离不开位运算的支持。通过熟练掌握位运算的基本操作和应用场景,开发者可以编写出更加高效、可靠的嵌入式系统代码。

三、嵌入式位运算高级技巧

在嵌入式系统开发中,位运算不仅是一种基础技能,更是优化性能、实现复杂逻辑和精确控制硬件设备的关键。

3.1. 位运算优化

在嵌入式系统中,性能优化至关重要。位运算因其高效性而常被用于替代乘法、除法等耗时操作。

示例:使用位运算实现乘法(针对2的幂次方)

#define MULTIPLY_BY_TWO(x) ((x) << 1) // 左移一位相当于乘以2
#define MULTIPLY_BY_FOUR(x) ((x) << 2) // 左移两位相当于乘以4
#define DIVIDE_BY_TWO(x) ((x) >> 1) // 右移一位相当于除以2(注意:结果为整数除法)int main() {int a = 8;int b = MULTIPLY_BY_TWO(a); // b = 16int c = DIVIDE_BY_TWO(a); // c = 4return 0;
}

注意:上述优化仅适用于乘以或除以2的幂次方的情况。对于其他乘法或除法操作,位运算并不能直接替代,但可以通过查表法、分段线性逼近等方法进行近似优化。

3.2. 位运算实现复杂逻辑

位运算不仅可以用于简单的标志位操作,还可以用于实现复杂的逻辑判断和数据处理。例如,可以使用位运算实现状态机的状态转移、数据的压缩和解压缩等。

示例1:使用位运算实现状态机的状态转移 

typedef enum {STATE_IDLE,STATE_RUNNING,STATE_ERROR,// 其他状态...
} State;State currentState = STATE_IDLE;void stateMachine(uint8_t event) {switch (currentState) {case STATE_IDLE:if (event & 0x01) { // 假设事件0x01表示启动currentState = STATE_RUNNING;}break;case STATE_RUNNING:if (event & 0x02) { // 假设事件0x02表示错误currentState = STATE_ERROR;} else if (event & 0x03) { // 假设事件0x03表示停止(包含0x01的启动位,但这里通过逻辑判断忽略)currentState = STATE_IDLE;}break;// 其他状态处理...}
}

注意:上述示例中的状态机实现较为简单,实际应用中可能需要更复杂的逻辑判断和状态转移条件。

示例2:数据的压缩和解压缩:可以使用位运算来实现简单的数据压缩算法。例如,将多个小的整数打包到一个较大的整数中,通过位操作来提取和解压这些数据。

// 压缩两个 4 位的整数到一个 8 位的整数
unsigned char compressData(unsigned char data1, unsigned char data2) {return (data1 << 4) | data2;
}// 解压一个 8 位的整数得到两个 4 位的整数
void decompressData(unsigned char compressedData, unsigned char *data1, unsigned char *data2) {*data1 = (compressedData >> 4) & 0x0F;*data2 = compressedData & 0x0F;
}

3.3. 位运算与硬件接口

在嵌入式系统中,许多硬件接口都是通过寄存器进行控制的。通过位运算,可以直接操作这些寄存器,实现对硬件设备的精确控制。例如,可以通过位运算设置GPIO引脚的输出状态、配置定时器的参数等。

示例1:通过位运算设置GPIO引脚的输出状态

#define GPIO_PORT *((volatile uint32_t*)0x40021000) // 假设GPIO端口地址
#define GPIO_PIN 5 // 假设要操作的GPIO引脚编号void setGPIOPin(uint8_t state) {if (state) {GPIO_PORT |= (1 << GPIO_PIN); // 设置GPIO引脚为高电平} else {GPIO_PORT &= ~(1 << GPIO_PIN); // 设置GPIO引脚为低电平}
}

示例2:配置定时器的参数:可以使用位运算来设置定时器的各种参数,如预分频值、计数模式等。

// 假设定时器寄存器地址为 0x40010000
volatile unsigned int *timerRegister = (volatile unsigned int *)0x40010000;// 设置预分频值为 128
*timerRegister &= ~(0xFF << 8);
*timerRegister |= (128 << 8);

3.4. 利用位运算实现快速判断奇偶性

对于一个整数n,如果n & 1的结果为0,则n是偶数;如果结果为1,则n是奇数。 

int isEven(int n) {return !(n & 1); // 如果n & 1为0,则n是偶数,返回1(真);否则返回0(假)
}int isOdd(int n) {return n & 1; // 如果n & 1为1,则n是奇数,返回1(真);否则返回0(假)
}

3.5. 不使用临时变量交换两个数

使用异或运算可以实现不借助临时变量交换两个数的值。 

void swap(int *a, int *b) {*a = *a ^ *b;*b = *a ^ *b;*a = *a ^ *b;
}

注意:虽然这种方法很有趣,但在实际应用中,由于现代编译器的优化和处理器指令集的发展,使用临时变量的交换操作通常与这种方法在性能上相差无几,甚至可能更优。因此,在选择交换方法时,应更关注代码的可读性和可维护性。

3.6. 求一个数的二进制中1的个数

可以通过Brian Kernighan算法(也称为“每次消除最低位的1”算法)来高效求解。 

int countOnes(int n) {int count = 0;while (n) {n &= (n - 1); // 每次消除最低位的1count++;}return count;
}

注意:这种方法的时间复杂度为O(k),其中k是二进制中1的个数。对于稀疏的二进制数(即1的个数较少),这种方法比逐位检查要快。

3.7. 判断一个数是否是2的幂次方

如果一个数n是2的幂次方,那么n的二进制表示中只有一位是1。可以通过判断n & (n - 1)是否为0来确定。

int isPowerOfTwo(int n) {return n > 0 && (n & (n - 1)) == 0; // 如果n大于0且n & (n - 1)为0,则n是2的幂次方
}

3.8. 利用位运算实现标志位的设置和清除

在嵌入式系统中,常常需要设置和清除一些标志位。可以使用或运算设置标志位,与运算清除标志位。

// 设置标志位
#define FLAG_BIT 1 << 3
void setFlag(unsigned int *flags) {*flags |= FLAG_BIT;
}// 清除标志位
void clearFlag(unsigned int *flags) {*flags &= ~FLAG_BIT;
}// 检查标志位是否设置
int isFlagSet(unsigned int flags) {return flags & FLAG_BIT;
}

位运算在嵌入式系统开发中具有广泛的应用和重要的价值。通过合理使用位运算技巧,可以优化代码性能、实现复杂逻辑判断和精确控制硬件设备。同时,也需要注意代码的可读性和可维护性,在性能与可读性之间找到平衡点。 

四、总结

综上所述,嵌入式位运算是嵌入式开发的核心技能,对系统性能优化至关重要。掌握位运算基础与高级技巧,如位运算优化、复杂逻辑实现、硬件接口控制等,能显著提升代码效率与硬件控制能力。这些技能有助于开发者深入理解嵌入式系统,实现性能与可读性的最佳平衡。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/479015.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

渗透测试笔记—shodan(7完结)

声明&#xff1a; 学习视频来自B站up主 【泷羽sec】有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&am…

2024年最新版Java八股文复习

最新版本Java八股文复习&#xff0c;每天更新一篇&#xff0c;博主正在持续努力更新中~~~ 一、Java基础篇1、怎么理解面向对象&#xff1f;简单说说封装、继承、多态三大特性&#xff1f;2、多态体现在哪几个方面&#xff1f;3、面向对象的设计原则你知道有哪些吗&#xff1f;4…

Notepad++ 替换所有数字给数字加单引号

前言 今天遇到这样一个场景&#xff1a; 要去更新某张表里 code1,2,3,4,5,6 的数据&#xff0c;把它的 name 设置为 ‘张三’ 但是 code在数据库里面的字段类型是 vachar(64)&#xff0c;它自身携带索引 原本可以这样写 SQL: update tableA set namezhangsan where code in …

前端图像处理(一)

目录 一、上传 1.1、图片转base64 二、图片样式 2.1、图片边框【border-image】 三、Canvas 3.1、把canvas图片上传到服务器 3.2、在canvas中绘制和拖动矩形 3.3、图片(同色区域)点击变色 一、上传 1.1、图片转base64 传统上传&#xff1a; 客户端选择图片&#xf…

推荐一款龙迅HDMI2.0转LVDS芯片 LT6211UX LT6211UXC

龙迅的HDMI2.0转LVDS芯片LT6211UX和LT6211UXC是两款高性能的转换器芯片&#xff0c;它们在功能和应用上有所差异&#xff0c;同时也存在一些共同点。以下是对这两款芯片的详细比较和分析&#xff1a; 一、LT6211UX 主要特性&#xff1a; HDMI2.0至LVDS和MIPI转换器。HDMI2.0输…

51单片机从入门到精通:理论与实践指南入门篇(二)

续51单片机从入门到精通&#xff1a;理论与实践指南&#xff08;一&#xff09;https://blog.csdn.net/speaking_me/article/details/144067372 第一篇总体给大家在&#xff08;全局&#xff09;总体上讲解了一下51单片机&#xff0c;那么接下来几天结束详细讲解&#xff0c;从…

STM32C011开发(3)----Flash操作

STM32C011开发----3.Flash操作 概述硬件准备视频教学样品申请源码下载参考程序生成STM32CUBEMX串口配置堆栈设置串口重定向FLASH数据初始化FLASH 读写演示 概述 STM32C011 系列微控制器内置 Flash 存储器&#xff0c;支持程序存储与数据保存&#xff0c;具备页面擦除、双字写入…

IDEA无法创建java8、11项目创建出的pom.xml为空

主要是由于Spring3.X版本不支持JDK8&#xff0c;JDK11&#xff0c;最低支持JDK17 解决的话要不就换成JDK17以上的版本&#xff0c;但是不太现实 另外可以参考以下方式解决 修改spring初始化服务器地址为阿里云的 https://start.aliyun.com/

【Unity3D】创建自定义字体

前置准备 如图所示&#xff0c;项目工程中需要用文件夹存储0-9的Sprite图片。 使用流程 直接右键存放图片的文件夹&#xff0c;选择【创建自定义字体】&#xff0c;之后会在脚本定义的FontOutputPath中生成材质球和字体。 源码 using System; using System.Collections.Gene…

logminer挖掘日志归档查找问题

--根据发生问题时间点查找归档文件 select first_time,NAME from gv$archived_log where first_time>2016-03-15 17:00:00 and first_time<2016-03-15 21:00:00; 2016-03-15 17:23:55 ARCH/jxdb/archivelog/2016_03_15/thread_1_seq_41588.4060.906577337 2016-03-15 17:…

电商项目高级篇06-缓存

电商项目高级篇06-缓存 1、docker下启动redis2、项目整合redis 缓存 流程图&#xff1a; data cache.load(id);//从缓存加载数据 If(data null){ data db.load(id);//从数据库加载数据 cache.put(id,data);//保存到 cache 中 } return data;在我们的单体项目中可以用Map作…

如何使用GCC手动编译stm32程序

如何不使用任何IDE&#xff08;集成开发环境&#xff09;编译stm32程序? 集成开发环境将编辑器、编译器、链接器、调试器等开发工具集成在一个统一的软件中&#xff0c;使得开发人员可以更加简单、高效地完成软件开发过程。如果我们不使用KEIL,IAR等集成开发环境&#xff0c;…

一个专为云原生环境设计的高性能分布式文件系统

大家好&#xff0c;今天给大家分享一款开源创新的分布式 POSIX 文件系统JuiceFS&#xff0c;旨在解决海量云存储与各类应用平台&#xff08;如大数据、机器学习、人工智能等&#xff09;之间高效对接的问题。 项目介绍 JuiceFS 是一款面向云原生设计的高性能分布式文件系统&am…

Vue-TreeSelect组件最下级隐藏No sub-options

问题&#xff1a;最下级没有数据的话&#xff0c;去除No sub-options信息 为什么没下级&#xff0c;会展示这个&#xff1f; 整个树形结构数据都是由后端构造好返回给前端的。默认子类没数据的话&#xff0c;children是一个空数组。也就是因为这最下级的空数组&#xff0c;导致…

k8s集群增加nfs-subdir-external-provisioner存储类

文章目录 前言一、版本信息二、本机安装nfs组件包三、下载nfs-subdir-external-provisioner配置文件并进行配置1.下载文件2.修改配置 三、进行部署备注&#xff1a;关于镜像无法拉取问题的处理 前言 手里的一台服务器搭建一个单点的k8s集群&#xff0c;然后在本机上使用nfs-su…

C语言数据结构-链表

C语言数据结构-链表 1.单链表1.1概念与结构1.2结点3.2 链表性质1.3链表的打印1.4实现单链表1.4.1 插入1.4.2删除1.4.3查找1.4.4在指定位置之前插入或删除1.4.5在指定位置之后插入或删除1.4.6删除指定位置1.4.7销毁链表 2.链表的分类3.双向链表3.1实现双向链表3.1.1尾插3.1.2头插…

【SpringCloud详细教程】-04-服务容错--Sentinel

精品专题&#xff1a; 01.《C语言从不挂科到高绩点》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12753294.html?spm1001.2014.3001.5482 02. 《SpringBoot详细教程》课程详细笔记 https://blog.csdn.net/yueyehuguang/category_12789841.html?spm1001.20…

【Python中while循环】

一、深拷贝、浅拷贝 1、需求 1&#xff09;拷贝原列表产生一个新列表 2&#xff09;想让两个列表完全独立开&#xff08;针对改操作&#xff0c;读的操作不改变&#xff09; 要满足上述的条件&#xff0c;只能使用深拷贝 2、如何拷贝列表 1&#xff09;直接赋值 # 定义一个…

在 Mac(ARM 架构)上安装 JDK 8 环境

文章目录 步骤 1&#xff1a;检查系统版本步骤 2&#xff1a;下载支持 ARM 的 JDK 8步骤 3&#xff1a;安装 JDK步骤 4&#xff1a;配置环境变量步骤 5&#xff1a;验证安装步骤 6&#xff1a;注意事项步骤7&#xff1a;查看Java的安装路径 在 Mac&#xff08;ARM 架构&#xf…

对比C++,Rust在内存安全上做的努力

简介 近年来&#xff0c;越来越多的组织表示&#xff0c;如果新项目在技术选型时需要使用系统级开发语言&#xff0c;那么不要选择使用C/C这种内存不安全的系统语言&#xff0c;推荐使用内存安全的Rust作为替代。 谷歌也声称&#xff0c;Android 的安全漏洞&#xff0c;从 20…