UML之活动图学习

目录

  1. 活动图的本质理解
  2. 活动图 vs 状态图 vs 时序图
  3. 核心元素详解
  4. 并发与同步(Fork/Join)
  5. 判断与循环
  6. 泳道(Swimlanes)
  7. 10个完整实战案例
  8. PlantUML 绘制指南
  9. 最佳实践与避坑指南

一、活动图的本质理解

一句话概念

活动图 = 带并发语义的流程图

特点对比

复制代码
普通流程图:
- 描述步骤顺序
- 简单直观
- 缺少严格语义

UML 活动图:
+ 更加严谨
+ 支持并发表达
+ 有标准的图形元素
+ 可以表达复杂业务逻辑

适用场景

复制代码
✅ 非常适合:
- 初始化流程
- 业务流程
- 算法步骤
- 通信协议
- 数据处理流程

❌ 不太适合:
- 对象间交互(用时序图)
- 状态转换(用状态图)
- 类结构(用类图)

二、活动图 vs 状态图 vs 时序图(必须分清)

三者对比表

对比项 活动图 状态图 时序图
关注点 做事的步骤 所处的状态 对象间交互
触发方式 顺序执行 事件驱动 消息传递
是否强调当前状态
是否适合算法 ✅ 非常适合 ❌ 不合适 ❌ 不合适
是否适合长期运行 ❌ 不适合 ✅ 适合 ❌ 不适合
时间维度 顺序(步骤) 持续(状态) 时间轴(消息)
典型问题 怎么做? 在干什么? 谁做什么?

使用场景判断

复制代码
场景:设备初始化
→ 活动图 ✅(一系列步骤)

场景:设备运行状态
→ 状态图 ✅(待机、运行、错误)

场景:模块间通信
→ 时序图 ✅(谁先谁后)

场景:单个函数算法
→ 活动图 ✅(算法步骤)

场景:协议交互
→ 时序图 ✅(请求响应)或活动图 ✅(协议流程)

记忆口诀

复制代码
算法用活动图,逻辑用状态图
自己怎么做 → 活动图
彼此怎么说 → 时序图
现在干什么 → 状态图

三、核心元素详解(工程够用的 9 个)

元素对照表

UML 元素 含义 图形表示 C/C++ 对应
Initial Node 开始 函数入口
Final Node 结束 return
Action 动作 圆角矩形 函数调用/代码块
Control Flow 控制流 箭头 程序流
Decision 判断 if/else
Merge 合并 分支结束
Fork 并发开始 创建任务/线程
Join 并发结束 同步点
Swimlane 泳道 分隔区域 模块/对象

详细说明

1. Initial Node(初始节点)

图形:实心黑色圆圈 ●

含义:活动的开始点

规则

  • 每个活动图只有一个初始节点
  • 初始节点只有出边,没有入边

代码对应

c 复制代码
void Function(void)  // ← 函数入口就是初始节点
{
    // ...
}

2. Final Node(终止节点)

图形:圆圈内有实心圆 ◎

含义:活动的结束点

类型

  • Activity Final Node:整个活动结束
  • Flow Final Node:某个分支结束

代码对应

c 复制代码
void Function(void)
{
    if (error) {
        return;  // ← 终止节点
    }
    // ...
    return;  // ← 终止节点
}

3. Action(动作)

图形:圆角矩形

复制代码
┌──────────────┐
│  Action Name │
└──────────────┘

含义:执行的一个操作单元

命名规范

c 复制代码
✅ 好的命名(动宾结构):
- Initialize UART
- Read Sensor Data
- Send Message
- Calculate CRC

❌ 不好的命名:
- UART  (只有名词)
- Data  (不清楚做什么)
- Process(太抽象)

代码对应

c 复制代码
// 每个动作对应一个函数调用或代码块
HAL_UART_Init(&huart1);     // ← Action: Initialize UART
ReadSensorData();            // ← Action: Read Sensor Data
SendMessage(data, len);      // ← Action: Send Message

4. Decision(判断节点)

图形:菱形 ◇

复制代码
        ┌───────┐
        │ Check │
        └───┬───┘
            │
         ◇─┴─◇  [condition]
        Yes │ No

含义:基于条件的分支

标注方式

复制代码
[condition] → 条件表达式
[else]      → 否则分支

代码对应

c 复制代码
if (rx_done) {        // ← Decision 节点
    ProcessData();    // ← [Yes] 分支
} else {
    WaitData();       // ← [No] 分支
}

重要规则

  • 一个 Decision 有一个入边
  • 一个 Decision 可以有多个出边
  • 每个出边都要标注条件

5. Merge(合并节点)

图形:菱形 ◇(与 Decision 相同图形,但语义不同)

含义:多个分支合并到一起

代码对应

c 复制代码
if (mode == 1) {
    Process1();
} else {
    Process2();
}
// ← 这里是 Merge 节点,两个分支汇合

ContinueWork();

6. Fork(并发开始)

图形:粗黑线 ▬

复制代码
     │
   ──┼──  Fork
     │
   ┌─┼─┐
   │ │ │

含义:从一个流分成多个并行流

代码对应

c 复制代码
// FreeRTOS 中的并发
xTaskCreate(Task1, ...);  // ← Fork 创建并行任务
xTaskCreate(Task2, ...);  // ← Fork 创建并行任务
xTaskCreate(Task3, ...);  // ← Fork 创建并行任务

// 或者使用中断(硬件并发)
HAL_UART_Receive_IT(&huart1, ...);  // 启动异步接收
HAL_TIM_Base_Start_IT(&htim2);      // 启动定时器

7. Join(并发结束)

图形:粗黑线 ▬

复制代码
   │ │ │
   └─┼─┘
   ──┼──  Join
     │

含义:等待所有并行流完成后才继续

代码对应

c 复制代码
// 等待多个任务完成
xEventGroupWaitBits(
    eventGroup,
    TASK1_BIT | TASK2_BIT | TASK3_BIT,
    pdTRUE,
    pdTRUE,  // ← 等待所有位都置位(Join语义)
    portMAX_DELAY
);

// 或者信号量计数
for (int i = 0; i < 3; i++) {
    xSemaphoreTake(sem, portMAX_DELAY);  // 等待3个任务
}

8. Swimlane(泳道)

图形:垂直或水平的分隔区域

