news 2026/5/28 8:28:14

手把手带你用C语言写一个带完整测试菜单的循环队列程序(附三种实现源码)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手把手带你用C语言写一个带完整测试菜单的循环队列程序(附三种实现源码)

手把手实现C语言循环队列:从理论到可交互测试的完整项目

循环队列是数据结构课程中的经典内容,但很多学习者止步于理论理解,缺乏将算法转化为可运行代码的实战经验。本文将带你用C语言构建一个完整的循环队列项目,包含三种不同的实现方式,并设计交互式测试菜单,让你能够直观观察不同实现下的队列行为差异。

1. 项目准备与环境搭建

在开始编码之前,我们需要明确循环队列的核心概念和本项目的基本结构。循环队列通过数组实现,通过模运算处理"假溢出"问题,相比普通队列能更高效利用存储空间。本项目将实现三种判断队空/队满的方法:

  1. 少用一个空间(常用方法)
  2. 使用flag标志位
  3. 维护length计数器

首先创建项目目录结构:

circular_queue/ ├── include/ # 头文件 │ └── queue.h ├── src/ # 源代码 │ ├── main.c │ ├── queue_v1.c # 少用一个空间 │ ├── queue_v2.c # flag标志 │ └── queue_v3.c # length计数 └── Makefile # 构建文件

安装必要的开发工具:

  • GCC编译器(Linux/macOS通常预装,Windows可用MinGW)
  • 代码编辑器(VS Code、CLion等)

2. 核心数据结构设计

queue.h中定义队列的基本结构和接口:

#ifndef QUEUE_H #define QUEUE_H #define MAX_SIZE 5 // 测试用较小容量 typedef struct { int data[MAX_SIZE]; int front; // 队头指针 int rear; // 队尾指针 } QueueV1; // 版本1:少用一个空间 typedef struct { int data[MAX_SIZE]; int front; int rear; int flag; // 0:空队列标记, 1:满队列标记 } QueueV2; // 版本2:使用flag标志 typedef struct { int data[MAX_SIZE]; int front; int rear; int length; // 当前队列长度 } QueueV3; // 版本3:维护length计数器 // 通用队列操作接口 void init_v1(QueueV1 *q); int is_empty_v1(QueueV1 *q); int is_full_v1(QueueV1 *q); int enqueue_v1(QueueV1 *q, int value); int dequeue_v1(QueueV1 *q, int *value); // 其他版本接口类似... #endif

3. 第一种实现:少用一个空间

这是最经典的循环队列实现方式,通过始终空出一个数组位置来区分队空和队满状态。实现文件queue_v1.c

#include "../include/queue.h" void init_v1(QueueV1 *q) { q->front = q->rear = 0; } int is_empty_v1(QueueV1 *q) { return q->front == q->rear; } int is_full_v1(QueueV1 *q) { return (q->rear + 1) % MAX_SIZE == q->front; } int enqueue_v1(QueueV1 *q, int value) { if (is_full_v1(q)) { return 0; // 入队失败 } q->data[q->rear] = value; q->rear = (q->rear + 1) % MAX_SIZE; return 1; // 成功 } int dequeue_v1(QueueV1 *q, int *value) { if (is_empty_v1(q)) { return 0; // 出队失败 } *value = q->data[q->front]; q->front = (q->front + 1) % MAX_SIZE; return 1; // 成功 }

关键点说明:

  • 队空条件:front == rear
  • 队满条件:(rear + 1) % size == front
  • 实际可用容量为MAX_SIZE - 1

4. 第二种实现:使用flag标志

这种方法通过额外的flag变量记录队列状态,可以充分利用所有数组空间。queue_v2.c实现:

