0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

鏈表結(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)該如何定義

電子工程師 ? 來(lái)源:互聯(lián)網(wǎng) ? 作者:佚名 ? 2017-09-20 16:28 ? 次閱讀

近日周立功教授公開了數(shù)年的心血之作《程序設(shè)計(jì)與數(shù)據(jù)結(jié)構(gòu)》,電子版已無(wú)償性分享到電子工程師與高校群體下載,經(jīng)周立功教授授權(quán),特對(duì)本書內(nèi)容進(jìn)行連載。

>>>1.1.1 數(shù)據(jù)與p_next分離

由于鏈表只關(guān)心p_next指針,因此完全沒有必要在鏈表結(jié)點(diǎn)中定義數(shù)據(jù)域,那么只保留p_next指針就好了。鏈表結(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)(slist.h)定義如下:

1 typedef struct _slist_node{

2 struct _slist_node *p_next; //指向下一個(gè)結(jié)點(diǎn)的指針

3 }slist_node_t;

由于結(jié)點(diǎn)中沒有任何數(shù)據(jù),因此節(jié)省了內(nèi)存空間,其示意圖詳見3.10。

圖3.10 鏈表示意圖

當(dāng)用戶需要使用鏈表管理數(shù)據(jù)時(shí),僅需關(guān)聯(lián)數(shù)據(jù)和鏈表結(jié)點(diǎn),最簡(jiǎn)單的方式是將數(shù)據(jù)和鏈表結(jié)點(diǎn)打包在一起。以int類型數(shù)據(jù)為例,首先將鏈表結(jié)點(diǎn)作為它的一個(gè)成員,再添加與用戶相關(guān)的int類型數(shù)據(jù),該結(jié)構(gòu)體定義如下:

1 typedef struct _slist_int{

2 slist_node_t node; //包含鏈表結(jié)點(diǎn)

3 int data; // int類型數(shù)據(jù)

4 }slist_int_t;

由此可見,無(wú)論是什么數(shù)據(jù),鏈表結(jié)點(diǎn)只是用戶數(shù)據(jù)記錄的一個(gè)成員。當(dāng)調(diào)用鏈表接口時(shí),僅需將node的地址作為鏈表接口參數(shù)即可。在定義鏈表結(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)時(shí),由于僅刪除了data成員,因此還是可以直接使用原來(lái)的slist_add_tail()函數(shù),管理int型數(shù)據(jù)的范例程序詳見程序清單3.14。

程序清單3.14管理int型數(shù)據(jù)的范例程序

1 #include

2 typedef struct _slist_int{

3 slist_node_t node;

4 int data;

5 }slist_int_t;

6

7 int main (void)

8 {

9 slist_node_t head = {NULL};

10 slist_int_t node1, node2, node3;

11 slist_node_t *p_tmp;

12

13 node1.data = 1;

14 slist_add_tail(&head, &node1.node);

15 node2.data = 2;

16 slist_add_tail(&head, &node2.node);

17 node3.data = 3;

18 slist_add_tail(&head, &node3.node);

19 p_tmp = head.p_next;

20 while (p_tmp != NULL){

21 printf("%d ", ((slist_int_t *)p_tmp)->data);

22 p_tmp = p_tmp->p_next;

23 }

24 return 0;

25 }

由于用戶需要初始化headNULL,且遍歷時(shí)需要操作各個(gè)結(jié)點(diǎn)的p_next指針。而將數(shù)據(jù)和p_next分離的目的就是使各自的功能職責(zé)分離,鏈表只需要關(guān)心p_next的處理,用戶只關(guān)心數(shù)據(jù)的處理。因此,對(duì)于用戶來(lái)說(shuō),鏈表結(jié)點(diǎn)的定義就是一個(gè)“黑盒子”,只能通過(guò)鏈表提供的接口訪問(wèn)鏈表,不應(yīng)該訪問(wèn)鏈表結(jié)點(diǎn)的具體成員。

為了完成頭結(jié)點(diǎn)的初始賦值,應(yīng)該提供一個(gè)初始化函數(shù),其本質(zhì)上就是將頭結(jié)點(diǎn)中的p_next成員設(shè)置為NULL。鏈表初始化函數(shù)原型為:

int slist_init (slist_node_t *p_head);

