隊列是基礎數據結構之一,本文提供一個基於鏈表的隊列 ADT 完整實現,包含創建、入隊、出隊、遍歷、銷毀等操作,並深度分析代碼中存在的內存洩漏、邏輯錯誤與不規範之處,作為學習參考與代碼審查練習。
typedef struct Node {
int data;
struct Node *next;
} *Node;
typedef struct Queue {
int size;
struct Node *first;
struct Node *end;
} *Queue;
// 核心操作
Queue CreatEmptyQueue(void);
int isEmpty(Queue q);
int isFull(Queue q);
int QueueIn(Queue q, int data);
int QueueOut(Queue q, int *data);
void printQueue(Queue q);
void destoryQueue(Queue q);
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#define MAX_SIZE 10
Queue CreatEmptyQueue() {
Queue q = malloc(sizeof(struct Queue));
if (!q) { perror("malloc queue"); exit(EXIT_FAILURE); }
q->size = 0;
q->first = q->end = NULL;
return q;
}
int isEmpty(Queue q) { return q->size <= 0; }
int isFull(Queue q) { return q->size >= MAX_SIZE; }
int QueueIn(Queue q, int data) {
assert(q);
if (isFull(q)) { fprintf(stderr, "Queue full\n"); return -1; }
Node p = malloc(sizeof(struct Node));
if (!p) { perror("malloc node"); return -1; }
p->data = data;
p->next = NULL;
if (isEmpty(q)) {
q->first = q->end = p;
} else {
q->end->next = p;
q->end = p;
}
q->size++;
return 0;
}
int QueueOut(Queue q, int *data) {
assert(q && data);
if (isEmpty(q)) { fprintf(stderr, "Queue empty\n"); return -1; }
Node p = q->first;
*data = p->data;
q->first = p->next;
q->size--;
if (isEmpty(q)) q->end = NULL; // 關鍵:最後一個節點出隊後 end 置 NULL
free(p);
return 0;
}
void printQueue(Queue q) {
assert(q);
printf("size=%d: ", q->size);
for (Node p = q->first; p; p = p->next)
printf("%d ", p->data);
putchar('\n');
}
void destoryQueue(Queue q) {
assert(q);
Node p = q->first, next;
while (p) {
next = p->next;
free(p);
p = next;
}
free(q); // 僅釋放隊列頭結構,調用者需自行置 NULL
}
int main() {
Queue q = CreatEmptyQueue();
int data;
// 空隊列出隊
if (QueueOut(q, &data) == 0) printf("out: %d\n", data);
else puts("empty queue");
// 入隊超過 MAX_SIZE
for (int i = 0; i < MAX_SIZE + 5; i++)
QueueIn(q, i * 5 + 10);
printQueue(q);
destoryQueue(q);
return 0;
}
| 問題 | 代碼位置 | 後果 | 修正方案 |
|---|---|---|---|
| destoryQueue 不置空傳入指針 | free(q); q = NULL; | 調用者持有懸空指針,後續誤用導致 UB | 改為 void destoryQueue(Queue *q),內部 *q = NULL |
| QueueOut 空隊列未清理 end | 原代碼無 q->end = NULL | 出隊後 end 指向已釋放節點 | 如現代代碼所示:if (isEmpty(q)) q->end = NULL; |
| CreatEmptyQueue 強制 exit | exit(-1) | 庫代碼不應終止進程,應返回錯誤碼 | 返回 NULL,調用者檢查 |
混用 malloc 與 assert | assert(q!=NULL) 後仍可能 OOM | 調試版釋出 assert 失效,生產環境無保護 | 運行時檢查返回碼,assert 僅用於邏輯前置條件 |
| Node/Queue 類型定義隱藏指針 | typedef struct Node *Node; | 讀者易忘記是指針,導致 sizeof(Node) 算錯 | 建議 typedef struct Node Node;,使用時顯式 Node* |
| 無拷貝構造/賦值防護 | 結構體含指針成員 | 淺拷貝導致雙重釋放 | C 中約定不可拷貝,或提供 QueueClone |
// 隱藏實現細節的不透明指針風格(.h)
typedef struct Queue *QueueHandle;
QueueHandle queue_create(int max_size);
void queue_destroy(QueueHandle *q); // 雙重指針置空
int queue_push(QueueHandle q, int data);
int queue_pop(QueueHandle q, int *data);
int queue_size(QueueHandle q);
bool queue_empty(QueueHandle q);
鏈式隊列實現看似簡單,實則暗藏 指針所有權轉移、邊界條件、錯誤處理策略、接口封裝 等工程核心課題。建議學習者: