C C 中煉表的三種設計方案

2022-04-28 22:39:18 字數 4217 閱讀 4380

1. 一般做法,用具體資料結構封裝鍊錶。

struct data

; 上邊這個例子,在data結構中有個next域,通過這個就可以組成乙個鍊錶,這個是我讀書時最常用的模式。缺點在於:每種具體的結構都要寫一遍鍊錶的增刪查改操作,重複了n多。

2. stl的做法:用鍊錶封裝具體資料結構。

struct data

; ...

listldata;

這種型別用到c++ stl的模板類list<>,之所以用指標,是因為在拷貝的時候不會頻繁的呼叫data類的拷貝建構函式。在c語言中沒有模板,怎麼辦?我倒是有個辦法:

struct list

; void addelem(struct list list, void *data);

...

struct  data

; ...

struct data d;

d.data = 5;

d.f = 6.0f;

list ldata;

addelem(struct list list, (void *)&d );

...

那個鍊錶list結構體就可以獨立於各種型別,到用到的時候再給據情況轉回來就可以了。在核心中有一部分**就是這麼做的。

3. 用具體資料結構封裝通用鍊錶。(linux核心的通用做法)

struct list_head

; #define list_head(name) struct list_head name = list_head_init(name)

#define list_head_init(ptr) /

do while(0)

/* 還為這個鍊錶定義一些通用操作和巨集 */

/* 在鍊錶頭部head後面插入新元素 */

list_add(struct list_head *new, struct list_head *head);

/* 從給定的入口將鍊錶entry刪除 */

list_del(struct list_head *entry);

...

struct data

; 好,先說一下上面的巨集list_head_init(ptr),可能會覺得奇怪,為什麼會用這種結構do...while(0)呢,不妨先想想。

。。。

揭底吧:因為這是2條語句,如果這樣

#define list_head_init(ptr) (ptr)->next = (ptr); (ptr)->prev = ptr;

那在下面的情況下就不能通過。

if(condition)

list_head_init(ptr);

else

...

這樣會編譯錯誤。

如果反過來就更糟糕,雖然編譯不錯,但執行結果肯定不是你想要的:

if(condition)

...

else

list_head_init(ptr); 

也許會想到這個樣子定義巨集,加個大括號:

#define list_head_init(ptr)

但是這樣的話在上面的語句裡面編譯一樣通不過:

if(condition)

list_head_init(ptr);

else

...

注意到分號就可以知道它編譯通不過,因為那個分號代表if分支的結束,下面那個else語句就無法與上面的if語句匹配。

當然可以限制說這個巨集不需要分號,...汗,這樣不是限制更多了。那有人記得住什麼情況用不用分號啊。所以就只能用這種情形:do...while(0);來封裝一堆語句了。

好了,言歸正傳。下面繼續,

上邊定義了乙個鍊錶結構體:struct data;

現在假設鍊錶已經建立好,要訪問struct data ld;的下乙個節點。怎麼辦?

這還不簡單,看:

struct list_head *pnode = ld.list.next;

...

然後呢?注意我要反問的是struct data結構的下乙個節點,而不是list_head的下乙個節點。

想...想

好,揭底:其實,核心裡面的**有很大的篇幅都是根據偏移量來計算變數的位址的,不向使用者程式,通過成員變數名來訪問。下面看一下這個巨集的基本實現:

#define entry_list(ptr, type, member) /

(type *)( (char *)ptr - ((char *)&((type *)0)->member) )

其中,ptr 是 鍊錶的乙個struct list_head的元素指標,

type 是 包含struct list_head結構成員的結構體

member是 使用者在type中定義那個struct list_head的成員的名字。

例如,通過pnode來取得它相對應的struct data結構體:

struct data *d = entry_list(&data.list, struct data, list);

進行一些說明:

以上的巨集的實現只是乙個基本實現,在linux核心的**中,那個東西還有一些別的因素要考慮的,因此**可能更為複雜一些。