由于頭結(jié)點(diǎn)的類型與其它普通結(jié)點(diǎn)的類型一樣,因此很容易讓用戶誤以為,這是初始化所有結(jié)點(diǎn)的函數(shù)。實(shí)際上,頭結(jié)點(diǎn)與普通結(jié)點(diǎn)的含義是不一樣的,由于只要獲取頭結(jié)點(diǎn)就可以遍歷整個(gè)鏈表,因此頭結(jié)點(diǎn)往往是被鏈表的擁有者持有,而普通結(jié)點(diǎn)僅僅代表單一的一個(gè)結(jié)點(diǎn)。為了避免用戶將頭結(jié)點(diǎn)和其它結(jié)點(diǎn)混淆,需要再定義一個(gè)頭結(jié)點(diǎn)類型(slist.h):

typedef slist_node_t slist_head_t;

基于此,將鏈表初始化函數(shù)原型(slist.h)修改為

int slist_init (slist_head_t *p_head);

其中,p_head指向待初始化的鏈表頭結(jié)點(diǎn),slist_init()函數(shù)的實(shí)現(xiàn)詳見程序清單3.15。

程序清單3.15鏈表初始化函數(shù)

1 int slist_init (slist_head_t *p_head)

2 {

3 if (p_head == NULL){

4 return -1;

5 }

6 p_head -> p_next = NULL;

7 return 0;

8 }

在向鏈表添加結(jié)點(diǎn)前,需要初始化頭結(jié)點(diǎn)。即

slist_node_t head;

slist_init(&head);

由于重新定義了頭結(jié)點(diǎn)的類型,因此添加結(jié)點(diǎn)的函數(shù)原型也應(yīng)該進(jìn)行相應(yīng)的修改。

int slist_add_tail (slist_head_t *p_head, slist_node_t *p_node);

其中,p_head指向鏈表頭結(jié)點(diǎn),p_node為新增的結(jié)點(diǎn),slist_add_tail()函數(shù)的實(shí)現(xiàn)詳見程序清單3.16

程序清單3.16新增結(jié)點(diǎn)范例程序

1 int slist_add_tail (slist_head_t *p_head, slist_node_t *p_node)

2 {

3 slist_node_t *p_tmp;

4

5 if ((p_head == NULL) || (p_node == NULL)){

6 return -1;

7 }

8 p_tmp = p_head;

9 while (p_tmp -> p_next != NULL){

10 p_tmp = p_tmp -> p_next;

11 }

12 p_tmp -> p_next = p_node;

13 p_node -> p_next = NULL;

14 return 0;

15 }

同理,當(dāng)前鏈表的遍歷采用的還是直接訪問(wèn)結(jié)點(diǎn)成員的方式,其核心代碼如下:

1 slist_node_t *p_tmp = head.p_next;

2 while (p_tmp != NULL){

3 printf("%d ", ((slist_int_t *)p_tmp)->data);

4 p_tmp = p_tmp->p_next;

5 }

這里主要對(duì)鏈表作了三個(gè)操作:(1)得到第一個(gè)用戶結(jié)點(diǎn);(2)得到當(dāng)前結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn);(3)判斷鏈表是否結(jié)束,與結(jié)束標(biāo)記(NULL)比較。

基于此,將分別提供三個(gè)對(duì)應(yīng)的接口來(lái)實(shí)現(xiàn)這些功能,避免用戶直接訪問(wèn)結(jié)點(diǎn)成員。它們的函數(shù)原型為(slist.h)

slist_node_t *slist_begin_get (slist_head_t *p_head); //獲取開始位置第一個(gè)用戶結(jié)點(diǎn)

slist_node_t *slist_next_get (slist_head_t *p_head, slist_node_t *p_pos);//獲取某一結(jié)點(diǎn)的后一結(jié)點(diǎn)

slist_node_t *slist_end_get (slist_head_t *p_head); //結(jié)束位置,尾結(jié)點(diǎn)下一個(gè)結(jié)點(diǎn)的位置

其實(shí)現(xiàn)代碼詳見程序清單3.17。

程序清單3.17遍歷相關(guān)函數(shù)實(shí)現(xiàn)

1 slist_node_t *slist_next_get (slist_head_t *p_head, slist_node_t *p_pos)

2 {

3 if (p_pos) { //找到p_pos指向的結(jié)點(diǎn)

4 return p_pos->p_next;

5 }

6 return NULL;

7 }

8

9 slist_node_t *slist_begin_get (slist_head_t *p_head)

10 {

11 return slist_next_get(p_head, p_head);

12 }

13

14 slist_node_t *slist_end_get (slist_head_t *p_head)

15 {

16 return NULL;

17 }