#include "../include/queue.h" void init_v2(QueueV2 *q) { q->front = q->rear = 0; q->flag = 0; // 初始为空队列 } int is_empty_v2(QueueV2 *q) { return q->front == q->rear && q->flag == 0; } int is_full_v2(QueueV2 *q) { return q->front == q->rear && q->flag == 1; } int enqueue_v2(QueueV2 *q, int value) { if (is_full_v2(q)) { return 0; } q->data[q->rear] = value; q->rear = (q->rear + 1) % MAX_SIZE; q->flag = 1; // 可能变为满 return 1; } int dequeue_v2(QueueV2 *q, int *value) { if (is_empty_v2(q)) { return 0; } *value = q->data[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->flag = 0; // 可能变为空 return 1; }

特点分析:

  • flag=0表示最近一次操作是出队(可能为空)
  • flag=1表示最近一次操作是入队(可能为满)
  • 可以完全利用数组空间(MAX_SIZE个元素)

5. 第三种实现:维护length计数器

通过显式维护队列当前长度,逻辑最为直观。queue_v3.c实现:

#include "../include/queue.h" void init_v3(QueueV3 *q) { q->front = q->rear = 0; q->length = 0; } int is_empty_v3(QueueV3 *q) { return q->length == 0; } int is_full_v3(QueueV3 *q) { return q->length == MAX_SIZE; } int enqueue_v3(QueueV3 *q, int value) { if (is_full_v3(q)) { return 0; } q->data[q->rear] = value; q->rear = (q->rear + 1) % MAX_SIZE; q->length++; return 1; } int dequeue_v3(QueueV3 *q, int *value) { if (is_empty_v3(q)) { return 0; } *value = q->data[q->front]; q->front = (q->front + 1) % MAX_SIZE; q->length--; return 1; }

优势比较:

  • 逻辑简单直接,易于理解
  • 不需要牺牲存储空间
  • length变量提供了额外信息

6. 交互式测试菜单实现

main.c中创建测试菜单,让用户可以交互式测试三种实现:

#include <stdio.h> #include <stdlib.h> #include "queue.h" void print_menu() { printf("\n=== 循环队列测试菜单 ===\n"); printf("1. 少用一个空间实现\n"); printf("2. 使用flag标志实现\n"); printf("3. 维护length计数器实现\n"); printf("0. 退出\n"); printf("请选择实现方式: "); } void test_queue_v1() { QueueV1 q; init_v1(&q); int choice, value; while (1) { printf("\n[少用一个空间实现] 当前状态: "); printf("front=%d, rear=%d\n", q.front, q.rear); printf("1. 入队\n2. 出队\n3. 检查状态\n0. 返回\n选择操作: "); scanf("%d", &choice); switch (choice) { case 1: printf("输入入队值: "); scanf("%d", &value); if (enqueue_v1(&q, value)) { printf("入队成功\n"); } else { printf("队列已满,入队失败\n"); } break; case 2: if (dequeue_v1(&q, &value)) { printf("出队值: %d\n", value); } else { printf("队列为空,出队失败\n"); } break; case 3: printf("队列状态: %s\n", is_empty_v1(&q) ? "空" : is_full_v1(&q) ? "满" : "非空非满"); break; case 0: return; default: printf("无效选择\n"); } } } // test_queue_v2和test_queue_v3类似... int main() { int choice; while (1) { print_menu(); scanf("%d", &choice); switch (choice) { case 1: test_queue_v1(); break; case 2: test_queue_v2(); break; case 3: test_queue_v3(); break; case 0: exit(0); default: printf("无效选择\n"); } } return 0; }

7. 边界测试与调试技巧

为了确保队列实现的正确性,我们需要设计全面的测试用例:

测试场景设计:

  1. 基本功能测试:

    • 空队列出队
    • 单元素入队出队
    • 连续入队直到满队
  2. 边界条件测试:

    • 满队时继续入队
    • 空队时继续出队
    • 循环跨越数组边界
  3. 综合测试:

    • 交替进行入队出队操作
    • 随机操作序列测试

