結構體聚合異構資料,指針串聯動態節點,兩者結合構成 C 語言資料結構的基石。本文深入解析結構體記憶體對齊、指針操作慣用模式、單/雙向鏈表完整實作、動態記憶體管理陷阱與防禦模式。
typedef struct {
char *firstName;
char *lastName;
char *title;
unsigned int age;
} Person;
int 4 位元組對齊)offsetof(Person, age) 取得欄位偏移(stddef.h)Person p = {0};
p.firstName = strdup("Emily");
p.age = 23;
Person *ptr = malloc(sizeof(Person));
ptr->firstName = strdup("Emily");
ptr->age = 23;
// 等價於 (*ptr).age = 23
Person p = { .age = 23, .firstName = "Emily" };
| 策略 | 語法 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 值傳遞 | void f(Person p) | 簡單、無副作用 | 拷貝開銷大、無法修改原值 | 小型結構、唯讀 |
| 指針傳遞 | void f(Person *p) | 零拷貝、可修改、支援動態分配 | 需檢查空指針 | 大型結構、需修改、動態節點 |
| const 指針 | void f(const Person *p) | 零拷貝、唯讀保護 | 不可修改 | 大型結構、唯讀 |
typedef struct Node {
int data;
struct Node *next; // 單向
// struct Node *prev; // 雙向額外加此欄位
} Node;
// 建立節點
Node* create_node(int val) {
Node *n = malloc(sizeof(Node));
if (!n) return NULL;
n->data = val;
n->next = NULL;
return n;
}
// 尾插法建表(帶頭節點)
Node* build_list(int *arr, int n) {
Node head = {0, NULL};
Node *tail = &head;
for (int i = 0; i < n; ++i) {
tail->next = create_node(arr[i]);
if (!tail->next) { free_list(head.next); return NULL; }
tail = tail->next;
}
return head.next;
}
// 遍歷
void traverse(Node *head) {
for (Node *p = head; p; p = p->next)
printf("%d ", p->data);
puts("");
}
// 查找
Node* find(Node *head, int val) {
for (Node *p = head; p; p = p->next)
if (p->data == val) return p;
return NULL;
}
// 插入(在第 pos 個節點後)
bool insert_after(Node *head, int pos, int val) {
Node *p = head;
for (int i = 0; i < pos && p; ++i) p = p->next;
if (!p) return false;
Node *n = create_node(val);
if (!n) return false;
n->next = p->next;
p->next = n;
return true;
}
// 刪除(刪除第 pos 個節點)
bool delete_at(Node **head, int pos) {
if (!*head) return false;
if (pos == 0) {
Node *tmp = *head;
*head = (*head)->next;
free(tmp);
return true;
}
Node *p = *head;
for (int i = 0; i < pos-1 && p->next; ++i) p = p->next;
if (!p->next) return false;
Node *tmp = p->next;
p->next = tmp->next;
free(tmp);
return true;
}
// 釋放整表
void free_list(Node *head) {
while (head) {
Node *tmp = head;
head = head->next;
free(tmp);
}
}
prev 指針| 函數 | 原型 | 特性 |
|---|---|---|
| malloc | void* malloc(size_t size) | 分配未初始化記憶體,失敗返回 NULL |
| calloc | void* calloc(size_t n, size_t size) | 分配並歸零,防止未初始化讀取 |
| realloc | void* realloc(void* ptr, size_t size) | 調整大小,可能移動記憶體位址 |
| free | void free(void* ptr) | 釋放,ptr 為 NULL 安全無操作 |
if (!p) return ERR;free(p); p = NULL;free(ptr->firstName); free(ptr->lastName); free(ptr);
memset(ptr, 0, size); free(ptr);malloc/calloc/realloc 對應唯一 free// 錯誤:釋放順序顛倒導致內層指針成迷途指針 free(ptr); // 外層先釋放 free(ptr->name); // 非法:ptr 已失效,ptr->name 讀取未定義行為 // 錯誤:未分配足夠空間給字串欄位 ptr->name = malloc(strlen(src)); // 缺少 +1 給 \0 strcpy(ptr->name, src); // 越界寫入 // 正確: ptr->name = malloc(strlen(src) + 1); strcpy(ptr->name, src);
結構體與指針的協奏,構建了 C 語言動態資料結構的無限可能。掌握記憶體佈局、所有權轉移、生命週期管理三大核心,方能馭駕複雜系統而不失控。