程序中獲取的第一個(gè)用戶結(jié)點(diǎn),其實(shí)質(zhì)上就是頭結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn),因此可以直接調(diào)用slist_next_get()實(shí)現(xiàn)。盡管slist_next_get()在實(shí)現(xiàn)時(shí)并沒有用到參數(shù)p_head,但還是將p_head參數(shù)傳進(jìn)來(lái)了,因?yàn)閷?shí)現(xiàn)其它的功能時(shí)將會(huì)用到p_head參數(shù),比如判斷p_pos是否在鏈表中。當(dāng)有了這些接口函數(shù)后,即可完成遍歷,詳見程序清單3.18。

程序清單3.18使用各個(gè)接口函數(shù)實(shí)現(xiàn)遍歷的范例程序

1 slist_node_t *p_tmp = slist_begin_get(&head);

2 slist_node_t *p_end = slist_end_get(&head);

3 while (p_tmp != p_end){

4 printf("%d ", ((slist_int_t *)p_tmp)->data);

5 p_tmp = slist_next_get(&head, p_tmp);

6 }

由此可見,slist_begin_get()和slist_end_get()的返回值決定了當(dāng)前有效結(jié)點(diǎn)的范圍,其范圍為一個(gè)半開半閉的空間,:[begin,end),包括begin,但是不包括end。當(dāng)begin與end相等時(shí),表明當(dāng)前鏈表為空,沒有一個(gè)有效結(jié)點(diǎn)。

程序清單3.18所示的遍歷程序中,只有printf()語(yǔ)句才是用戶實(shí)際關(guān)心的語(yǔ)句,其它語(yǔ)句都是固定的模式,為此可以封裝一個(gè)通用的遍歷函數(shù),便于用戶順序處理與各個(gè)鏈表結(jié)點(diǎn)相關(guān)聯(lián)的數(shù)據(jù)。顯然,只有使用鏈表的用戶才知道數(shù)據(jù)的具體含義,對(duì)數(shù)據(jù)的實(shí)際處理應(yīng)該交由用戶完成,比如,程序清單3.18中的打印語(yǔ)句,因此訪問(wèn)數(shù)據(jù)的行為應(yīng)該由用戶定義,定義一個(gè)回調(diào)函數(shù),通過(guò)參數(shù)傳遞給遍歷函數(shù),每遍歷到一個(gè)結(jié)點(diǎn)時(shí),都調(diào)用該回調(diào)函數(shù)處理對(duì)數(shù)據(jù)進(jìn)行處理。遍歷鏈表的函數(shù)原型(slist.h)為:

typedef int (*slist_node_process_t) (void *p_arg, slist_node_t *p_node);

int slist_foreach(slist_head_t *p_head,

slist_node_process_t pfn_node_process,

void *p_arg);

其中,p_head指向鏈表頭結(jié)點(diǎn),pfn_node_process為結(jié)點(diǎn)處理回調(diào)函數(shù)。每遍歷到一個(gè)結(jié)點(diǎn)時(shí),都會(huì)調(diào)用pfn_node_process指向的函數(shù),便于用戶根據(jù)需要自行處理結(jié)點(diǎn)數(shù)據(jù)。當(dāng)調(diào)用該回調(diào)函數(shù)時(shí),會(huì)自動(dòng)將用戶參數(shù)p_arg作為回調(diào)函數(shù)的第1個(gè)參數(shù),將指向當(dāng)前遍歷到的結(jié)點(diǎn)的指針作為回調(diào)函數(shù)的第2個(gè)參數(shù)。

當(dāng)遍歷到某個(gè)結(jié)點(diǎn)時(shí),用戶可能希望終止遍歷,此時(shí)只要在回調(diào)函數(shù)中返回負(fù)值即可。一般地,若要繼續(xù)遍歷,函數(shù)執(zhí)行結(jié)束后返回0。slist_foreach()函數(shù)的實(shí)現(xiàn)詳見程序清單3.19。

程序清單3.19遍歷鏈表范例程序

1 int slist_foreach( slist_head_t *p_head,

2 slist_node_process_t pfn_node_process,

3 void *p_arg);

4

5 {

6 slist_node_t *p_tmp, *p_end;

7 int ret;

8

9 if ((p_head == NULL) || (pfn_node_process == NULL)){

10 return -1;

11 }

12 p_tmp = slist_begin_get(p_head);

13 p_end = slist_end_get(p_head);

14 while (p_tmp != p_end){

15 ret = pfn_node_process(p_arg, p_tmp);

16 if (ret < 0)???? return ret;????? ???????????? //?不再繼續(xù)遍歷

17 p_tmp = slist_next_get(p_head, p_tmp); //繼續(xù)下一個(gè)結(jié)點(diǎn)

18 }

19 return 0;

20 }