巨集entry_list的實現當中,是通過計算元素位址的偏移量來取得對應資料結構變數的位址。這在核心中是很常用的。當中的(char *)ptr是鍊錶成員的位址,然後((char *)&((type *)0)->member)是把struct data放到記憶體為0處時計算出的member的偏移量,所以相減就可以得到這個struct data的位址(稱為宿主)

由此可見,只要有心,linux核心的**中處處都是可以學習的東西,大到整體設計,小到一些具體的技巧。都是思維的寶藏。

以上只是粗略的說說,如果要更詳細的解釋,在google 上搜 「linux 核心 鍊錶"吧,嘿嘿

再說一些題外的東西:

我看linux核心中程序的狀態的時候,發現一些資料:

#define task_running 0

#define task_interruptible 1

#define task_uninterruptible 2

#define task_stopped 4

#define task_traced 8

#define task_zombie 16

#define task_dead 32

第一次讀這些的時候我沒有留意,第二次讀的時候我突然間想到,如果是我定義的話,我會將這些巨集定義為:0 1 2 3 4 5...那麼它為什麼要這樣定義呢?

這麼一想,我注意到這些都是2的冥,也就是說,在二進位制中都是某一位為1,剩下的為0.

然後我聯想到以前那些c語言的檔案呼叫的狀態引數有那麼多的或運算子,就大致清楚這些東西的作用了,可以很方便的進行狀態的組合,只要把這些狀態用'或'運算子即可。

這麼一想,使我聯絡到了前段日子看的《程式設計之美》的乙個章節,問的問題:要測試乙個三角形,應該定義測試狀態,答案是這個三角形可能是:銳角,鈍角,直角,等腰,等邊...其中有些狀態可以重複,怎麼辦呢?我以前一般是這樣想的,用0,1,2,3,4來定義那些東西,比如等腰是1,直角是2, 等腰直角是3等等,但是這樣有一大堆重複的狀態,不優雅啊

現在才知道(可見我不大符合做技術,因為總是要看過別人的東西才能聯想起,而不能自己進行創新),可以通過把這些狀態分割成一組互相獨立的狀態,比如等腰是1,直角是2,等邊是4,等等,都是2的冥,如果測試是等腰直角的話,可以返回 "(等腰) | (直角) " = 1 | 2 = (二進位制數的11),這樣就可以很輕鬆的解決啦。

剛才寫的時候,又聯想起高中學物理的正交分解了...無語啊,總是看過之後才能想起,其實,這個不就是正交分解在計算機的應用嘛?誒,早就已經知道的東西,卻不懂得應用,這就是大師和普通人的區別了吧。可能是?我也許一輩子是乙個再也平凡不過的人罷了。

好了,不發牢騷了,呵呵。金剛經有言曰:「應無所住,而生其心」。 懂過了就算了啊,無需執著一切法相。

三種許可權設計方案的歸納和比較

許可權設計是很多系統重要的組成部分,主要用於控制功能和流程,本文將幾種常見的許可權設計方案 許可權系統的名都是自己起的 的基本設計寫出來,其中不恰當處還請大家指出,我們來討論一下.1.等級許可權系統 這種許可權系統在論壇中很常見,在這種系統中,許可權級別如同官階從低到高排列,每個使用者擁有乙個許可權...

鍊錶的三種實現

原題 頻繁的插入和刪除就用鍊錶,陣列可以實現隨機訪問,鍊錶不能 題解 方法一 陣列解法 includeusing namespace std int n,a,b,k,v 100000 struct studentd 100000 int main else cin n for int i 1 i n...

資料庫表的設計方案

1 一對多或者多對一的物件在資料庫裡面如何設定表來儲存資料原理解說當在程式中物件的關係為1對多或者多對1的關係時,在資料庫裡面我們怎樣設計表來儲存資料呢?1 首先分別設計兩個表來儲存兩個物件的基本屬性,不用管他們之間的關係 2 然後再在多的物件的表裡面設定外來鍵來描述兩個表之間資料的關係即可滿足需求...