多級指標和鍊錶

2021-08-25 16:11:06 字數 2950 閱讀 8806

如果看到乙個宣告:type **********************ptr;你會怎麼想?估計一半人都瘋了,如此宣告乙個變數的人本身要麼是乙個高手,要麼是乙個低能。這樣的一排*事實上表示的是乙個鍊錶,鍊錶上的每乙個元素可以分布在記憶體的任意乙個位置,它們之間每兩個通過乙個*相聯絡。*p定義乙個指標,p指向乙個記憶體位置,該位置中儲存p宣告的資料型別,而**p表示乙個指標的指標,p指向乙個位置,該位置存放乙個指標,後者指向p宣告的資料型別,類似的***p也一樣表示乙個指標,****p也沒什麼區別,這麼一大堆指標真的就是乙個鍊錶了,以下是乙個例子:

void test()

會列印什麼呢?l1,l2,l3,l4都在棧上分配,其位址當然也在棧上,1處將列印l2的位址,由於l2指向的也是乙個位址,2處將列印l3的位址,同樣3處列印l4的位址,4處最終解碼出了0x1234這個數字,是這樣嗎?測試一下就知道了。由此可見**********...確實可以當成鍊錶的next來使用,每增加乙個*,鍊錶就往前推進乙個元素,相當於引用了當前節點的next節點。由於指標實際上是乙個位址,因此構造這個******...鍊錶的時候一定要注意每乙個節點都必須是通過&取到的真正的位址,而不能任意賦值,否則就會造成訪存違規,程式出錯。既然使用了*****...這麼一大堆指標,你就要保證使用(*****...)p解碼節點元素的時候每一級都是乙個位址,上述的例子通過&取到了位址,然後賦值給乙個指標,最終的那個l1其實就是乙個4級指標,****p最終指向0x1234。

理解了多重指標的本質,下面需要將其構造成乙個真正的鍊錶。上面的test函式僅僅將lx宣告成int型別的指標,乙個int型的資料存放在記憶體的乙個位置,既然通過*相聯絡的節點沒有必要相鄰,那麼乙個int型的指標除了存放指標值之外,該值所在位址下面或者上面的一部分相鄰的位址空間也是可以被利用的,比如:

int *p1;

int *p2 = &p1;

int *p3 = &p2;

這裡的p1,p2,p3都是指標,也就是乙個位址,那麼px+/-y的區域都可以被使用,每乙個p將得到乙個y+sizeof(int *)的連續空間範圍,這個範圍內可以儲存自己的資料,最終通過****...可以解碼到某個節點,然後再該節點附近大小為y的範圍內就可以找到自己的資料,由此可以設計出以下的資料結構:

struct lst;

struct lst ;

可見,lst是乙個結構體,攜帶了乙個value資料,這裡不要太在意fuc(?)字段,它的位址其實就是lst的位址,而它的內容則是next的位址,通過該欄位可以將幾個lst結構體鏈結在一起,這是c語言的要求,和上述int型別測試時的不斷&取位址是一致的。接下來看一下新的test2函式,它演示了***...和鍊錶的同一性:

void test2()

顯然,列印結果如下:

? value:0x111

? value:0x222

? value:0x333

? value:0x444

結構體lst實際上和上述的int型別的指標是一樣的,只不過在其指標位址的下面又攜帶了乙個int型的字段value,這樣的話乙個鍊錶就可以攜帶資料了。

進一步思考,既然鏈表現在可以攜帶資料了,那麼攜帶些什麼資料呢?是不是每乙個攜帶有資料的鍊錶都要構造乙個結構體呢?這樣是不是太浪費了,鍊錶操作api又不能統一。我們反過來想這個問題,既然鍊錶可以攜帶資料,然後通過鍊錶中的fuc(next)字段或者通過***...找到這個資料,那麼資料是不是也可以攜帶乙個鍊錶呢?這是可以的,它們都在連續的記憶體空間內,對於位址空間而言,誰攜帶誰是無所謂的,我們需要做的僅僅是管理好鍊錶的next指標即可,比方說乙個資料結構data:

