【嵌入式 C 语言实战】栈、队列、二叉树核心解析:存储原理 + 应用场景 + 实现思路

【嵌入式 C 语言实战】栈、队列、二叉树核心解析:存储原理 + 应用场景 + 实现思路

大家好,我是学嵌入式的小杨同学。在嵌入式开发中,栈、队列、二叉树是除了链表之外最核心的三种数据结构,它们各自有着独特的存储规则和应用场景 ------ 栈的 "先进后出"、队列的 "先进先出"、二叉树的 "层级存储",分别对应中断机制、消息传递、资源管理等高频需求。今天就结合资料,系统讲解这三种数据结构的核心原理、存储方式和嵌入式实战场景,帮你一次性掌握嵌入式数据结构的 "半壁江山"。

一、先理清核心:三种数据结构的本质区别

栈、队列、二叉树虽同属数据结构,但存储规则和核心用途完全不同,用一张表就能快速区分:

数据结构 核心特点 存储规则 生活类比 嵌入式核心场景
栈(Stack) 先进后出(FILO) 仅从一端(栈顶)插入 / 删除数据 有底的盒子(先放的后拿) 函数调用栈、中断机制、局部变量存储
队列(Queue) 先进先出(FIFO) 从一端(队尾)插入、另一端(队首)删除 排队(先到先得) 消息队列(MQTT)、串口数据缓存、任务调度
二叉树(Binary Tree) 层级结构 每个节点最多两个子节点(左 / 右子树) 族谱、公司组织结构 资源管理器、设备树解析、语法解析树

二、栈(Stack):先进后出的 "数据盒子"

栈是嵌入式开发中最基础也最常用的数据结构,其核心是 "只能在栈顶操作数据",操作效率极高(时间复杂度 O (1))。

1. 栈的核心概念

  • 栈底(pstart):栈的起始位置,固定不变;
  • 栈顶(ptemp):永远指向下一个可存储数据的位置,随数据插入 / 删除移动;
  • 栈空:栈顶指针(ptemp)与栈底指针(pstart)重合;
  • 栈满:栈顶指针(ptemp)超过栈空间的末尾(pend+1)。

2. 两种存储方式(嵌入式实战)

(1)数组存储(线性存储)

用固定大小的数组作为栈空间,实现简单、访问速度快,适合数据量固定的场景(如小型缓存)。

c

运行

复制代码
#include <stdio.h>
#include <stdlib.h>

#define STACK_SIZE 10  // 栈空间大小

// 栈结构体定义
typedef struct {
    int data[STACK_SIZE]; // 栈空间(数组)
    int *pstart;          // 栈底指针
    int *pend;            // 栈空间末尾指针
    int *ptemp;           // 栈顶指针
} Stack;

// 初始化栈
Stack* InitStack() {
    Stack *stack = (Stack *)malloc(sizeof(Stack));
    if (stack == NULL) {
        printf("malloc error:栈初始化失败\n");
        return NULL;
    }
    stack->pstart = stack->data;       // 栈底指向数组起始位置
    stack->pend = stack->data + STACK_SIZE - 1; // 栈尾指向数组最后一个元素
    stack->ptemp = stack->pstart;      // 栈顶初始指向栈底(空栈)
    return stack;
}

// 入栈(压栈)
int PushStack(Stack *stack, int num) {
    // 判断栈满
    if (stack->ptemp > stack->pend) {
        printf("栈满,无法入栈\n");
        return 0;
    }
    *stack->ptemp = num; // 数据存入栈顶
    stack->ptemp++;      // 栈顶指针上移
    return 1;
}

// 出栈(弹栈)
int PopStack(Stack *stack) {
    // 判断栈空
    if (stack->ptemp == stack->pstart) {
        printf("栈空,无法出栈\n");
        return -1;
    }
    stack->ptemp--;      // 栈顶指针下移
    return *stack->ptemp; // 返回栈顶数据
}

// 打印栈
void PrintStack(Stack *stack) {
    if (stack->ptemp == stack->pstart) {
        printf("栈空\n");
        return;
    }
    int *temp = stack->pstart;
    printf("栈元素(从栈底到栈顶):");
    while (temp < stack->ptemp) {
        printf("%d ", *temp);
        temp++;
    }
    printf("\n");
}
(2)链表存储(链式存储)

用链表的头插法实现栈,支持动态扩容,无栈满限制,适合数据量不固定的场景。核心逻辑:

  • 入栈:头插法新增节点(栈顶为链表头部);
  • 出栈:删除链表头部节点(栈顶节点);
  • 优势:无需预先分配固定空间,内存利用率更高。

