C 语言笔记: 链表节点实现技巧--struct的妙用
????鏈表節點實現技巧–struct的妙用
作者能力有限, 如果您在閱讀過程中發現任何錯誤, 還請您務必聯系本人,指出錯誤, 避免后來讀者再學習錯誤的知識.謝謝!
廢話
C 語言雖然只提供了非常簡單的語法,但是絲毫不影響 C 語言程序員使用 C 來實現很多讓人嘆為觀止的高級功能.
本文介紹一項在 C 語言中非常常見的鏈表節點實現的一個技巧.
也許你看過了好幾本 C 語言的書籍,也看到過相關的介紹,但是你卻沒有很在意,那么這里我們來詳細的學習一下.
接下來,我們將描述一個鏈表節點的實現,先不要失望,它的實現可能并不像所想的那么簡單.
節點的定義
typedef struct _LIST_ENTRY {struct _LIST_ENTRY *Next; } LIST_ENTRY, *PLIST_ENTRY;LIST_ENTRY 代表雙向鏈表的一個節點. Next 是指向下一個節點的指針.
但是對于上述節點,我們沒法使用它, 因為它除了能表示一個節點之外, 無法包含其他任何額外的信息.
好,這里我們假設我們想創建一個表示學生的鏈表,我們先定義一下學生結構吧.
typedef struct _STUDENT {char name[64];int age; } STUDENT, *PSTUDENT;我們隨手就寫出來一個表示學生的結構體,它很簡單,是因為這里我們只是用它來說明我們如果使用 LIST_ENTRY, 而并不想講解如果構建一個學生管理系統.
為了讓 STUDENT 結構可以成為鏈表的一個節點,我們需要將他們合并一下. 然后我們的 STUDENT 結構就變成了這樣:
typedef struct _STUDENT {LIST_ENTRY list_entry;char name[64];int age; } STUDENT, *PSTUDENT;注意,我們將 LIST_ENTRY 結構嵌套在 STUDENT 結構的開始位置,這將使得后續的實現簡單很多. 放在其他位置當然也是可以的,但是卻會把事情搞得復雜起來.
使用
既然結構體定義好了,下面我們就來看看,我們如何使用這個結構體,以及這個結構體體的巧妙之處,這也是本文想要表達的東西.
再次重申一下,本文是想描述這個結構體用法的妙處,無意于實現一個完整的鏈表. 因此只給出了最簡陋的版本.
#define GET_STUDENT(address, type, field) ((type *)( \(char *)(address) - \(char *)(&((type *)0)->field)))PLIST_ENTRY list_header = NULL; // 鏈表頭// 在鏈表的尾部添加一個新的節點 int add_student(char* name, int age) {// create a student with the given parametersPSTUDENT student = malloc(sizeof(STUDENT));if (student == NULL)return -1;memset(student, 0, sizeof(STUDENT));strcpy(student->name, name);student->age = age;if (list_header == NULL) {list_header = &student->list_entry;} else {PLIST_ENTRY p = list_header;while (p->Next) {p = p->Next;}p->Next = &student->list_entry;// student->list_entry.Next is NULL} }int main() {// 添加兩個節點add_student("student abc", 22);add_student("student ijk", 25);// 遍歷整個鏈表請注意這里!!!!/for (p = list_header; p != NULL; p = p->Next) {// get the student structPSTUDENT student = GET_STUDENT(p, STUDENT, list_entry);// PSTUDENT student = (PSTUDENT)(((char*)p - (char*)(&((PSTUDENT)0)->list_entry)));printf("student name: %s, student age: %d\n", student->name, student->age);}/// 省略釋放內存的代碼return 0; }解析
如果至此,你已經看到了它的巧妙之處,就不需要再浪費時間看接下來的部分了.
上述代碼的重點在哪兒呢?
重點就是 GET_STUDENT 那個宏.
為了方便調試,我們在提供了 42 行的宏展開之后的形式以方便調試.
首先,在 42 行加個斷點調試一下,我們得到了如下結果:
請注意,我們此時 p 的地址和 student 的地址是一樣的. 這是因為我們 LIST_ENTRY 放在 STUDENT 結構體的第一個位置,而我們在往鏈表中添加新節點的時候添加的都是 STUDETN 結構,在這種情況下,我們可以將一個 STUDENT 結構的指針賦值給一個 LIST_ENTRY 的指針.
這里我們在看一樣整個 student 結構的內部布局:
這里首先看到我們 student 的內存開始地址為 0x00000000600049fb0, 這個值和我們上圖中顯示的一致的,因為我的計算機是 64 位機器,因此地址占用 8 的字節.
這里我們詳細解析一下這些字節的意義:
(1) 因為我們 student 的第一個字段是 LIST_ENTRY,而 LIST_ENTRY 中僅包含一個指向鏈表下一個節點的 Next 指針. 因此毫無疑問前八個字 10a00400 06000000表示當前字節的下一個節點的首地址. 注意這里是小段表示,因此和第一張圖片中看到的地址字節序列正好相反, 最高有效位在最后.
(2) 解析來的字節值止倒數第三個字節的內存都是用來保存 student->name
(3) 最后兩個字節表示 student->age,它的值是小段編碼的 16.
4. 接著,我們讓循環繼續,定位鏈表的第二個節點,看一下內存布局:
驗證一下我們上面說的對不對. 第二個節點的地址為 0x0000000060004a010, 它正好與 10a00400 06000000 吻合,因為我們知道第一個節點的 list_entry->Next 指向的正是當前節點. 它的前兩個字節為 0,是因為當前節點的 Next 是空. 接下來的字段與 (2)(3) 小結相同,這里我們不再解釋.
總結, LIST_ENTRY 與 STUDENT 的結構體巧妙的使用了 C 語言結構體內存布局的特點, 將 STUDENT 結構體置入一個 LIST_ENTRY 的鏈表. 它的優點是什么呢?
這就使得我們可以將任何結構放入我們使用 LIST_ENTRY 定義的鏈表中,而我們不必為每一個需要放入鏈表的結構單獨定義相關的字段使得他們得以互聯. 這樣做之后我們等于將鏈表相關的邏輯從真正用來保存信息的結構體中抽離出來,我們在編寫操作鏈表的方法時幾乎可以不關注存入鏈表中的真正的 student 類型.
歡迎交流任何想法.
End…
總結
以上是生活随笔為你收集整理的C 语言笔记: 链表节点实现技巧--struct的妙用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python字符串筛选输出_如何在Pyt
- 下一篇: STM32与MS5837压力传感器的I2