現(xiàn)在可以使用這些接口函數(shù),迭代如程序清單3.14所示的功能,詳見程序清單3.20

程序清單3.20管理int型數(shù)據(jù)的范例程序

1 #include

2 #include "slist.h"

3

4 typedef struct _slist_int {

5 slist_node_t node; //包含鏈表結(jié)點(diǎn)

6 int data; // int類型數(shù)據(jù)

7 }slist_int_t;

8

9 int list_node_process (void *p_arg, slist_node_t *p_node)

10 {

11 printf("%d ", ((slist_int_t *)p_node)->data);

12 return 0;

13 }

14

15 int main(void)

16 {

17 slist_head_t head; //定義鏈表頭結(jié)點(diǎn)

18 slist_int_t nodel, node2, node3;

19 slist_init(&head);

20

21 node1.data = 1;

22 slist_add_tail(&head, &(node1.node));

23 node2.data = 2;

24 slist_add_tail(&head, &(node2.node));

25 node3.data = 3;

26 slist_add_tail(&head, &(node3.node));

27 slist_foreach(&head, list_node_process, NULL); //遍歷鏈表,用戶參數(shù)為NULL

28 return 0;

29 }
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 電子工程師
    +關(guān)注

    關(guān)注

    252

    文章

    766

    瀏覽量

    95502
  • 數(shù)據(jù)結(jié)構(gòu)

    關(guān)注

    3

    文章

    568

    瀏覽量

    40030
  • 周立功
    +關(guān)注

    關(guān)注

    38

    文章

    130

    瀏覽量

    37438
  • 鏈表
    +關(guān)注

    關(guān)注

    0

    文章

    80

    瀏覽量

    10520

原文標(biāo)題:周立功:數(shù)據(jù)與p_next分離