3. 嵌入式核心应用场景

  • 函数调用栈:C 语言函数调用时,参数、返回地址、局部变量会自动压入栈中,函数执行完毕后弹栈;
  • 中断机制:中断发生时,CPU 会将当前程序状态压入栈,中断处理完成后弹栈恢复;
  • 临时数据缓存:如串口接收短数据时,用栈临时存储,处理完毕后依次弹栈。

三、队列(Queue):先进先出的 "数据管道"

队列与栈相反,遵循 "先进先出" 规则,核心是 "两端操作",是嵌入式中数据传递的核心载体。

1. 队列的核心概念

  • 队空间:存储数据的内存区域(数组或链表);
  • 队底(pstart):队列的起始位置;
  • 队顶(pend):队列的末尾位置;
  • 入队指针(pin):指向下一个可入队的位置;
  • 出队指针(pout):指向下一个可出队的位置;
  • 队空:计数变量 count=0;
  • 队满:计数变量 count=total(允许入队的总数量)。

2. 两种存储方式(嵌入式实战)

(1)数组存储(循环队列)

用数组实现循环队列,解决 "假溢出" 问题(数组尾部满但头部有空位),是嵌入式高频实现方式。核心逻辑:

  • 入队:pin 指向的位置存入数据,pin++;若 pin 超过 pend,重置为 pstart;
  • 出队:取出 pout 指向的数据,pout++;若 pout 超过 pend,重置为 pstart;
  • 用 count 计数,避免入队 / 出队指针重合时无法判断空满。

c

运行

复制代码
#define QUEUE_TOTAL 10  // 队列最大容量

// 循环队列结构体
typedef struct {
    int data[QUEUE_TOTAL]; // 队空间(数组)
    int *pstart;           // 队底指针
    int *pend;             // 队顶指针
    int pin;               // 入队指针(数组下标)
    int pout;              // 出队指针(数组下标)
    int count;             // 当前队列元素个数
} CircleQueue;

// 初始化循环队列
CircleQueue* InitCircleQueue() {
    CircleQueue *queue = (CircleQueue *)malloc(sizeof(CircleQueue));
    if (queue == NULL) {
        printf("malloc error:队列初始化失败\n");
        return NULL;
    }
    queue->pstart = queue->data;
    queue->pend = queue->data + QUEUE_TOTAL - 1;
    queue->pin = 0;
    queue->pout = 0;
    queue->count = 0;
    return queue;
}

// 入队
int InQueue(CircleQueue *queue, int num) {
    if (queue->count == QUEUE_TOTAL) {
        printf("队满,无法入队\n");
        return 0;
    }
    queue->data[queue->pin] = num;
    queue->pin++;
    // 循环:pin超过队尾,重置为队头
    if (queue->pin > QUEUE_TOTAL - 1) {
        queue->pin = 0;
    }
    queue->count++;
    return 1;
}

// 出队
int OutQueue(CircleQueue *queue) {
    if (queue->count == 0) {
        printf("队空,无法出队\n");
        return -1;
    }
    int data = queue->data[queue->pout];
    queue->pout++;
    // 循环:pout超过队尾,重置为队头
    if (queue->pout > QUEUE_TOTAL - 1) {
        queue->pout = 0;
    }
    queue->count--;
    return data;
}
(2)链表存储(链式队列)

用链表的尾插法实现队列,动态扩容,无队满限制,适合大数据量场景(如消息队列)。核心逻辑:

  • 入队:尾插法新增节点(队尾为链表尾部);
  • 出队:删除链表头部节点(队首节点);
  • 优势:无需担心队列溢出,适合数据量波动大的场景。

3. 嵌入式核心应用场景

  • 消息队列(MQTT):物联网设备中,传感器数据、控制指令通过队列缓存,按顺序处理;
  • 串口数据缓存:串口接收数据时,用队列异步缓存,主线程同步读取处理,避免数据丢失;
  • 任务调度:RTOS 中,就绪队列存储等待执行的任务,调度器按优先级依次取出任务执行。

四、二叉树(Binary Tree):层级分明的 "数据结构树"

二叉树是树形结构的基础,每个节点最多有两个子节点(左子树、右子树),适合层级化数据存储和查询。

1. 二叉树的核心概念

  • 根节点:树的顶层节点,无父节点;
  • 子节点:每个节点的左、右子节点,构成左子树、右子树;
  • 叶子节点:无左、右子节点的节点;
  • 层级:从根节点开始计数,根节点为第 1 层。

2. 存储方式(链式存储,嵌入式首选)