复制代码
┌─────────────┬─────────────┬─────────────┐
│   User      │   System    │   Database  │
├─────────────┼─────────────┼─────────────┤
│             │             │             │
│  [Action1]  │             │             │
│     │       │             │             │
│     │       │  [Action2]  │             │
│     │       │     │       │             │
│     │       │     │       │  [Action3]  │
│             │             │             │
└─────────────┴─────────────┴─────────────┘

含义:将活动按职责/模块分组

适用场景

  • 多个模块协作
  • 业务流程涉及多个角色
  • 系统分层设计

代码对应

c 复制代码
// 每个泳道对应一个模块或层

// User Layer
void User_ClickButton(void);

// System Layer  
void System_ProcessRequest(void);

// Database Layer
void DB_Query(void);

四、并发与同步(Fork/Join)

为什么并发是活动图的核心价值?

复制代码
这是活动图和普通流程图的本质区别!

普通流程图:只能表达串行流程
活动图:可以精确表达并行和同步

并发场景 1:系统启动(多任务并行)

UML 活动图
复制代码
          ●  (Start)
          │
          │
    ┌─────────────┐
    │System Init  │
    └──────┬──────┘
           │
         ──┼──  Fork(并发开始)
           │
    ┌──────┼──────┬──────────┐
    │      │      │          │
    ▼      ▼      ▼          ▼
┌────────┐ ┌────┐ ┌────┐ ┌────────┐
│UART RX │ │Key │ │LED │ │Sensor  │
│Task    │ │Scan│ │Blink│ │Task    │
└────────┘ └────┘ └────┘ └────────┘
    │      │      │          │
    └──────┼──────┴──────────┘
           │
         ──┼──  Join(同步等待)
           │
          ◎  (End)
FreeRTOS 代码实现
c 复制代码
void main(void)
{
    // 系统初始化
    SystemInit();
    
    // Fork - 创建并行任务
    xTaskCreate(UART_RX_Task,  "UART",  128, NULL, 1, NULL);
    xTaskCreate(Key_Scan_Task, "Key",   128, NULL, 1, NULL);
    xTaskCreate(LED_Blink_Task,"LED",   128, NULL, 1, NULL);
    xTaskCreate(Sensor_Task,   "Sensor",128, NULL, 1, NULL);
    
    // 启动调度器
    vTaskStartScheduler();  // 任务开始并行运行
    
    // Join - 这里不会到达(调度器永不返回)
}

void UART_RX_Task(void *param)
{
    while (1) {
        // UART 接收处理
        vTaskDelay(10);
    }
}

void Key_Scan_Task(void *param)
{
    while (1) {
        // 按键扫描
        vTaskDelay(20);
    }
}

void LED_Blink_Task(void *param)
{
    while (1) {
        // LED 闪烁
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
        vTaskDelay(500);
    }
}

void Sensor_Task(void *param)
{
    while (1) {
        // 传感器读取
        ReadSensorData();
        vTaskDelay(100);
    }
}

并发场景 2:数据采集与处理(生产者-消费者)

UML 活动图
复制代码
          ●  (Start)
          │
         ──┼──  Fork
          │
    ┌─────┴─────┐
    │           │
    ▼           ▼
┌────────┐  ┌────────┐
│Acquire │  │Process │
│ Data   │  │ Data   │
│        │  │        │
│[loop]  │  │[loop]  │
└────────┘  └────────┘
    │           │
    └─────┬─────┘
          │
         ──┼──  Join
          │
          ◎  (End)
代码实现(队列同步)
c 复制代码
QueueHandle_t dataQueue;

void main(void)
{
    // 创建队列
    dataQueue = xQueueCreate(10, sizeof(SensorData_t));
    
    // Fork - 并行任务
    xTaskCreate(DataAcquireTask, "Acquire", 256, NULL, 2, NULL);
    xTaskCreate(DataProcessTask,  "Process", 256, NULL, 1, NULL);
    
    vTaskStartScheduler();
}

// 生产者任务
void DataAcquireTask(void *param)
{
    SensorData_t data;
    
    while (1) {
        // 采集数据
        data.temp = ReadTemperature();
        data.humi = ReadHumidity();
        data.timestamp = GetTick();
        
        // 发送到队列
        xQueueSend(dataQueue, &data, portMAX_DELAY);
        
        vTaskDelay(100);
    }
}

// 消费者任务
void DataProcessTask(void *param)
{
    SensorData_t data;
    
    while (1) {
        // 从队列接收(等待数据)
        if (xQueueReceive(dataQueue, &data, portMAX_DELAY) == pdTRUE) {
            // 处理数据
            FilterData(&data);
            SaveToFlash(&data);
            SendToCloud(&data);
        }
    }
}

并发场景 3:事件同步(Join 的实际应用)

需求描述
  • 启动三个初始化任务
  • 等待所有任务完成后才继续
UML 活动图
复制代码
          ●  (Start)
          │
         ──┼──  Fork
          │
    ┌─────┼─────┬─────┐
    │     │     │     │
    ▼     ▼     ▼     ▼
┌────┐ ┌────┐ ┌────┐ ┌────┐
│Init│ │Init│ │Init│ │Init│
│GPIO│ │UART│ │SPI │ │I2C │
└────┘ └────┘ └────┘ └────┘
    │     │     │     │
    └─────┼─────┴─────┘
          │
         ──┼──  Join(等待所有完成)
          │
    ┌─────────┐
    │Start App│
    └─────────┘
          │
          ◎
代码实现(EventGroup 同步)
c 复制代码
#define GPIO_INIT_BIT  (1 << 0)
#define UART_INIT_BIT  (1 << 1)
#define SPI_INIT_BIT   (1 << 2)
#define I2C_INIT_BIT   (1 << 3)

EventGroupHandle_t initEventGroup;

void main(void)
{
    // 创建事件组
    initEventGroup = xEventGroupCreate();
    
    // Fork - 并行初始化
    xTaskCreate(GPIO_InitTask, "GPIO", 128, NULL, 1, NULL);
    xTaskCreate(UART_InitTask, "UART", 128, NULL, 1, NULL);
    xTaskCreate(SPI_InitTask,  "SPI",  128, NULL, 1, NULL);
    xTaskCreate(I2C_InitTask,  "I2C",  128, NULL, 1, NULL);
    
    // Join - 等待所有初始化完成
    EventBits_t bits = xEventGroupWaitBits(
        initEventGroup,
        GPIO_INIT_BIT | UART_INIT_BIT | SPI_INIT_BIT | I2C_INIT_BIT,
        pdFALSE,
        pdTRUE,  // 等待所有位都置位
        portMAX_DELAY
    );
    
    if (bits == (GPIO_INIT_BIT | UART_INIT_BIT | SPI_INIT_BIT | I2C_INIT_BIT)) {
        printf("All peripherals initialized!\n");
        StartApplication();
    }
}

