第一部分:結構體體系深度解析
1. 結構體類型定義三式
// 1. 先定類型,後定變量
struct Student { char name[20]; int age; float score; };
struct Student s1, s2;
// 2. 定類型同時定變量
struct Teacher { char name[20]; int id; } t1, t2;
// 3. 匿名結構體(僅能定變量,無法復用類型)
struct { char name[20]; int age; } anon1, anon2;
2. 成員訪問與嵌套
struct Date { int y, m, d; };
struct Person { char name[20]; struct Date birthday; };
struct Person p = {"Tom", {2000, 5, 1}};
p.birthday.m = 6; // 多級成員訪問
struct Person *pp = &p;
pp->birthday.m = 7; // 指針訪問嵌套成員
3. 內存對齊與大小計算 —— 面試高頻考點
struct Demo {
char c1; // 偏移 0, 大小 1
// 3 字節填充
int i; // 偏移 4, 大小 4
char c2; // 偏移 8, 大小 1
// 3 字節填充(結構體大小須為最大成員 4 的倍數)
}; // sizeof = 12
// 驗證
printf("%zu\n", sizeof(struct Demo)); // 12
printf("%zu\n", offsetof(struct Demo, i)); // 4
對齊規則:
- 首成員偏移 0。
- 後續成員偏移 = min(該類型大小, 編譯器對齊數) 的倍數(通常 = 類型大小)。
- 結構體總大小 = 最大成員大小的整數倍(尾部填充)。
#pragma pack(n) 可壓縮對齊(n=1 為緊湊),但可能降低訪問性能或導致硬件異常(某些架構)。
4. 結構體與函數交互三模式
| 模式 | 原型 | 特點 | 適用場景 |
| 值傳遞 | void print(struct Student s) | 拷貝整個結構體,開銷大,不修改原值 | 小型結構體(<16 字節),只讀 |
| 指針傳遞 | void print(const struct Student *p) | 傳地址,無拷貝,可修改(無 const 時) | 通用首選,大結構體、需修改 |
| 返回結構體 | struct Student input(void) | C99 支持直接返回,編譯器優化(RVO/NRVO) | 工廠函數、輸入封裝 |
5. 結構體數組:批量管理同質對象
struct Student class[50] = {
{101, "Zhang", 88.5},
{102, "Li", 92.0}
};
// 遍歷統計
for (int i = 0; i < 50; ++i)
if (class[i].score > 90) printf("%s\n", class[i].name);
第二部分:鏈表——動態數據結構核心
1. 節點定義與基本操作
struct Node {
int data;
struct Node *next;
};
// 創建節點
struct Node *create_node(int val) {
struct Node *p = malloc(sizeof(*p));
if (!p) return NULL;
p->data = val;
p->next = NULL;
return p;
}
// 尾插法建表(帶頭節點)
struct Node *build_list(int *arr, int n) {
struct Node head = {0, NULL}, *tail = &head;
for (int i = 0; i < n; ++i) {
tail->next = create_node(arr[i]);
tail = tail->next;
}
return head.next; // 返回首數據節點
}
// 遍歷
void print_list(struct Node *head) {
for (struct Node *p = head; p; p = p->next)
printf("%d -> ", p->data);
puts("NULL");
}
// 查找
struct Node *find(struct Node *head, int val) {
for (struct Node *p = head; p; p = p->next)
if (p->data == val) return p;
return NULL;
}
// 插入(在 p 後插入 new_node)
void insert_after(struct Node *p, struct Node *new_node) {
new_node->next = p->next;
p->next = new_node;
}
// 刪除 p 後節點
void delete_after(struct Node *p) {
if (p->next) {
struct Node *tmp = p->next;
p->next = tmp->next;
free(tmp);
}
}
// 銷毀鏈表
void destroy_list(struct Node *head) {
while (head) {
struct Node *tmp = head;
head = head->next;
free(tmp);
}
}
2. 雙向鏈表節點
struct DNode {
int data;
struct DNode *prev, *next;
};
// 支持 O(1) 反向遍歷、任意節點刪除(無需前驅)
3. 鏈表 vs 結構體數組 對比
| 維度 | 結構體數組 | 鏈表 |
| 內存分配 | 連續,編譯期/啟動期固定大小 | 離散,運行期動態申請/釋放 |
| 插入刪除 | O(n) 移動元素 | O(1) 指針重接(已知位置) |
| 隨機訪問 | O(1) 下標 | O(n) 遍歷 |
| 內存開銷 | 無額外指針 | 每節點 +1/2 指針 |
| 緩存友好 | 極佳(空間局部性) | 差(指針跳躍) |
| 適用場景 | 大小固定、高頻隨機訪問 | 大小動態、高頻增刪、未知上限 |
第三部分:實戰項目——學生管理系統(鏈表版)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Student {
int id;
char name[20];
float score;
struct Student *next;
};
struct Student *head = NULL;
// 增
void add_student(int id, const char *name, float score) {
struct Student *p = malloc(sizeof(*p));
p->id = id; strcpy(p->name, name); p->score = score;
p->next = head; head = p; // 頭插
}
// 刪
bool del_student(int id) {
struct Student **pp = &head; // 指向指針的指針,統一處理頭節點
while (*pp) {
if ((*pp)->id == id) {
struct Student *tmp = *pp;
*pp = tmp->next;
free(tmp);
return true;
}
pp = &(*pp)->next;
}
return false;
}
// 查
struct Student *find_student(int id) {
for (struct Student *p = head; p; p = p->next)
if (p->id == id) return p;
return NULL;
}
// 改
bool update_score(int id, float new_score) {
struct Student *p = find_student(id);
if (p) { p->score = new_score; return true; }
return false;
}
// 遍歷輸出
void list_all(void) {
for (struct Student *p = head; p; p = p->next)
printf("ID:%d Name:%s Score:%.1f\n", p->id, p->name, p->score);
}
int main(void) {
add_student(1, "Alice", 90);
add_student(2, "Bob", 85);
list_all();
update_score(1, 95);
del_student(2);
list_all();
// 程序結束前釋放
while (head) { struct Student *t = head; head = head->next; free(t); }
return 0;
}
總結
- 結構體是異構數據聚合,內存對齊決定大小,指針傳遞是函數交互主流。
- 鏈表用指針串聯離散節點,實現動態增刪,犧牲隨機訪問換取靈活性。
- 雙重指針(
**pp)是鏈表刪除/插入統一處理頭節點的神級技巧。
- 實戰中:小規模固定集合用數組,動態不定集合用鏈表,高性能場景考慮內存池+鏈表或跳表/B+樹。