调试技巧:

  • 在关键操作后打印队列状态:

    void print_queue_state(QueueV1 *q) { printf("front=%d, rear=%d, data=[", q->front, q->rear); for (int i = 0; i < MAX_SIZE; i++) { printf("%d ", q->data[i]); } printf("]\n"); }
  • 使用断言检查不变条件:

    #include <assert.h> void check_invariants(QueueV3 *q) { assert(q->length >= 0 && q->length <= MAX_SIZE); assert(q->front >= 0 && q->front < MAX_SIZE); assert(q->rear >= 0 && q->rear < MAX_SIZE); }

8. 三种实现的性能与适用场景对比

特性少用一个空间使用flag标志维护length计数器
存储空间利用率MAX_SIZE-1MAX_SIZEMAX_SIZE
判断逻辑复杂度中等较高简单
额外空间开销1个int1个int
代码可读性一般较差优秀
适合场景通用空间敏感可读性优先

实际项目中,length计数器实现通常是最佳选择,因为:

  • 代码更易理解和维护
  • 不牺牲存储空间
  • length信息有时可直接用于业务逻辑

9. 项目扩展与进阶思考

完成基础实现后,可以考虑以下扩展方向:

  1. 动态扩容:当队列满时自动扩大存储空间

    int resize_queue(QueueV3 *q, int new_size) { int *new_data = malloc(new_size * sizeof(int)); // 迁移数据... }
  2. 泛型队列:使用void指针支持任意数据类型

    typedef struct { void **data; // 指针数组 // ...其他字段 } GenericQueue;
  3. 多线程安全:添加互斥锁保护共享队列

    #include <pthread.h> typedef struct { int data[MAX_SIZE]; pthread_mutex_t lock; // ...其他字段 } ThreadSafeQueue;
  4. 性能优化:考虑缓存友好性、减少模运算等

10. 实际项目中的经验分享

在嵌入式系统中,我曾使用flag标志实现处理串口接收缓冲区,因为内存极其有限(仅256字节),必须充分利用每个字节。而在Web服务器项目中,则选择了length计数器实现来管理请求队列,因为代码可读性和维护性更为重要。

调试循环队列时最常见的错误是:

  • 忘记模运算导致数组越界
  • 队空/队满条件判断错误
  • 指针更新顺序不当

一个实用的调试技巧是:在每次操作后打印整个数组内容和指针位置,可视化观察队列变化。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 8:28:13

RimSort终极指南:5步掌握开源跨平台模组管理器

RimSort终极指南&#xff1a;5步掌握开源跨平台模组管理器 【免费下载链接】RimSort RimSort is an open source mod manager for the video game RimWorld. There is support for Linux, Mac, and Windows, built from the ground up to be a reliable, community-managed alt…

作者头像 李华
网站建设 2026/5/28 8:27:06

3分钟从视频中智能提取PPT:告别手动截图的终极指南

3分钟从视频中智能提取PPT&#xff1a;告别手动截图的终极指南 【免费下载链接】extract-video-ppt extract the ppt in the video 项目地址: https://gitcode.com/gh_mirrors/ex/extract-video-ppt 你是否厌倦了从视频中一帧帧截图来获取PPT内容&#xff1f;extract-vi…

作者头像 李华
网站建设 2026/5/28 8:13:04

告别SD卡!用STM32F030和W25Q16打造你的轻量级文件存储系统

STM32F030与W25Q16构建高效嵌入式存储系统的实战指南在资源受限的嵌入式开发中&#xff0c;如何平衡存储性能、成本与系统复杂度一直是开发者面临的挑战。传统SD卡方案虽然容量大但接口复杂&#xff0c;而EEPROM又受限于写入次数和容量。本文将带你探索一种折中方案——基于STM…

作者头像 李华
网站建设 2026/5/28 8:12:09

Nova AI Ops:AI原生操作系统如何重塑SRE的智能运维实践

1. 项目概述&#xff1a;当SRE遇见AI原生操作系统最近几年&#xff0c;SRE&#xff08;站点可靠性工程&#xff09;团队的日子是越来越“卷”了。监控告警半夜响个不停&#xff0c;故障根因像捉迷藏&#xff0c;容量规划靠拍脑袋&#xff0c;变更发布如履薄冰。我们手里工具不少…

作者头像 李华