void GPIO_InitTask(void *param)
{
    HAL_GPIO_Init();
    xEventGroupSetBits(initEventGroup, GPIO_INIT_BIT);
    vTaskDelete(NULL);  // 任务完成,删除自己
}

void UART_InitTask(void *param)
{
    HAL_UART_Init(&huart1);
    xEventGroupSetBits(initEventGroup, UART_INIT_BIT);
    vTaskDelete(NULL);
}

// SPI_InitTask 和 I2C_InitTask 类似

五、判断与循环

Decision(判断)节点详解

基本用法
复制代码
        ┌──────────┐
        │Read Data │
        └────┬─────┘
             │
          ◇──┴──◇  [valid?]
         Yes │ No
             │
       ┌─────┴─────┐
       │           │
       ▼           ▼
  ┌────────┐  ┌────────┐
  │Process │  │Discard │
  └────────┘  └────────┘
       │           │
       └─────┬─────┘
             │
          ◇──┴──◇  Merge
             │
        ┌────┴────┐
        │Continue │
        └─────────┘
代码对应
c 复制代码
void HandleData(void)
{
    uint8_t data = ReadData();
    
    if (IsValid(data)) {  // Decision
        ProcessData(data);
    } else {
        DiscardData();
    }
    
    // Merge - 两个分支汇合
    Continue();
}

多路分支

UML 表示
复制代码
        ┌──────────┐
        │Get State │
        └────┬─────┘
             │
          ◇──┴──◇
             │
    ┌────────┼────────┬────────┐
    │        │        │        │
[idle]  [running] [error] [shutdown]
    │        │        │        │
    ▼        ▼        ▼        ▼
┌────┐   ┌────┐   ┌────┐   ┌────┐
│Idle│   │Run │   │Fix │   │Stop│
└────┘   └────┘   └────┘   └────┘
    │        │        │        │
    └────────┼────────┴────────┘
             │
          ◇──┴──◇  Merge
             │
代码对应
c 复制代码
switch (GetState()) {  // Decision
    case STATE_IDLE:
        HandleIdle();
        break;
    case STATE_RUNNING:
        HandleRunning();
        break;
    case STATE_ERROR:
        HandleError();
        break;
    case STATE_SHUTDOWN:
        HandleShutdown();
        break;
}

// Merge - 继续后续流程

循环结构

Loop(循环)表示方式

方式 1:使用决策节点回退

复制代码
        ┌──────────┐
    ┌──>│Read Byte │
    │   └────┬─────┘
    │        │
    │     ◇──┴──◇  [complete?]
    │    No  │ Yes
    │        │
    └────────┘
             │
        ┌────┴────┐
        │Process  │
        └─────────┘

代码对应

c 复制代码
while (!IsComplete()) {  // Loop 循环条件
    ReadByte();
}
ProcessData();

方式 2:使用循环框标注

复制代码
┌─ loop [count < 10] ────────────┐
│                                │
│  ┌──────────┐                  │
│  │Read Byte │                  │
│  └────┬─────┘                  │
│       │                        │
│  ┌────┴────┐                   │
│  │Store    │                   │
│  └─────────┘                   │
│                                │
└────────────────────────────────┘
        │
   ┌────┴────┐
   │Process  │
   └─────────┘

代码对应

c 复制代码
for (int i = 0; i < 10; i++) {  // Loop
    ReadByte();
    Store();
}
ProcessData();

六、泳道(Swimlanes)

什么是泳道?

定义:按职责或组织单位划分的活动区域

类比

  • 游泳池的泳道 → 每个人在自己的道上游
  • 活动图的泳道 → 每个角色/模块在自己的区域内工作

泳道的价值

复制代码
✅ 明确职责边界
✅ 展示跨模块交互
✅ 便于理解复杂流程
✅ 适合业务流程建模

示例 1:订单处理流程(从图片1)

UML 活动图
复制代码
┌──────────────┬─────────────────┬───────────────┐
│   Customer   │    System       │   Payment     │
├──────────────┼─────────────────┼───────────────┤
│              │                 │               │
│              │   ● Start       │               │
│              │   │             │               │
│              │   ▼             │               │
│              │ ┌─────────────┐ │               │
│              │ │Receive Order│ │               │
│              │ └──────┬──────┘ │               │
│              │        │        │               │
│              │      ──┼──  Fork│               │
│              │        │        │               │
│              │   ┌────┴────┐   │               │
│              │   │         │   │               │
│              │   ▼         ▼   │               │
│              │ ┌────┐  ┌────┐  │               │
│              │ │Fill│  │Send│  │               │
│              │ │Order  │Inv.│  │               │
│              │ └──┬─┘  └──┬─┘  │               │
│              │    │  ◇   │     │               │
│              │    │[rush]│     │               │
│              │ ┌──┴───┐  │     │               │
│              │ │Overnight│     │               │
│              │ │ or     │     │               │
│              │ │Regular │     │               │
│              │ └──┬───┘  │     │               │
│              │    │  ◇   │     │               │
│              │    └──┼───┘     │               │
│              │     ──┼──  Join │               │
│              │       │         │               │
│              │       ▼         │               │
│              │                 │┌──────────┐   │
│              │                 ││Receive   │   │
│              │                 ││Payment   │   │
│              │                 │└────┬─────┘   │
│              │       │         │    │          │
│              │     ──┼──  Join │    │          │
│              │       │         │    │          │
│              │       ▼         │    │          │
│              │      ◎ End      │    │          │
│              │                 │    │          │
└──────────────┴─────────────────┴────┴──────────┘
PlantUML 代码
plantuml 复制代码
@startuml OrderProcessing

|Customer|
start

|System|
:Receive Order;

fork
  :Fill Order;
  if (rush order?) then (yes)
    :Arrange Overnight Delivery;
  else (no)
    :Arrange Regular Delivery;
  endif
