9种单片机常用的软件架构
长文预警,加代码5000多字,写了4个多小时,盘软件架构,这篇文章就够了!
可能很多工程师,工作了很多年,都不会有软件架构的概念。
因为我在做研发工程师的第6年,才开始意识到这个东西,在此之前,都是做一些比较简单的项目,一个main函数干到底,架构复杂了反而是累赘。
后面有幸,接触了稍微复杂点的项目,感觉以前水平Hold不住,然后借着项目需求,学习了很多优秀的代码架构,比如以前同事的,一些模组厂的SDK,还有市面上成熟的系统。
说出来可能有点夸张,一个好项目带来的成长,顶你做几年小项目。
在一个工程师从入门到成为高级工程师,都会经历哪些软件架构?
下面给大家盘点一下,每个都提供了简易的架构模型代码。
1.线性架构
这是最简单的一种程序设计方法,也就是我们在入门时写的,下面是一个使用C语言编写的线性架构示例:
#include <reg51.h> // 包含51系列单片机的寄存器定义// 延时函数,用于产生一定的延迟
void delay(unsigned int count) {unsigned int i;while(count--) {for(i = 0; i < 120; i++) {} // 空循环,用于产生延迟}
}void main() {// 初始设置P1端口为输出模式,用于控制LEDP1 = 0xFF; // 将P1端口设置为高电平,关闭所有LEDwhile(1) { // 无限循环P1 = 0x00; // 将P1端口设置为低电平,点亮所有LEDdelay(500000); // 调用延时函数,延迟一段时间P1 = 0xFF; // 将P1端口设置为高电平,关闭所有LEDdelay(500000); // 再次调用延时函数,延迟相同的时间}
}
2.模块化架构
模块化架构是一种将程序分解为独立模块的设计方法,每个模块执行特定的任务。
这种架构有助于代码的重用、维护和测试。
下面是一个使用C语言编写的模块化架构示例,该程序模拟了一个简单的交通信号灯控制系统。
#include <reg51.h> // 包含51系列单片机的寄存器定义// 定义信号灯的状态
typedef enum {RED_LIGHT,YELLOW_LIGHT,GREEN_LIGHT
} TrafficLightState;// 函数声明
void initializeTrafficLight(void);
void setTrafficLight(TrafficLightState state);
void delay(unsigned int milliseconds);// 信号灯控制主函数
void main(void) {initializeTrafficLight(); // 初始化交通信号灯while(1) {setTrafficLight(RED_LIGHT);delay(5000); // 红灯亮5秒setTrafficLight(YELLOW_LIGHT);delay(2000); // 黄灯亮2秒setTrafficLight(GREEN_LIGHT);delay(5000); // 绿灯亮5秒}
}// 初始化交通信号灯的函数
void initializeTrafficLight(void) {// 这里可以添加初始化代码,比如设置端口方向、默认状态等// 假设P1端口连接了信号灯,初始状态为熄灭(高电平)P1 = 0xFF;
}// 设置交通信号灯状态的函数
void setTrafficLight(TrafficLightState state) {switch(state) {case RED_LIGHT:// 设置红灯亮,其他灯灭P1 = 0b11100000; // 假设低电平有效,这里设置P1.0为低电平,其余为高电平break;case YELLOW_LIGHT:// 设置黄灯亮,其他灯灭P1 = 0b11011000; // 设置P1.1为低电平,其余为高电平break;case GREEN_LIGHT:// 设置绿灯亮,其他灯灭P1 = 0b11000111; // 设置P1.2为低电平,其余为高电平break;default:// 默认为熄灭所有灯P1 = 0xFF;break;}
}// 延时函数,参数是毫秒数
void delay(unsigned int milliseconds) {unsigned int delayCount = 0;while(milliseconds--) {for(delayCount = 0; delayCount < 120; delayCount++) {// 空循环,用于产生延时}}
}
3.层次化架构
层次化架构是一种将系统分解为多个层次的设计方法,每个层次负责不同的功能。
着以下是一个使用C语言编写的层次化架构示例,模拟了一个具有不同权限级别的嵌入式系统。
#include <reg51.h> // 包含51系列单片机的寄存器定义// 定义不同的操作级别
typedef enum {LEVEL_USER,LEVEL_ADMIN,LEVEL_SUPERUSER
} OperationLevel;// 函数声明
void systemInit(void);
void performOperation(OperationLevel level);
void displayMessage(char* message);// 系统初始化后的主循环
void main(void) {systemInit(); // 系统初始化// 模拟用户操作performOperation(LEVEL_USER);// 模拟管理员操作performOperation(LEVEL_ADMIN);// 模拟超级用户操作performOperation(LEVEL_SUPERUSER);while(1) {// 主循环可以是空闲循环或者处理其他低优先级任务}
}// 系统初始化函数
void systemInit(void) {// 初始化系统资源,如设置端口、中断等// 这里省略具体的初始化代码
}// 执行不同级别操作的函数
void performOperation(OperationLevel level) {switch(level) {case LEVEL_USER://用户操作具体代码break;case LEVEL_ADMIN://管理员操作具体代码break;case LEVEL_SUPERUSER://超级用户操作具体代码break;}
}// 显示消息的函数
void displayMessage(char* message) {// 这里省略了实际的显示代码,因为单片机通常没有直接的屏幕输出// 消息可以通过LED闪烁、串口输出或其他方式展示// 假设通过P1端口的LED展示,每个字符对应一个LED闪烁模式// 实际应用中,需要根据硬件设计来实现消息的显示
}
4.事件驱动架构
事件驱动架构是一种编程范式,其中程序的执行流程由事件(如用户输入、传感器变化、定时器到期等)触发。
在单片机开发中,事件驱动架构通常用于响应外部硬件中断或软件中断。
以下是一个使用C语言编写的事件驱动架构示例,模拟了一个基于按键输入的LED控制。
#include <reg51.h> // 包含51系列单片机的寄存器定义// 定义按键和LED的状态
#define KEY_PORT P3 // 假设按键连接在P3端口
#define LED_PORT P2 // 假设LED连接在P2端口// 函数声明
void delay(unsigned int milliseconds);
bit checkKeyPress(void); // 返回按键是否被按下的状态(1表示按下,0表示未按下)// 定时器初始化函数
void timer0Init(void)
{TMOD = 0x01; // 设置定时器模式寄存器,使用模式1(16位定时器)TH0 = 0xFC; // 设置定时器初值,用于产生定时中断TL0 = 0x18;ET0 = 1; // 开启定时器0中断EA = 1; // 开启总中断TR0 = 1; // 启动定时器
}// 定时器中断服务程序
void timer0_ISR() interrupt 1
{// 定时器溢出后自动重新加载初值,无需手动重置// 这里可以放置定时器溢出后需要执行的代码
}// 按键中断服务程序
bit keyPress_ISR(void) interrupt 2 using 1
{if(KEY_PORT != 0xFF) // 检测是否有按键按下{ LED_PORT = ~LED_PORT; // 如果有按键按下,切换LED状态delay(20); // 去抖动延时while(KEY_PORT != 0xFF); // 等待按键释放return 1; // 返回按键已按下}return 0; // 如果没有按键按下,返回0
}// 延时函数,参数是毫秒数
void delay(unsigned int milliseconds) {unsigned int i, j;for(i = 0; i < milliseconds; i++)for(j = 0; j < 1200; j++); // 空循环,用于产生延时
}// 主函数
void main(void)
{timer0Init(); // 初始化定时器LED_PORT = 0xFF; // 初始LED熄灭(假设低电平点亮LED)while(1) {if(checkKeyPress()){ // 检查是否有按键按下事件// 如果有按键按下,这里可以添加额外的处理代码}}
}// 检查按键是否被按下的函数
bit checkKeyPress(void)
{bit keyState = 0;// 模拟按键中断触发,实际应用中需要连接硬件中断if(1) // 假设按键中断触发{ keyState = keyPress_ISR(); // 调用按键中断服务程序}return keyState; // 返回按键状态
}
事实上,真正的事件型驱动架构,是非常复杂的,我职业生涯的巅峰之作,就是用的事件型驱动架构。
5.状态机架构
在单片机开发中,状态机常用于处理复杂的逻辑和事件序列,如用户界面管理、协议解析等。
以下是一个使用C语言编写的有限状态机(FSM)的示例,模拟了一个简单的自动售货机的状态转换。
#include <reg51.h> // 包含51系列单片机的寄存器定义// 定义自动售货机的状态
typedef enum {IDLE,COIN_INSERTED,PRODUCT_SELECTED,DISPENSE,CHANGE_RETURNED
} VendingMachineState;// 定义事件
typedef enum {COIN_EVENT,PRODUCT_EVENT,DISPENSE_EVENT,REFUND_EVENT
} VendingMachineEvent;// 函数声明
void processEvent(VendingMachineEvent event);
void dispenseProduct(void);
void returnChange(void);// 当前状态
VendingMachineState currentState = IDLE;// 主函数
void main(void)
{// 初始化代码(如果有)// ...while(1){// 假设事件由外部触发,这里使用一个模拟事件VendingMachineEvent currentEvent = COIN_EVENT; // 模拟投入硬币事件processEvent(currentEvent); // 处理当前事件}
}// 处理事件的函数
void processEvent(VendingMachineEvent event)
{switch(currentState){case IDLE:if(event == COIN_EVENT){// 如果在空闲状态且检测到硬币投入事件,则转换到硬币投入状态currentState = COIN_INSERTED;}break;case COIN_INSERTED:if(event == PRODUCT_EVENT){// 如果在硬币投入状态且用户选择商品,则请求出货currentState = PRODUCT_SELECTED;}break;case PRODUCT_SELECTED:if(event == DISPENSE_EVENT){dispenseProduct(); // 出货商品currentState = DISPENSE;}break;case DISPENSE:if(event == REFUND_EVENT){returnChange(); // 返回找零currentState = CHANGE_RETURNED;}break;case CHANGE_RETURNED:// 等待下一个循环,返回到IDLE状态currentState = IDLE;break;default:// 如果状态非法,重置为IDLE状态currentState = IDLE;break;}
}// 出货商品的函数
void dispenseProduct(void)
{// 这里添加出货逻辑,例如激活电机推出商品// 假设P1端口连接了出货电机P1 = 0x00; // 激活电机// ... 出货逻辑P1 = 0xFF; // 关闭电机
}// 返回找零的函数
void returnChange(void)
{// 这里添加找零逻辑,例如激活机械臂放置零钱// 假设P2端口连接了找零机械臂P2 = 0x00; // 激活机械臂// ... 找零逻辑P2 = 0xFF; // 关闭机械臂
}
6.面向对象架构
STM32的库,就是一种面向对象的架构。
不过在单片机由于资源限制,OOP并不像在高级语言中那样常见,但是一些基本概念如封装和抽象仍然可以被应用。
虽然C语言本身并不直接支持面向对象编程,但可以通过结构体和函数指针模拟一些面向对象的特性。
下面是一个简化的示例,展示如何在C语言中模拟面向对象的编程风格,以51单片机为背景,创建一个简单的LED类。
#include <reg51.h>// 定义一个LED类
typedef struct {unsigned char state; // LED的状态unsigned char pin; // LED连接的引脚void (*turnOn)(struct LED*); // 点亮LED的方法void (*turnOff)(struct LED*); // 熄灭LED的方法
} LED;// LED类的构造函数
void LED_Init(LED* led, unsigned char pin) {led->state = 0; // 默认状态为熄灭led->pin = pin; // 设置LED连接的引脚
}// 点亮LED的方法
void LED_TurnOn(LED* led) {// 根据引脚状态点亮LEDif(led->pin < 8) {P0 |= (1 << led->pin); // 假设P0.0到P0.7连接了8个LED} else {P1 &= ~(1 << (led->pin - 8)); // 假设P1.0到P1.7连接了另外8个LED}led->state = 1; // 更新状态为点亮
}// 熄灭LED的方法
void LED_TurnOff(LED* led) {// 根据引脚状态熄灭LEDif(led->pin < 8) {P0 &= ~(1 << led->pin); // 熄灭P0上的LED} else {P1 |= (1 << (led->pin - 8)); // 熄灭P1上的LED}led->state = 0; // 更新状态为熄灭
}// 主函数
void main(void) {LED myLed; // 创建一个LED对象LED_Init(&myLed, 3); // 初始化LED对象,连接在P0.3// 给LED对象绑定方法myLed.turnOn = LED_TurnOn;myLed.turnOff = LED_TurnOff;// 使用面向对象的风格控制LEDwhile(1) {myLed.turnOn(&myLed); // 点亮LED// 延时myLed.turnOff(&myLed); // 熄灭LED// 延时}
}
这段代码定义了一个结构体LED,模拟面向对象中的“类。
这个示例仅用于展示如何在C语言中模拟面向对象的风格,并没有使用真正的面向对象编程语言的特性,如继承和多态,不过对于单片机的应用,足以。
7.基于任务的架构
这种我最喜欢用,结构,逻辑清晰,每个任务都能灵活调度。
基于任务的架构是将程序分解为独立的任务,每个任务执行特定的工作。
在单片机开发中,如果没有使用实时操作系统,我们可以通过编写一个简单的轮询调度器来模拟基于任务的架构。
以下是一个使用C语言编写的基于任务的架构的示例,该程序在51单片机上实现。
为了简化,我们将使用一个简单的轮询调度器来在两个任务之间切换:一个是按键扫描任务,另一个是LED闪烁任务。
#include <reg51.h>// 假设P1.0是LED输出
sbit LED = P1^0;// 全局变量,用于记录系统Tick
unsigned int systemTick = 0;// 任务函数声明
void taskLEDBlink(void);
void taskKeyScan(void);// 定时器0中断服务程序,用于产生Tick
void timer0_ISR() interrupt 1 using 1
{// 定时器溢出后自动重新加载初值,无需手动重置systemTick++; // 更新系统Tick计数器
}// 任务调度器,主函数中调用,负责任务轮询
void taskScheduler(void)
{// 检查系统Tick,决定是否执行任务// 例如,如果我们需要每1000个Tick执行一次LED闪烁任务if (systemTick % 1000 == 0) {taskLEDBlink();}// 如果有按键任务,可以类似地检查Tick并执行if (systemTick % 10 == 0) {taskKeyScan();}
}// LED闪烁任务
void taskLEDBlink(void)
{static bit ledState = 0; // 用于记录LED的当前状态ledState = !ledState; // 切换LED状态LED = ledState; // 更新LED硬件状态
}// 按键扫描任务(示例中省略具体实现)
void taskKeyScan(void)
{// 按键扫描逻辑
}// 主函数
void main(void)
{// 初始化LED状态LED = 0;// 定时器0初始化设置TMOD &= 0xF0; // 设置定时器模式寄存器,使用模式1(16位定时器/计数器)TH0 = 0x4C; // 设置定时器初值,产生定时中断(定时周期取决于系统时钟频率)TL0 = 0x00;ET0 = 1; // 允许定时器0中断EA = 1; // 允许中断TR0 = 1; // 启动定时器0while(1) {taskScheduler(); // 调用任务调度器}
}
这里只是举个简单的例子,这个代码示例,比较适合51和stm8这种资源非常少的单片机。
8.代理架构
这个大家或许比较少听到过,但在稍微复杂的项目中,是非常常用的。
在代理架构中,每个代理(Agent)都是一个独立的实体,它封装了特定的决策逻辑和数据,并与其他代理进行交互。
在实际项目中,需要创建多个独立的任务或模块,每个模块负责特定的功能,并通过某种机制(如消息队列、事件触发等)进行通信。
这种方式可以大大提高程序可扩展性和可移植性。
以下是一个LED和按键代理的简化模型。
#include <reg51.h> // 包含51系列单片机的寄存器定义// 假设P3.5是按键输入,P1.0是LED输出
sbit KEY = P3^5;
sbit LED = P1^0;typedef struct
{unsigned char pin; // 代理关联的引脚void (*action)(void); // 代理的行为函数
} Agent;// 按键代理的行为函数声明
void keyAction(void);
// LED代理的行为函数声明
void ledAction(void);// 代理数组,存储所有代理的行为和关联的引脚
Agent agents[] =
{{5, keyAction}, // 按键代理,关联P3.5{0, ledAction} // LED代理,关联P1.0
};// 按键代理的行为函数
void keyAction(void)
{if(KEY == 0) // 检测按键是否被按下{ LED = !LED; // 如果按键被按下,切换LED状态while(KEY == 0); // 等待按键释放}
}// LED代理的行为函数
void ledAction(void)
{static unsigned int toggleCounter = 0;toggleCounter++;if(toggleCounter == 500) // 假设每500个时钟周期切换一次LED{ LED = !LED; // 切换LED状态toggleCounter = 0; // 重置计数器}
}// 主函数
void main(void)
{unsigned char agentIndex;// 主循环while(1) {for(agentIndex = 0; agentIndex < sizeof(agents) / sizeof(agents[0]); agentIndex++) {// 调用每个代理的行为函数(*agents[agentIndex].action)(); // 注意函数指针的调用方式}}
}
9.组件化架构
组件化架构是一种将软件系统分解为独立、可重用组件的方法。
将程序分割成负责特定任务的模块,如LED控制、按键处理、传感器读数等。
每个组件可以独立开发和测试,然后被组合在一起形成完整的系统。
以下是一个简化的组件化架构示例,模拟了一个单片机系统中的LED控制和按键输入处理两个组件。
为了简化,组件间的通信将通过直接函数调用来模拟。
#include <reg51.h> // 包含51系列单片机的寄存器定义// 定义组件结构体
typedef struct
{void (*init)(void); // 组件初始化函数void (*task)(void); // 组件任务函数
} Component;// 假设P3.5是按键输入,P1.0是LED输出
sbit KEY = P3^5;
sbit LED = P1^0;// LED组件
void LED_Init(void)
{LED = 0; // 初始化LED状态为关闭
}void LED_Task(void)
{static unsigned int toggleCounter = 0;toggleCounter++;if (toggleCounter >= 1000) // 假设每1000个时钟周期切换一次LED{ LED = !LED; // 切换LED状态toggleCounter = 0; // 重置计数器}
}// 按键组件
void KEY_Init(void)
{// 按键初始化代码
}void KEY_Task(void)
{if (KEY == 0) // 检测按键是否被按下{ LED = !LED; // 如果按键被按下,切换LED状态while(KEY == 0); // 等待按键释放}
}// 组件数组,存储系统中所有组件的初始化和任务函数
Component components[] =
{{LED_Init, LED_Task},{KEY_Init, KEY_Task}
};// 系统初始化函数,调用所有组件的初始化函数
void System_Init(void)
{unsigned char componentIndex;for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++) {components[componentIndex].init();}
}// 主循环,调用所有组件的任务函数
void main(void)
{System_Init(); // 系统初始化while(1) {unsigned char componentIndex;for (componentIndex = 0; componentIndex < sizeof(components) / sizeof(components[0]); componentIndex++){components[componentIndex].task(); // 调用组件任务}}
}
以上几种,我都整理到单片机入门到高级资料+工具包了,大家可自行在朋友圈找我安排。
当然,以上都是最简易的代码模型,如果想用于实际项目,很多细节还要优化。
后面为了适应更复杂的项目,我基于以上这几种编程思维,重构了代码,使OS变得移植性和扩展性更强,用起来也更灵活。
我在2019年,也系统录制过关于这套架构的教程,粉丝可找我安排。
目前我们无际单片机特训营项目3和6就是采用这种架构,稳的一批。
如果想系统提升编程思维和代码水平,还是得从0到1去学习我们项目,并不是说技术有多难,而是很多思维和实现细节,没有参考,没人指点,靠自己需要摸索很久。
除了以上架构,更复杂的就是RTOS了。
不过一般对于有架构设计能力的工程师来说,更习惯于使用传统的裸机编程方式,这种方式可能更直观且可控。
相关文章:

9种单片机常用的软件架构
长文预警,加代码5000多字,写了4个多小时,盘软件架构,这篇文章就够了! 可能很多工程师,工作了很多年,都不会有软件架构的概念。 因为我在做研发工程师的第6年,才开始意识到这个东西,在…...

PyQt5中重要的概念:信号与槽
PyQt中信号与槽概念定义如下(网络上引用的): 信号(signal)和槽(slot)是Qt的核心机制,也是在PyQt编程中对象之间进行通信的机制。在创建事件循环之后,通过建立信号和槽的…...

MacOS快速安装FFmpeg,并使用FFmpeg转换视频
前言:目前正在接入flv视频流,但是没有一个合适的flv视频流地址。网上提供的flv也都不是H264AAC(一种视频和音频编解码器组合),所以想通过fmpeg来将flv文件转换为H264AAC。 一、MacOS环境 博主的MacOS环境(…...

docker部署nginx并配置https
1.准备SSL证书: 生成私钥:运行以下命令生成一个私钥文件。 生成证书请求(CSR):运行以下命令生成证书请求文件。 生成自签名证书:使用以下命令生成自签名证书。 openssl genrsa -out example.com.key 2048 …...

五一小长假,景区智慧公厕发挥了那些作用?
五一小长假已经过去,在旅途中相信大家非常开心,其中也不乏一些细节让你有了更好的体验,而在您享受美景、畅游风光的同时,或许并未留意到那个角落里,默默为您服务的智慧公厕。是的,它们将成为您旅途中不可或…...

Spring - 9 ( 10000 字 Spring 入门级教程 )
一: MyBatis XML 配置文件 Mybatis 的开发有两种方式: 注解XML 我们已经学习了注解的方式, 接下来我们学习 XML 的方式 MyBatis XML 的方式需要以下两步: 配置数据库连接字符串和 MyBatis写持久层代码 1.1 配置连接字符串和 MyBatis 此步骤需要进…...

shpfile转GeoJSON;控制shp转GeoJSON的精度;如何获取GeoJSON;GeoJSON是什么有什么用;GeoJSON结构详解(带数据示例)
目录 一、GeoJSON是什么 二、GeoJSON的结构组成 2.1、点(Point)数据示例 2.2、线(LineString)数据示例 2.3、面(Polygon)数据示例 2.4、特征(Feature)数据示例 2.5、特征集合&…...
没有强有力的科技支撑,就没有保密工作的高质量发展。新修订的《中华人民共和国保守国家秘密法》在总则中新增保密科技创新有关内容包括()
没有强有力的科技支撑,就没有保密工作的高质量发展。新修订的《中华人民共和国保守国家秘密法》在总则中新增保密科技创新有关内容包括() 点击查看答案内容: A.国家鼓励和支持保密科学技术研究和应用B.提升自主创新能力 C.明确依法保护保密领…...

【快速入门】数据库的增删改查与结构讲解
文章的操作都是基于小皮php study的MySQL5.7.26进行演示 what 数据库是能长期存储在计算机内,有组织的,可共享的大量数据的集合。数据库中的数据按照一定的数据模型存储,具有较小的冗余性,较高的独立性和易扩展性,并为…...

使用AIGC生成软件类图表
文章目录 如何使用 AI 生成软件类图表什么是 MermaidMermaid 的图片如何保存?mermaid.liveDraw.io Mermaid可以画什么图?流程图时序图 / 序列图类图状态图甘特图实体关系图 / ER图 如何使用 AI 生成软件类图表 ChatGPT 大语言模型不能直接生成各类图表。…...

机器学习实践:超市商品购买关联规则分析
第2关:动手实现Apriori算法 任务描述 本关任务:编写 Python 代码实现 Apriori 算法。 相关知识 为了完成本关任务,你需要掌握 Apriori 算法流程。 Apriori 算法流程 Apriori 算法的两个输人参数分别是最小支持度和数据集。该算法首先会生成所…...
自动化图像识别:提高效率和准确性的新途径
自动化图像识别是人工智能领域中的一项关键技术,它通过算法自动解析图像内容,为各种应用提供准确的信息。随着技术的不断发展,自动化图像识别在提高效率和准确性方面展现出新的途径。 一、深度学习技术的应用 深度学习是自动化图像识别领域…...

根据最近拒包项目总结,详细讲解Google最新政策(上)
关于占比最多的移动垃圾软件拒审问题 移动垃圾软件(Mobile Unwanted Software)特征表现1> 具有欺骗性,承诺其无法实现的价值主张。2> 诱骗用户进行安装,或搭载在用户安装的其他程序上。3> 不向用户告知其所有主要功能和重要功能。4> 以非预期方式影响用户的系统…...

【Qt之OpenGL】01创建OpenGL窗口
1.创建子类继承QOpenGLWidget 2.重写三个虚函数 /** 设置OpenGL的资源和状态,最先调用且调用一次* brief initializeGL*/ virtual void initializeGL() override; /** 设置OpenGL视口、投影等,当widget调整大小(或首次显示)时调用* brief resizeGL* param w* para…...

如何判断代理IP质量?
由于各种原因(从匿名性和安全性到绕过地理限制),代理 IP 的使用变得越来越普遍。然而,并非所有代理 IP 都是一样的,区分高质量和低质量的代理 IP 对于确保流畅、安全的浏览体验至关重要。以下是评估代理 IP 质量时需要…...
2023-2024年Web3行业报告合集(精选13份)
Web3行业报告(精选13份) 2023-2024年 来源:2023-2024年Web3行业报告合集(精选13份) 【以下是资料目录】 2023Web3产业发展现状分析及国内外落地实践报告 2023模块化区块链承载Web3.0应用的新模式 2023年AI应用需求…...

CSS中文本样式(详解网页文本样式)
目录 一、Text介绍 1.概念 2.特点 3.用法 4.应用 二、Text语法 1.文本格式 2.文本颜色 3.文本的对齐方式 4.文本修饰 5.文本转换 6.文本缩进 7.color:设置文本颜色。 8.font-family:设置字体系列。 9.font-size:设置字体大小。…...
tensorflow学习笔记(2)线性回归-20240507
通过调用Tensorflow计算梯度下降的函数tf.train.GradientDescentOptimizer来实现优化。 代码如下: #!/usr/bin/env python3 # -*- coding: utf-8 -*- #程序作用: #线性回归:通过调用Tensorflow计算梯度下降的函数tr.train.GradientDescentOptimizer来实现优化。import os …...
【JavaScript】作用域
作用域是指在程序中定义变量的区域,决定了这些变量在哪里可以被访问和使用。JavaScript 中的作用域有全局作用域、函数作用域和块级作用域。 1. 什么是作用域? 作用域是代码中定义变量的区域,它决定了变量的可见性和生命周期。作用域规定了…...

C++程序设计教案
文章目录: 一:软件安装环境 第一种:vc2012 第二种:Dev-C 第三种:小熊猫C 二:语法基础 1.相关 1.1 注释 1.2 换行符 1.3 规范 1.4 关键字 1.5 ASCll码表 1.6 转义字符 2.基本框架 2.1 第一种&…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...

聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

AI书签管理工具开发全记录(十九):嵌入资源处理
1.前言 📝 在上一篇文章中,我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源,方便后续将资源打包到一个可执行文件中。 2.embed介绍 🎯 Go 1.16 引入了革命性的 embed 包,彻底改变了静态资源管理的…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...

给网站添加live2d看板娘
给网站添加live2d看板娘 参考文献: stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下,文章也主…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...