struct data

由此,value欄位進入了data,value就沒有必要存在於lst了,由此lst成了以下的樣子:

struct lst

如果我們現在擁有乙個data例項,可以在它的首位址下面偏移4位元組(other的大小)處取出它的lt欄位,也就是乙個鍊錶,反過來如果我們擁有乙個鍊錶節點例項,也可以在這個上面上面偏移4位元組處取到乙個data例項。看一下這像什麼?是不是linux核心的list_head?只不過list_head是乙個雙向鍊錶,還有乙個prev欄位,這個通過list找data的過程也就是linux核心的container_of巨集:

#define container_of(ptr, type, member) ()

可以看到,必須指定乙個資料型別和該list_head在該資料型別中的欄位名。正是虛擬記憶體空間的連續性使得我們可以抽出list_head這個結構,使嵌入式鍊錶成為可能,這樣就可以單獨為list_head設計一套api了,而不用關心它攜帶的是什麼資料,實際上它從來不攜帶資料,而是其它資料結構例項攜帶了它。

*****...是乙個鍊錶,任意的多級指標都是乙個鍊錶,理解了這個之後,不但可以單獨設計出諸如list_head之類的linux核心的核心資料結構,對於hlist_head的理解也加深了一步:

struct hlist_head ;

struct hlist_node ;

hlist_head中只有乙個指向hlist_node的指標,而hlist_node中卻有乙個**型別的指標,也就是乙個指標的指標,證明它指向的乙個元素是乙個指標指向另乙個元素,指向誰呢?其實是指向該hlist_node自己的,指向自己?豈不多此一舉?事實上它在刪除節點的時候十分有用,它可以直接修改前乙個節點的next欄位,使之指向當前被刪除節點的後乙個節點,整個過程不用考慮前乙個節點到底是hlist_node還是hlist_head,只要它有乙個hlist_node型別的指標欄位就可以了,這麼一來就解放了hlist_head,使得乙個和hlist_node截然不同的資料型別可以連線進雜湊桶,而這個hlist_head中只有乙個字段,大大節省了空間(為何hlist_node不去掉pprev呢,這樣不更節省空間?是節省了空間,然而刪除的時候就麻煩了,需要遍歷操作,找到這個需要刪除節點的位置)。

從*******...一路上到達了hlist,事實上全部可以用指標來說話,指標真的是乙個了不起的特性,c語言正是因為有了指標才可以訪問位址空間任意的位址,正是這種便利性,作業系統核心用c寫是最好的,c提供了機器的乙個最好的抽象,不多也不少,而這個最好是通過指標來實現的。

指標 引用和鍊錶

還是課堂的筆記。記憶體中的乙個位元組為乙個儲存單元 byte 儲存單元的編號是位址 變數的位址是該變數所在儲存區域的第乙個位元組 單元 的位址 位址成為變數的指標 定義格式 型別說明符 指標變數名 int pi float pf 1 乙個指標變數只能指向統一資料型別的變數。在定義指標變數時明確給定的...

指標和鍊錶訓練題目

training5 指標和鍊錶訓練 1.題目 有n個人圍成一圈,順序排號,從第乙個開始報數 從1到3報數 凡報到3的人退出圈子,問最後最後留下的是原來第幾號的那位.include include typedef struct baoshu b int main head p p1 p printf ...

C 多級指標

可以認為,指標是c 這把寶劍最鋒利的部分,當然,如果你使用不當,也會傷到自己的 何為11級指標,其實,就是在指標前面加了11個 加乙個 就是1級指標,加兩個 就是二級指標 char qqptr null 指標和陣列有著扯不清的關係,這個11級指標,你可以理解為11維度的陣列,平日裡見得最多的恐怕也就...