fork again
  :Send Invoice;
end fork

|Payment|
:Receive Payment;

|System|
end

@enduml

示例 2:在线购物流程(从图片2)

流程描述
复制代码
泳道:
- Customer(客户)
- System(系统)
- Payment System(支付系统)
- Database(数据库)

流程:
1. 客户搜索商品
2. 系统查询数据库
3. 客户浏览商品
4. 客户添加到购物车
5. 客户结账
6. 支付系统处理支付
7. 系统更新订单
8. 数据库保存记录
PlantUML 代码
plantuml 复制代码
@startuml OnlineShopping

|Customer|
start
:Search Items;

|System|
:Query Database;

|Database|
:Return Results;

|Customer|
:Browse Items;
repeat
  :View Item Details;
  if (Add to Cart?) then (yes)
    |System|
    :Add to Shopping Cart;
  else (no)
  endif
  |Customer|
repeat while (Continue Shopping?) is (yes)
->no;

:Proceed to Checkout;

|System|
:Calculate Total;

|Customer|
:Enter Payment Info;

|Payment System|
:Process Payment;

if (Payment Success?) then (yes)
  |System|
  :Create Order;
  
  |Database|
  :Save Order;
  
  |System|
  :Send Confirmation;
  
  |Customer|
  :Receive Confirmation;
  stop
else (no)
  |Customer|
  :Payment Failed;
  stop
endif

@enduml

七、完整实战案例

案例 1:STM32 系统初始化流程

场景描述

STM32 上电后的完整初始化序列

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Reset Handler│
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │SystemInit   │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Clock Config │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │GPIO Init    │
    └──────┬──────┘
           │
         ──┼──  Fork(并行初始化)
           │
    ┌──────┼──────┬──────────┐
    │      │      │          │
    ▼      ▼      ▼          ▼
┌────┐  ┌────┐ ┌────┐    ┌────┐
│UART│  │SPI │ │I2C │    │ADC │
│Init│  │Init│ │Init│    │Init│
└────┘  └────┘ └────┘    └────┘
    │      │      │          │
    └──────┼──────┴──────────┘
           │
         ──┼──  Join
           │
    ┌──────┴──────┐
    │RTOS Init    │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Create Tasks │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Start Scheduler
    └──────┬──────┘
           │
          ◎ End
代码实现
c 复制代码
int main(void)
{
    // Reset Handler
    HAL_Init();
    
    // SystemInit
    SystemInit();
    
    // Clock Config
    SystemClock_Config();
    
    // GPIO Init
    MX_GPIO_Init();
    
    // Fork - 并行初始化(实际上是顺序,但逻辑上独立)
    MX_UART_Init();
    MX_SPI_Init();
    MX_I2C_Init();
    MX_ADC_Init();
    
    // Join - 初始化完成
    
    // RTOS Init
    osKernelInitialize();
    
    // Create Tasks
    CreateAllTasks();
    
    // Start Scheduler
    osKernelStart();
    
    // 不会到达这里
    while (1);
}

案例 2:UART 数据接收流程

场景描述

接收完整的数据帧,格式:[STX][LEN][DATA...][CRC][ETX]

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Wait for STX │◄────┐
    └──────┬──────┘     │
           │            │
        ◇──┴──◇         │
      [rx==STX?]        │
        Yes│ No         │
           │            │
           ├────────────┘
           │
    ┌──────┴──────┐
    │Receive LEN  │
    └──────┬──────┘
           │
        ◇──┴──◇
      [len valid?]
        Yes│ No
           │  └─────────┐
           │            │
    ┌─ loop [count<len] ┴────┐
    │                        │
    │  ┌──────────┐          │
    │  │Receive   │          │
    │  │Data Byte │          │
    │  └──────────┘          │
    │                        │
    └────────────────────────┘
           │
    ┌──────┴──────┐
    │Receive CRC  │
    └──────┬──────┘
           │
        ◇──┴──◇
      [CRC OK?]
        Yes│ No
           │  └─────────────┐
           │                │
    ┌──────┴──────┐         │
    │Receive ETX  │         │
    └──────┬──────┘         │
           │                │
        ◇──┴──◇             │
      [rx==ETX?]            │
        Yes│ No             │
           │  └─────────────┤
    ┌──────┴──────┐         │
    │Process Frame│         │
    └──────┬──────┘         │
           │                │
          ◎ End             │
           │                │
           └────────────────┘
        [Error - Reset]
代码实现
c 复制代码
typedef enum {
    RX_STATE_WAIT_STX,
    RX_STATE_GET_LEN,
    RX_STATE_GET_DATA,
    RX_STATE_GET_CRC,
    RX_STATE_GET_ETX,
    RX_STATE_PROCESS,
    RX_STATE_ERROR
} RxState_t;

#define STX 0x02
#define ETX 0x03

void UART_ReceiveFrame(void)
{
    static RxState_t state = RX_STATE_WAIT_STX;
    static uint8_t buffer[256];
    static uint8_t length = 0;
    static uint8_t index = 0;
    static uint8_t crc = 0;
    
    uint8_t rxByte;
    
    while (UART_Available()) {
        rxByte = UART_ReadByte();
        
        switch (state) {
            case RX_STATE_WAIT_STX:
                if (rxByte == STX) {  // Decision
                    state = RX_STATE_GET_LEN;
                    index = 0;
                    crc = 0;
                }
                // else stay in this state
                break;
                
            case RX_STATE_GET_LEN:
                length = rxByte;
                if (length > 0 && length <= 256) {  // Decision
                    crc += length;
                    state = RX_STATE_GET_DATA;
                } else {
                    state = RX_STATE_ERROR;
                }
                break;
                
            case RX_STATE_GET_DATA:
                buffer[index++] = rxByte;
                crc += rxByte;
                
                if (index >= length) {  // Loop condition
                    state = RX_STATE_GET_CRC;
                }
                break;
                
            case RX_STATE_GET_CRC:
                if (rxByte == crc) {  // Decision
                    state = RX_STATE_GET_ETX;
                } else {
                    state = RX_STATE_ERROR;
                }
                break;
                
            case RX_STATE_GET_ETX:
                if (rxByte == ETX) {  // Decision
                    state = RX_STATE_PROCESS;
                } else {
                    state = RX_STATE_ERROR;
                }
                break;
                
            case RX_STATE_PROCESS:
                ProcessFrame(buffer, length);
                state = RX_STATE_WAIT_STX;  // Reset
                break;
                
            case RX_STATE_ERROR:
                // Log error
                state = RX_STATE_WAIT_STX;  // Reset
                break;
        }
    }
}