文章出處:【微信號(hào):Zlgmcu7890,微信公眾號(hào):周立功單片機(jī)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    數(shù)據(jù)結(jié)構(gòu)中最簡(jiǎn)單的鏈表

    數(shù)據(jù)結(jié)構(gòu)作為嵌入式工程師必修課程之一,今天,我們就來(lái)講一講數(shù)據(jù)結(jié)構(gòu)中最簡(jiǎn)單的鏈表,包含鏈表的初始化、插入和遍歷操作。 鏈表在項(xiàng)目開發(fā)中使用的
    發(fā)表于 06-13 17:40 ?310次閱讀

    數(shù)據(jù)結(jié)構(gòu):?jiǎn)?b class='flag-5'>鏈表的排序

    給定一個(gè)單鏈表的頭結(jié)點(diǎn)head(結(jié)點(diǎn)有值),長(zhǎng)度為n的無(wú)序單鏈表,對(duì)其按升序排序后,返回新鏈表
    的頭像 發(fā)表于 11-30 13:56 ?1143次閱讀
    <b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>:?jiǎn)?b class='flag-5'>鏈表</b>的排序

    數(shù)據(jù)結(jié)構(gòu):刪除有序鏈表的重復(fù)節(jié)點(diǎn)

    給定一個(gè)有序單鏈表(從小到大有序)的頭結(jié)點(diǎn)head(結(jié)點(diǎn)有值),刪除鏈表中的重復(fù)元素,使鏈表
    的頭像 發(fā)表于 12-05 15:46 ?552次閱讀
    <b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>:刪除有序<b class='flag-5'>鏈表</b>的重復(fù)節(jié)點(diǎn)

    數(shù)據(jù)結(jié)構(gòu)

    1.數(shù)據(jù)結(jié)構(gòu)的概念 所謂數(shù)據(jù)結(jié)構(gòu)是指由某一數(shù)據(jù)對(duì)象及對(duì)象中所有數(shù)據(jù)成員之間的關(guān)系組成的集合。成員之間的關(guān)系有很多種,最常見的是前后件關(guān)系。
    發(fā)表于 03-04 14:13

    Linux Kernel數(shù)據(jù)結(jié)構(gòu):鏈表

    Linux Kernel數(shù)據(jù)結(jié)構(gòu)鏈表原創(chuàng) 2016年10月20日 22:58:25標(biāo)簽:LINUX/kernel/鏈表 數(shù)據(jù)結(jié)構(gòu)數(shù)據(jù)結(jié)構(gòu)
    發(fā)表于 09-25 16:41

    數(shù)據(jù)結(jié)構(gòu)之鏈?zhǔn)綏=榻B

    數(shù)據(jù)結(jié)構(gòu)之鏈?zhǔn)綏f準(zhǔn)綏f準(zhǔn)綏5?b class='flag-5'>定義鏈?zhǔn)綏2僮鞯膶?shí)現(xiàn)鏈?zhǔn)綏3跏蓟準(zhǔn)綏H霔f準(zhǔn)綏3鰲f準(zhǔn)綏3跏蓟準(zhǔn)綏f準(zhǔn)綏o(wú)棧滿問(wèn)題,空間可以擴(kuò)充插入與刪除僅在棧頂處執(zhí)行鏈?zhǔn)綏5臈m斣阪滎^鏈?zhǔn)綏5?b class='flag-5'>定義 //
    發(fā)表于 12-17 08:11

    數(shù)據(jù)結(jié)構(gòu)鏈表的基本操作

    嵌入式學(xué)習(xí)基礎(chǔ)-數(shù)據(jù)結(jié)構(gòu)鏈表的基本操作鏈表節(jié)點(diǎn)采用結(jié)構(gòu)體的方式進(jìn)行定義,下面是最基礎(chǔ)的定義只有一
    發(fā)表于 12-22 08:05

    在RT-Thread中普通鏈表和侵入式鏈表有何區(qū)別

    普通鏈表學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的時(shí)候?qū)懙?b class='flag-5'>鏈表是下面這個(gè)樣子侵入式鏈表在 RT-Thread 以及 Linux 內(nèi)核中鏈表是這樣
    發(fā)表于 04-11 15:15

    Linux內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)的一點(diǎn)認(rèn)識(shí)

    成員,那么到時(shí)候鏈表中沒有任何數(shù)據(jù),這樣的鏈表有什么用呢?其實(shí)這就是內(nèi)核鏈表設(shè)計(jì)的巧妙之處,因?yàn)樵谡麄€(gè)內(nèi)核中需要使用鏈表來(lái)存放的
    發(fā)表于 04-20 16:42

    RT-Thread中侵入式鏈表的應(yīng)用有哪些呢

    侵入式鏈表普通鏈表學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的時(shí)候?qū)懙?b class='flag-5'>鏈表是下面這個(gè)樣子的typedef struct LNode{int data;/* 數(shù)據(jù)
    發(fā)表于 12-05 13:59

    算法與數(shù)據(jù)結(jié)構(gòu)——雙向鏈表

    第三章為算法與數(shù)據(jù)結(jié)構(gòu),本文為3.3 雙向鏈表。
    的頭像 發(fā)表于 09-19 17:56 ?7205次閱讀
    算法與<b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>——雙向<b class='flag-5'>鏈表</b>

    鏈表的基礎(chǔ)知識(shí)

    在學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)的時(shí)候,最開始接觸到的一種數(shù)據(jù)結(jié)構(gòu)就是線性表,對(duì)于線性表的定義是: **零個(gè)或多個(gè)數(shù)據(jù)元素的有限序列** ,那對(duì)于線性表來(lái)講,又分為順序存儲(chǔ)
    的頭像 發(fā)表于 01-20 17:00 ?933次閱讀
    <b class='flag-5'>鏈表</b>的基礎(chǔ)知識(shí)

    LeetCode876鏈表的中間結(jié)點(diǎn)介紹

    給定一個(gè)頭結(jié)點(diǎn)為 head 的非空單鏈表,返回鏈表的中間結(jié)點(diǎn)。
    的頭像 發(fā)表于 01-11 17:58 ?753次閱讀
    LeetCode876<b class='flag-5'>鏈表</b>的中間<b class='flag-5'>結(jié)點(diǎn)</b>介紹

    Linux內(nèi)核的鏈表數(shù)據(jù)結(jié)構(gòu)

    Linux內(nèi)核實(shí)現(xiàn)了自己的鏈表數(shù)據(jù)結(jié)構(gòu),它的設(shè)計(jì)與傳統(tǒng)的方式不同,非常巧妙也很通用。
    的頭像 發(fā)表于 03-24 11:34 ?741次閱讀
    Linux內(nèi)核的<b class='flag-5'>鏈表</b><b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>

    鏈表數(shù)據(jù)結(jié)構(gòu)基本概念

    的必要元素。 頭節(jié)點(diǎn): 頭結(jié)點(diǎn)是為了操作的統(tǒng)一和方便而設(shè)立的,放在第一元素的結(jié)點(diǎn)之前,其數(shù)據(jù)域一般無(wú)意義(也可存放鏈表的長(zhǎng)度)。 有了頭結(jié)點(diǎn)
    的頭像 發(fā)表于 07-27 11:14 ?716次閱讀
    <b class='flag-5'>鏈表</b><b class='flag-5'>數(shù)據(jù)結(jié)構(gòu)</b>基本概念