用结构体定义节点,包含数据域和左 / 右子节点指针,是二叉树的标准实现方式。

c

运行

复制代码
// 二叉树节点结构体
typedef struct TreeNode {
    int data;                  // 数据域
    struct TreeNode *left;     // 左子节点指针
    struct TreeNode *right;    // 右子节点指针
} TreeNode;

// 创建二叉树节点
TreeNode* CreateTreeNode(int data) {
    TreeNode *node = (TreeNode *)malloc(sizeof(TreeNode));
    if (node == NULL) {
        printf("malloc error:节点创建失败\n");
        return NULL;
    }
    node->data = data;
    node->left = NULL;
    node->right = NULL;
    return node;
}

// 构建示例二叉树(手动构建)
TreeNode* BuildBinaryTree() {
    // 根节点
    TreeNode *root = CreateTreeNode(1);
    // 第二层节点
    root->left = CreateTreeNode(2);
    root->right = CreateTreeNode(3);
    // 第三层节点
    root->left->left = CreateTreeNode(4);
    root->left->right = CreateTreeNode(5);
    return root;
}

// 前序遍历(根→左→右)
void PreOrderTraversal(TreeNode *root) {
    if (root != NULL) {
        printf("%d ", root->data);
        PreOrderTraversal(root->left);
        PreOrderTraversal(root->right);
    }
}

3. 嵌入式核心应用场景

  • 资源管理器:嵌入式系统的文件系统(如 FAT32),本质是二叉树的变种(多叉树),用于层级管理文件和目录;
  • 设备树解析:ARM 嵌入式设备中,设备树(Device Tree)用树形结构描述硬件配置,内核启动时解析设备树;
  • 语法解析树:编译器编译代码时,会将源代码解析为二叉树结构,用于语法检查和指令生成。

五、嵌入式开发关键注意事项

  1. 存储方式选择
    • 数据量固定、追求速度:选数组存储(栈 / 队列);
    • 数据量不固定、避免浪费:选链表存储(栈 / 队列 / 二叉树);
  2. 内存管理
    • 链式存储时,malloc分配的节点必须用free释放,避免内存泄漏;
    • 数组存储时,需合理设置空间大小,避免溢出或浪费;
  3. 线程安全
    • 多线程 / 中断中操作栈 / 队列,需添加互斥锁或关中断保护,避免并发访问导致数据混乱;
  4. 效率优化
    • 栈 / 队列的操作效率均为 O (1),二叉树遍历效率为 O (n),大数据量查询建议用二叉搜索树(BST)优化。

六、总结

栈、队列、二叉树是嵌入式 C 语言的核心数据结构,核心要点可总结为:

  1. 栈:先进后出,适合临时存储、函数调用、中断处理;
  2. 队列:先进先出,适合数据传递、消息缓存、任务调度;
  3. 二叉树:层级结构,适合层级化数据存储、资源管理、解析场景;
  4. 存储方式:数组快但固定,链表灵活但需管理内存,嵌入式需按需选择。

掌握这三种数据结构,能轻松应对嵌入式开发中 80% 以上的复杂数据处理场景,为后续学习 RTOS、文件系统、设备驱动打下坚实基础。我是学嵌入式的小杨同学,关注我,后续会分享更多嵌入式实战技巧!

相关推荐
专注API从业者5 小时前
淘宝商品 API 接口架构解析:从请求到详情数据返回的完整链路
java·大数据·开发语言·数据库·架构
VekiSon5 小时前
ARM架构——时钟系统与定时器详解
linux·c语言·arm开发·嵌入式硬件·架构
Mr -老鬼5 小时前
MySQL 8+ ibd文件恢复表结构实战:从ibd2sdi解析到数据重建
数据库·mysql
2401_841495645 小时前
【LeetCode刷题】删除链表的倒数第N个结点
数据结构·python·算法·leetcode·链表·遍历·双指针
哪里不会点哪里.5 小时前
Spring 的装配顺序详解(配置 → 扫描 → 注入 → 初始化)
java·sql·spring
xiaolyuh1235 小时前
Spring MVC 深度解析
java·spring·mvc
舰长1155 小时前
SSLITLS协议信息泄露漏洞(CVE-2016-2183)【原理扫描】
网络
optimistic_chen5 小时前
【Docker入门】Docker Registry(镜像仓库)
linux·运维·服务器·docker·容器·镜像仓库·空间隔离
❀͜͡傀儡师5 小时前
SpringBoot与Artemis整合,实现航空行李追踪消息中枢系统
java·spring boot·后端