案例 3:Modbus 主机请求流程

场景描述

Modbus 主机发送请求并等待响应,支持超时重试

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Build Request│
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Add CRC      │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Send Request │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Start Timer  │
    └──────┬──────┘
           │
    ┌─────────────────┐
    │Wait for Response│◄───┐
    └──────┬──────────┘    │
           │               │
        ◇──┴──◇            │
      [response received?] │
        Yes│ No            │
           │  │            │
           │  ◇────────◇   │
           │  [timeout?]   │
           │   Yes│ No     │
           │      │  └─────┘
           │      │
           │      ◇────────◇
           │      [retry < 3?]
           │       Yes│ No
           │          │  │
           │          │  └─────┐
    ┌──────┴──────┐   │        │
    │Verify CRC   │   │        │
    └──────┬──────┘   │        │
           │          │        │
        ◇──┴──◇       │        │
      [CRC OK?]       │        │
        Yes│ No       │        │
           │  └───────┤        │
    ┌──────┴──────┐  │        │
    │Parse Response│  │        │
    └──────┬──────┘  │        │
           │         │        │
          ◎ Success  │        │
                     │        │
                ◎ Retry      ◎ Timeout
代码实现
c 复制代码
typedef enum {
    MODBUS_SUCCESS,
    MODBUS_TIMEOUT,
    MODBUS_CRC_ERROR,
    MODBUS_RETRY
} ModbusResult_t;

ModbusResult_t Modbus_SendRequest(uint8_t slaveAddr, uint8_t function, 
                                   uint16_t addr, uint16_t count)
{
    uint8_t request[8];
    uint8_t response[256];
    uint16_t crc;
    int retry = 0;
    
    // Build Request
    request[0] = slaveAddr;
    request[1] = function;
    request[2] = (addr >> 8) & 0xFF;
    request[3] = addr & 0xFF;
    request[4] = (count >> 8) & 0xFF;
    request[5] = count & 0xFF;
    
    // Add CRC
    crc = CalculateCRC(request, 6);
    request[6] = crc & 0xFF;
    request[7] = (crc >> 8) & 0xFF;
    
    while (retry < 3) {  // Retry loop
        // Send Request
        UART_Transmit(request, 8);
        
        // Start Timer
        uint32_t startTime = HAL_GetTick();
        uint32_t timeout = 1000;  // 1 second
        
        // Wait for Response
        while (HAL_GetTick() - startTime < timeout) {
            if (UART_DataAvailable()) {  // Decision: response received?
                int len = UART_Receive(response, sizeof(response));
                
                // Verify CRC
                uint16_t rxCRC = (response[len-1] << 8) | response[len-2];
                uint16_t calcCRC = CalculateCRC(response, len - 2);
                
                if (rxCRC == calcCRC) {  // Decision: CRC OK?
                    // Parse Response
                    ParseResponse(response, len);
                    return MODBUS_SUCCESS;
                } else {
                    return MODBUS_CRC_ERROR;
                }
            }
        }
        
        // Timeout - retry
        retry++;
    }
    
    return MODBUS_TIMEOUT;
}

案例 4:文件读写流程

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Open File    │
    └──────┬──────┘
           │
        ◇──┴──◇
      [success?]
        Yes│ No
           │  └────────┐
    ┌──────┴──────┐   │
    │Read Data    │   │
    └──────┬──────┘   │
           │          │
    ┌──────┴──────┐   │
    │Process Data │   │
    └──────┬──────┘   │
           │          │
    ┌──────┴──────┐   │
    │Write Result │   │
    └──────┬──────┘   │
           │          │
    ┌──────┴──────┐   │
    │Close File   │   │
    └──────┬──────┘   │
           │          │
          ◎ Success   │
                      │
                 ◎ Error
代码实现
c 复制代码
bool ProcessFile(const char *inputPath, const char *outputPath)
{
    FILE *fin = NULL;
    FILE *fout = NULL;
    bool success = false;
    
    // Open File
    fin = fopen(inputPath, "rb");
    if (fin == NULL) {  // Decision
        goto error;  // Error path
    }
    
    fout = fopen(outputPath, "wb");
    if (fout == NULL) {  // Decision
        goto error;  // Error path
    }
    
    // Read Data
    uint8_t buffer[1024];
    size_t bytesRead;
    
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), fin)) > 0) {
        // Process Data
        ProcessBuffer(buffer, bytesRead);
        
        // Write Result
        fwrite(buffer, 1, bytesRead, fout);
    }
    
    success = true;
    
error:
    // Close File
    if (fin != NULL) {
        fclose(fin);
    }
    if (fout != NULL) {
        fclose(fout);
    }
    
    return success;
}

案例 5:ADC 多通道采样

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Config ADC   │
    └──────┬──────┘
           │
         ──┼──  Fork(并发采样)
           │
    ┌──────┼──────┬──────────┐
    │      │      │          │
    ▼      ▼      ▼          ▼
┌────┐  ┌────┐ ┌────┐    ┌────┐
│CH0 │  │CH1 │ │CH2 │    │CH3 │
│Read│  │Read│ │Read│    │Read│
└────┘  └────┘ └────┘    └────┘
    │      │      │          │
    └──────┼──────┴──────────┘
           │
         ──┼──  Join
           │
    ┌─────────────┐
    │Convert to   │
    │Physical Unit│
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Apply Filter │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Store Result │
    └──────┬──────┘
           │
          ◎ End
代码实现
c 复制代码
typedef struct {
    uint16_t ch0_raw;
    uint16_t ch1_raw;
    uint16_t ch2_raw;
    uint16_t ch3_raw;
    
    float ch0_voltage;
    float ch1_voltage;
    float ch2_voltage;
    float ch3_voltage;
} ADC_Result_t;

ADC_Result_t ADC_ReadAllChannels(void)
{
    ADC_Result_t result;
    
    // Config ADC
    HAL_ADC_Start(&hadc1);
    
    // Fork - 并发采样(DMA模式)
    // 实际上 DMA 自动按顺序采样多通道
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)&result, 4);
    
    // Wait for completion
    while (HAL_ADC_PollForConversion(&hadc1, 100) != HAL_OK);
    
    // Join - 采样完成
    
    // Convert to Physical Unit
    result.ch0_voltage = result.ch0_raw * 3.3f / 4096.0f;
    result.ch1_voltage = result.ch1_raw * 3.3f / 4096.0f;
    result.ch2_voltage = result.ch2_raw * 3.3f / 4096.0f;
    result.ch3_voltage = result.ch3_raw * 3.3f / 4096.0f;
    
    // Apply Filter
    result.ch0_voltage = LowPassFilter(result.ch0_voltage, 0);
    result.ch1_voltage = LowPassFilter(result.ch1_voltage, 1);
    result.ch2_voltage = LowPassFilter(result.ch2_voltage, 2);
    result.ch3_voltage = LowPassFilter(result.ch3_voltage, 3);
    
    // Store Result
    SaveADC_Result(&result);
    
    return result;
}

案例 6:HTTP 请求处理流程(Web 服务器)

UML 活动图
复制代码
┌─────────────┬────────────────┬──────────────┐
│   Client    │    Server      │   Database   │
├─────────────┼────────────────┼──────────────┤
│             │                │              │
│ ● Start     │                │              │
│     │       │                │              │
│ ┌───┴───┐   │                │              │
│ │Send   │   │                │              │
│ │Request│───┼───────────────>│              │
│ └───────┘   │                │              │
│             │ ┌────────────┐ │              │
│             │ │Parse       │ │              │
│             │ │Request     │ │              │
│             │ └─────┬──────┘ │              │
│             │       │        │              │
│             │    ◇──┴──◇     │              │
│             │    [method?]   │              │
│             │  GET│ POST│    │              │
│             │     │     │    │              │
│             │   ┌─┴─┐ ┌─┴─┐  │              │
│             │   │GET│ │POST│ │              │
│             │   └─┬─┘ └─┬─┘  │              │
│             │     └───┬─┘    │              │
│             │         │      │              │
│             │    ◇────┴────◇ │              │
│             │    [auth OK?]  │              │
│             │     Yes│ No    │              │
│             │        │  └───────────────┐   │
│             │        │                  │   │
│             │   ┌────┴────┐             │   │
│             │   │Query    │             │   │
│             │   │Database │────────────>│   │
│             │   └────┬────┘             │   │
│             │        │  ┌──────────┐    │   │
│             │        │<─│Return    │    │   │
│             │        │  │Result    │    │   │
│             │        │  └──────────┘    │   │
│             │   ┌────┴────┐             │   │
│             │   │Format   │             │   │
│             │   │Response │             │   │
│             │   └────┬────┘             │   │
│             │        │                  │   │
│             │        └──┬───────────────┘   │
│             │           │                   │
│             │    ◇──────┴──────◇            │
│             │    [200 or 401]               │
│             │           │                   │
│ ┌───────┐   │   ┌───────┴───────┐           │
│ │Receive│<──┼───│Send Response  │           │
│ │Response   │   └───────────────┘           │
│ └───────┘   │                               │
│     │       │                               │
│    ◎ End    │                               │
│             │                               │
└─────────────┴───────────────────────────────┘

案例 7:智能家居场景控制

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Detect       │
    │"Coming Home"│
    └──────┬──────┘
           │
         ──┼──  Fork(并行控制)
           │
    ┌──────┼──────┬──────────┬──────────┐
    │      │      │          │          │
    ▼      ▼      ▼          ▼          ▼
┌────┐  ┌────┐ ┌────┐    ┌────┐    ┌────┐
│Turn│  │Turn│ │Turn │    │Set │    │Start│
│On  │  │On  │ │On   │    │AC  │    │Music│
│Lights  │TV  │ │Curtains  │22°C│    │     │
└────┘  └────┘ └────┘    └────┘    └────┘
    │      │      │          │          │
    └──────┼──────┴──────────┴──────────┘
           │
         ──┼──  Join
           │
    ┌──────┴──────┐
    │Send         │
    │Notification │
    └──────┬──────┘
           │
          ◎ End
代码实现
c 复制代码
void SmartHome_ComingHomeScene(void)
{
    EventGroupHandle_t sceneGroup;
    sceneGroup = xEventGroupCreate();
    
    // Fork - 并行控制
    xTaskCreate(TurnOnLights,   "Lights",  128, sceneGroup, 1, NULL);
    xTaskCreate(TurnOnTV,       "TV",      128, sceneGroup, 1, NULL);
    xTaskCreate(OpenCurtains,   "Curtains",128, sceneGroup, 1, NULL);
    xTaskCreate(SetAC_22C,      "AC",      128, sceneGroup, 1, NULL);
    xTaskCreate(StartMusic,     "Music",   128, sceneGroup, 1, NULL);
    
    // Join - 等待所有动作完成
    xEventGroupWaitBits(
        sceneGroup,
        LIGHTS_BIT | TV_BIT | CURTAINS_BIT | AC_BIT | MUSIC_BIT,
        pdTRUE,
        pdTRUE,
        pdMS_TO_TICKS(5000)
    );
    
    // Send Notification
    SendNotification("Welcome home! Scene activated.");
    
    vEventGroupDelete(sceneGroup);
}

void TurnOnLights(void *param)
{
    EventGroupHandle_t group = (EventGroupHandle_t)param;
    
    SmartLight_TurnOn(ALL_ROOMS);
    xEventGroupSetBits(group, LIGHTS_BIT);
    
    vTaskDelete(NULL);
}

// 其他任务类似实现...

案例 8:固件升级流程(OTA)

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Check Update │
    └──────┬──────┘
           │
        ◇──┴──◇
      [available?]
        Yes│ No
           │  └─────────┐
    ┌──────┴──────┐    │
    │Download     │    │
    │Firmware     │    │
    └──────┬──────┘    │
           │           │
        ◇──┴──◇        │
      [complete?]      │
        Yes│ No        │
           │  └────────┤
    ┌──────┴──────┐   │
    │Verify       │   │
    │Signature    │   │
    └──────┬──────┘   │
           │          │
        ◇──┴──◇       │
      [valid?]        │
        Yes│ No       │
           │  └───────┤
    ┌──────┴──────┐  │
    │Backup       │  │
    │Current FW   │  │
    └──────┬──────┘  │
           │         │
    ┌──────┴──────┐  │
    │Flash New FW │  │
    └──────┬──────┘  │
           │         │
        ◇──┴──◇      │
      [success?]     │
        Yes│ No      │
           │  └──────┤
    ┌──────┴──────┐ │
    │Reboot       │ │
    └──────┬──────┘ │
           │        │
          ◎ Success │
                    │
               ◎ Failed
代码实现
c 复制代码
typedef enum {
    OTA_SUCCESS,
    OTA_NO_UPDATE,
    OTA_DOWNLOAD_FAILED,
    OTA_VERIFY_FAILED,
    OTA_FLASH_FAILED
} OTA_Result_t;

OTA_Result_t OTA_UpdateFirmware(void)
{
    FirmwareInfo_t fwInfo;
    
    // Check Update
    if (!OTA_CheckUpdate(&fwInfo)) {  // Decision
        return OTA_NO_UPDATE;
    }
    
    // Download Firmware
    if (!OTA_Download(fwInfo.url, fwInfo.size)) {  // Decision
        return OTA_DOWNLOAD_FAILED;
    }
    
    // Verify Signature
    if (!OTA_VerifySignature()) {  // Decision
        return OTA_VERIFY_FAILED;
    }
    
    // Backup Current FW
    OTA_BackupCurrentFirmware();
    
    // Flash New FW
    if (!OTA_FlashFirmware()) {  // Decision
        // Restore backup
        OTA_RestoreBackup();
        return OTA_FLASH_FAILED;
    }
    
    // Reboot
    printf("Firmware updated successfully. Rebooting...\n");
    HAL_Delay(1000);
    NVIC_SystemReset();
    
    return OTA_SUCCESS;
}

案例 9:数据加密流程

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Read Plain   │
    │Text         │
    └──────┬──────┘
           │
        ◇──┴──◇
      [data valid?]
        Yes│ No
           │  └────────┐
    ┌──────┴──────┐   │
    │Generate IV  │   │
    └──────┬──────┘   │
           │          │
    ┌──────┴──────┐   │
    │Load Key     │   │
    └──────┬──────┘   │
           │          │
    ┌─ loop [each block] ┬───┐
    │                    │   │
    │  ┌──────────┐      │   │
    │  │Encrypt   │      │   │
    │  │Block     │      │   │
    │  └──────────┘      │   │
    │                    │   │
    └────────────────────┴───┘
           │
    ┌──────┴──────┐
    │Add HMAC     │
    └──────┬──────┘
           │
    ┌──────┴──────┐
    │Write        │
    │Ciphertext   │
    └──────┬──────┘
           │
          ◎ End    │
                   │
              ◎ Error

案例 10:数据采集与上报(完整流程)

UML 活动图
复制代码
          ● Start
          │
    ┌─────────────┐
    │Initialize   │
    │Sensors      │
    └──────┬──────┘
           │
         ──┼──  Fork
           │
    ┌──────┴──────────────────┐
    │                          │
    ▼                          ▼
┌─────────────────┐      ┌─────────────────┐
│Acquisition Loop │      │Upload Loop      │
│                 │      │                 │
│ ┌─loop[forever]┐│      │ ┌─loop[forever]┐│
│ │              ││      │ │              ││
│ │ ┌──────────┐ ││      │ │ ┌──────────┐ ││
│ │ │Read      │ ││      │ │ │Wait Queue│ ││
│ │ │Sensor    │ ││      │ │ └────┬─────┘ ││
│ │ └────┬─────┘ ││      │ │      │       ││
│ │      │       ││      │ │ ┌────┴─────┐ ││
│ │ ┌────┴─────┐ ││      │ │ │Build     │ ││
│ │ │Filter    │ ││      │ │ │Packet    │ ││
│ │ └────┬─────┘ ││      │ │ └────┬─────┘ ││
│ │      │       ││      │ │      │       ││
│ │ ┌────┴─────┐ ││      │ │ ┌────┴─────┐ ││
│ │ │Store     │ ││      │ │ │Send to   │ ││
│ │ │to Queue  │ ││      │ │ │Cloud     │ ││
│ │ └────┬─────┘ ││      │ │ └────┬─────┘ ││
│ │      │       ││      │ │      │       ││
│ │ ┌────┴─────┐ ││      │ │   ◇──┴──◇    ││
│ │ │Delay     │ ││      │ │   [success?] ││
│ │ │100ms     │ ││      │ │   Yes│ No    ││
│ │ └──────────┘ ││      │ │      │  └───┐││
│ │              ││      │ │ ┌────┴─────┐││
│ └──────────────┘│      │ │ │Log       │││
│                 │      │ │ │Success   │││
│                 │      │ │ └──────────┘││
│                 │      │ │              ││
│                 │      │ └──────────────┘│
└─────────────────┘      └─────────────────┘
代码实现
c 复制代码
QueueHandle_t dataQueue;

void main(void)
{
    // Initialize Sensors
    Sensor_Init();
    
    // Create queue
    dataQueue = xQueueCreate(50, sizeof(SensorData_t));
    
    // Fork - 并发任务
    xTaskCreate(AcquisitionTask, "Acquire", 256, NULL, 2, NULL);
    xTaskCreate(UploadTask,      "Upload",  256, NULL, 1, NULL);
    
    vTaskStartScheduler();
}

void AcquisitionTask(void *param)
{
    SensorData_t data;
    
    while (1) {  // Loop forever
        // Read Sensor
        data.temperature = ReadTemperature();
        data.humidity = ReadHumidity();
        data.timestamp = GetTick();
        
        // Filter
        data.temperature = LowPassFilter(data.temperature);
        
        // Store to Queue
        if (xQueueSend(dataQueue, &data, 0) != pdTRUE) {
            // Queue full, data lost
            LogWarning("Data queue full");
        }
        
        // Delay 100ms
        vTaskDelay(pdMS_TO_TICKS(100));
    }
}

void UploadTask(void *param)
{
    SensorData_t data;
    uint8_t packet[128];
    
    while (1) {  // Loop forever
        // Wait Queue
        if (xQueueReceive(dataQueue, &data, portMAX_DELAY) == pdTRUE) {
            // Build Packet
            int len = BuildPacket(packet, &data);
            
            // Send to Cloud
            bool success = SendToCloud(packet, len);
            
            // Decision: success?
            if (success) {
                LogSuccess("Data uploaded");
            } else {
                LogError("Upload failed");
                // Could add retry logic here
            }
        }
    }
}

八、PlantUML 绘制指南

基本语法

plantuml 复制代码
@startuml

start
:Action 1;
:Action 2;
stop

@enduml

常用元素

元素 语法 说明
开始 start 初始节点
结束 stop 终止节点
动作 :Action; 圆角矩形
判断 if (condition) then (yes) 菱形
循环 repeat / while 循环结构
Fork fork / fork again 并发开始
Join end fork 并发结束
泳道 ` Swimlane
注释 note right 说明文字

判断语句

plantuml 复制代码
@startuml

start
:Read Data;

if (Data Valid?) then (yes)
  :Process Data;
else (no)
  :Discard Data;
endif

:Continue;
stop

@enduml

循环语句

plantuml 复制代码
@startuml

start

repeat
  :Read Byte;
  :Store Byte;
repeat while (More Data?) is (yes)
->no;

:Process Buffer;
stop

@enduml

并发结构

plantuml 复制代码
@startuml

start
:Initialize;

fork
  :Task 1;
fork again
  :Task 2;
fork again
  :Task 3;
end fork

:Continue;
stop

@enduml

泳道

plantuml 复制代码
@startuml

|Customer|
start
:Place Order;

|System|
:Process Order;

fork
  |Warehouse|
  :Prepare Package;
fork again
  |Finance|
  :Process Payment;
end fork

|Customer|
:Receive Order;
stop

@enduml

完整示例

plantuml 复制代码
@startuml CompleteExample

title Device Initialization Flow

start
:System Reset;
:Clock Config;

fork
  :GPIO Init;
fork again
  :UART Init;
fork again
  :SPI Init;
fork again
  :I2C Init;
end fork

:RTOS Init;

if (All Peripherals OK?) then (yes)
  :Start Application;
  stop
else (no)
  :Enter Error Handler;
  stop
endif

@enduml

九、最佳实践与避坑指南

1. 活动图的正确使用场景

复制代码
✅ 应该使用活动图:
- 复杂函数的算法流程
- 初始化序列
- 数据处理流程
- 业务流程
- 协议处理流程

❌ 不应该使用活动图:
- 简单的 if-else(不值得画)
- 对象间交互(用时序图)
- 长期运行的状态(用状态图)
- 类的结构(用类图)

2. 活动图的粒度控制

复制代码
❌ 粒度太细:
每一行代码都画一个节点
→ 图太复杂,失去意义

✅ 合适粒度:
一个节点对应一个函数调用或一个有意义的步骤
→ 清晰表达主要流程

❌ 粒度太粗:
一个节点包含大量逻辑
→ 无法看出细节

3. 命名规范

复制代码
✅ 好的命名:
- Initialize UART(动宾结构)
- Read Sensor Data
- Calculate Checksum
- Send Response

❌ 不好的命名:
- UART(只有名词)
- Data(太抽象)
- Process(不明确做什么)
- Step1(无意义)

4. 并发的正确表达

复制代码
✅ 正确:
使用 Fork/Join 明确表达并发关系
→ 清楚哪些是并行的,哪些需要同步

❌ 错误:
所有流程都是串行
→ 无法表达真实的并发逻辑

5. 决策节点的使用

复制代码
✅ 正确:
- 每个分支都标注条件
- 有 Merge 节点合并分支
- 覆盖所有可能的情况

❌ 错误:
- 分支没有标注条件
- 分支后没有合并
- 遗漏某些情况

6. 循环的表达

复制代码
✅ 清晰的循环:
- 明确循环条件
- 有退出机制
- 循环体清晰

❌ 不清楚的循环:
- 没有退出条件
- 可能的死循环
- 循环逻辑混乱

7. 活动图与代码的对应

复制代码
✅ 好的实践:
1. 先画活动图
2. 根据图写代码
3. 代码中添加注释引用图中的节点
4. 代码变动时同步更新图

❌ 错误做法:
- 图和代码不一致
- 画完图就不管了
- 图和代码完全对应不上

8. 错误处理

复制代码
✅ 完整的错误处理:
- 每个可能失败的点都有判断
- 有明确的错误处理路径
- 错误情况下的资源清理

❌ 缺失错误处理:
- 只画正常流程
- 忽略异常情况
- 没有清理资源

十、总结

活动图的核心价值

复制代码
1. 设计工具:把复杂流程理清楚
2. 沟通工具:团队统一理解
3. 文档工具:维护和交接
4. 调试工具:对照检查逻辑

记忆口诀

复制代码
算法用活动图,逻辑用状态图
自己怎么做 → 活动图
彼此怎么说 → 时序图
现在干什么 → 状态图

能力自检

你是否能做到:

  • 识别适合用活动图的场景
  • 正确使用 Fork/Join 表达并发
  • 用决策节点和循环表达控制流
  • 使用泳道组织多模块流程
  • 将活动图翻译成代码
  • 保持活动图与代码一致
  • 使用 PlantUML 快速绘图

如果能做到 5 条以上,你已经掌握了活动图的核心技能!


附录:参考资源

  • UML 2.5 Activity Diagram Specification
  • PlantUML 官网:https://plantuml.com
  • 《UML Distilled》- Martin Fowler
  • 《Real-Time UML》- Bruce Powel Douglass

记住:活动图是设计算法和流程的最佳工具,熟练使用能让你的代码逻辑更清晰!

相关推荐
为自己_带盐2 小时前
记一次“丝滑”的服务器迁移
运维·服务器
ん贤2 小时前
io.copy
运维·服务器·网络·io.copy
默|笙2 小时前
【Linux】进程控制(2)进程等待
linux·运维·服务器
后端小张2 小时前
【AI 学习】深度解析Transformer核心:注意力机制的原理、实现与应用
人工智能·深度学习·学习·机器学习·自然语言处理·数据挖掘·transformer
旖旎夜光2 小时前
Linux(5)(上)
linux·学习
点云SLAM2 小时前
Scenarios 英文单词学习
学习·英文单词学习·雅思备考·情景 / 情节·剧情 / 故事情景·scenarios
QT 小鲜肉11 小时前
【Linux命令大全】001.文件管理之git命令(实操篇)
linux·服务器·笔记·git·elasticsearch
半夏知半秋11 小时前
docker常用指令整理
运维·笔记·后端·学习·docker·容器