国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
C語言的那些小秘密之鏈表(一)

C語言的那些小秘密之鏈表(一)

分類: 【C語言的那些小秘密】 586人閱讀 評論(5) 收藏 舉報(bào)
鏈表,一個對于學(xué)習(xí)過C語言的人都是再熟悉不過的概念了,可能很多學(xué)習(xí)過鏈表的人都覺得鏈表沒什么值得太在意的地方,可是如果你走進(jìn)linux內(nèi)核,去看看linux內(nèi)核里面鏈表的實(shí)現(xiàn)方式,你不得不為之驚嘆。可能有人會覺得linux內(nèi)核鏈表實(shí)現(xiàn)方式僅此而已,但是你要知道,如果你沒有見到這樣的實(shí)現(xiàn)方式之前,能寫出那樣的鏈表嘛?所以在寫鏈表的文章時(shí),我深知自己不可能用一篇文章來講解完鏈表的知識點(diǎn),所以我特地分為三個部分(單鏈表、雙鏈表、linux內(nèi)核鏈表,而其中l(wèi)inux內(nèi)核鏈表單獨(dú)拿出來講是因?yàn)樗奶厥庑?,在后面的博客中我們再來?xì)談它)來進(jìn)行講解,盡可能用簡短的文字描述加上簡單易懂的代碼來向讀者講解我所理解的鏈表,希望我所講的鏈表能都對你有所幫助。接下來言歸正傳,開始我們的鏈表之旅。

那么什么是鏈表呢?鏈表是一種物理存儲單元上非連續(xù)、非順序的存儲結(jié)構(gòu),數(shù)據(jù)元素的邏輯順序是通過鏈表中的指針鏈接次序?qū)崿F(xiàn)的。鏈表由一系列結(jié)點(diǎn)組成,即鏈表中的每個元素,結(jié)點(diǎn)可以在運(yùn)行時(shí)動態(tài)生成。每個結(jié)點(diǎn)均由兩個部分所組成:一個是存儲數(shù)據(jù)元素的數(shù)據(jù)域,另一個是存儲相鄰結(jié)點(diǎn)地址的指針域。相比于線性表順序結(jié)構(gòu),鏈表比較方便插入和刪除操作。

對于鏈表我們可以將其分為單鏈表、雙向鏈表和循環(huán)鏈表等。首先我們先講講單鏈表。

所謂單鏈表,是指數(shù)據(jù)結(jié)點(diǎn)是單向排列的。一個單鏈表結(jié)點(diǎn),其結(jié)構(gòu)類型分為兩部分:

1、數(shù)據(jù)域:用來存儲本身數(shù)據(jù)

2、指針域:用來存儲下一個結(jié)點(diǎn)地址或者說指向其直接后繼的指針。

如下圖所示:

注意:如果有圖中的紅色箭頭部分,則變?yōu)榱藛蜗蜓h(huán)鏈表。

那什么又是雙鏈表呢?雙向鏈表其實(shí)是單鏈表的改進(jìn)。當(dāng)我們對單鏈表進(jìn)行操作時(shí),有時(shí)你要對某個結(jié)點(diǎn)的直接前驅(qū)進(jìn)行操作時(shí),又必須從表頭開始查找。這是由單鏈表結(jié)點(diǎn)的結(jié)構(gòu)所限制的。因?yàn)閱捂湵砻總€結(jié)點(diǎn)只有一個存儲直接后繼結(jié)點(diǎn)地址的鏈域,那么能不能定義一個既有存儲直接后繼結(jié)點(diǎn)地址的鏈域,又有存儲直接前驅(qū)結(jié)點(diǎn)地址的鏈域的這樣一個雙鏈域結(jié)點(diǎn)結(jié)構(gòu)呢?這就是雙向鏈表。

在雙向鏈表中,結(jié)點(diǎn)除含有數(shù)據(jù)域外,還有兩個鏈域,一個存儲直接后繼結(jié)點(diǎn)地址,一般稱之為右鏈域;一個存儲直接前驅(qū)結(jié)點(diǎn)地址,一般稱之為左鏈域。

如下圖所示:

 

注意:如果有圖中的紅色箭頭部分,則變?yōu)榱穗p向循環(huán)鏈表。

看完了上面的介紹之后,我想讀者對于鏈表算是有了一個大致的了解。那么接下來我們的任務(wù)就是學(xué)習(xí)如何使用鏈表,我們從最簡單的代碼開始,教會讀者來學(xué)習(xí)使用鏈表,首先我們來看一個單鏈表的創(chuàng)建。為了便于講解,我在此就把所有的代碼放到一個源文件中來執(zhí)行了,當(dāng)然讀者在實(shí)際編寫代碼的過程中不管是為了維護(hù)還是閱讀都應(yīng)該使用頭文件,而不要在一個源文件中一條龍似地寫到底。

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>
#define N 3
#undef  _EXAM_ASSERT_TEST_    //禁用
//#define  _EXAM_ASSERT_TEST_   //啟用
#ifdef _EXAM_ASSERT_TEST_     //啟用斷言測試
 void assert_report( const char * file_name, const char * function_name, unsigned int line_no )
{
 printf( "\n[EXAM]Error Report file_name: %s, function_name: %s, line %u\n",
         file_name, function_name, line_no );
  abort();
}
#define  ASSERT_REPORT( condition )       \
 do{       \
 if ( condition )       \
  NULL;        \
 else         \
  assert_report( __FILE__, __func__, __LINE__ ); \
 }while(0)
#else // 禁用斷言測試
#define ASSERT_REPORT( condition )  NULL
#endif /* end of ASSERT */
typedef enum _SListReturn
{
 SLIST_RETURN_OK
}SListReturn;
typedef struct node
{
 char name[10];
 int score;
 struct node *link;
}stud;
stud * creat(int n)
{
 stud *p,*h,*s;
 int i;
 if((h=(stud *)malloc(sizeof(stud)))==NULL)
 {
  printf("分配內(nèi)存空間失敗!");
  exit(0);
 }
 h->name[0]='\0';
 h->score=0;
 h->link=NULL;
 p=h;
 for(i=0;i<n;i++)
 {
  if((s= (stud *) malloc(sizeof(stud)))==NULL)
  {
   printf("分配內(nèi)存空間失敗!");
   exit(0);
  }
 p->link=s;
 printf("請輸入第%d個人的姓名:",i+1);
 scanf("%s",s->name);
 printf("請輸入第%d個人的成績:",i+1);
 scanf("%d",&s->score);
 s->link=NULL;
 p=s;
 }
 return h;
}
SListReturn destroy(stud* head)
{
 stud* tmp,*next;
 tmp=head;
 while(tmp!=NULL)
 {
  next=tmp->link;
  tmp->link=NULL;
  free(tmp);
  tmp=next;
 } 
 return SLIST_RETURN_OK;
}
SListReturn print(stud* head)
{
 stud* tmp=head->link;
 while(tmp!=NULL)
 {
  printf("%s的成績?yōu)?d\t",tmp->name,tmp->score);
  tmp=tmp->link;
 }
 return SLIST_RETURN_OK;
}
void main()
{
 int number;
 stud *head;
 number=N;
 head=creat(number);
 ASSERT_REPORT(print(head)==SLIST_RETURN_OK);
 ASSERT_REPORT(destroy(head)==SLIST_RETURN_OK);
}

運(yùn)行結(jié)果為:

root@ubuntu:/home/paixu# ./tt
請輸入第1個人的姓名:rewq
請輸入第1個人的成績:123
請輸入第2個人的姓名:fdsa
請輸入第2個人的成績:456    
請輸入第3個人的姓名:vcxz
請輸入第3個人的成績:789
rewq成績?yōu)?23  fdsa成績?yōu)?56  vcxz成績?yōu)?89 

看了上面的代碼,如果讀過我之前寫的那篇《C語言的那些小秘密之?dāng)嘌浴返淖x者就知道,在這段代碼的紅色部分我們使用了自己實(shí)現(xiàn)的斷言,學(xué)以致用嘛,所有特此在這里拿出來使用下,如果還沒有看那篇文章的讀者,我建議你看看,畢竟斷言還是很有用的。代碼的藍(lán)色部分也算是一個特色點(diǎn),因?yàn)橐郧翱赡芪覀冊谧约旱拇a中都是返回一些int型值或者NULL之類的,使得代碼的返回值不能夠直觀的體現(xiàn)出運(yùn)行結(jié)果,也使得代碼的可讀性比較差,所有為了改善我們的代碼,我們要學(xué)習(xí)自己定義返回類型,做到盡可能的從各個方面去改善我們編寫的代碼。在這里我們自己用枚舉型的數(shù)據(jù)結(jié)構(gòu)來定義了返回類型,因?yàn)榇a的關(guān)系,我這里僅僅使用了一個返回類型SLIST_RETURN_OK,根據(jù)自己代碼的需要讀者可以自己添加編寫更多的返回值,我僅僅是在這里舉出一個例子。如:

typedef enum _SListReturn
{
 SLIST_RETURN_OK,
 SLIST_RETURN_STOP,
 SLIST_RETURN_FAIL
}SListReturn;

現(xiàn)在我們從以上代碼中選出部分代碼來加注釋進(jìn)行講解。

stud *p,*h,*s; /* *h保存表頭結(jié)點(diǎn)的指針,*p指向當(dāng)前結(jié)點(diǎn)的前一個結(jié)點(diǎn),*s指向當(dāng)前結(jié)點(diǎn)*/

h->link=NULL; /*把表頭結(jié)點(diǎn)的鏈域置空*/

p=h; /*p指向表頭結(jié)點(diǎn)*/

 p->link=s; /*把s的地址賦給p所指向的結(jié)點(diǎn)的鏈域,這樣就把p和s所指向的結(jié)點(diǎn)連接起來了*/

在代碼中除了創(chuàng)建鏈表的函數(shù)外,我們還使用了兩個函數(shù),一個是SListReturn print(stud* head)函數(shù),通過該函數(shù)我們打印輸出鏈表中的數(shù)據(jù)。另一個函數(shù)是SListReturn destroy(stud* head),通過該函數(shù)我們對申請的鏈表空間進(jìn)行釋放。通過以上函數(shù)我想讀者應(yīng)該知道了如何創(chuàng)建一個鏈表和處理鏈表中的數(shù)據(jù),在這里我僅僅是對數(shù)據(jù)做了打印操作,如果讀者有興趣可以進(jìn)行其他的操作。

以上代碼實(shí)現(xiàn)了單鏈表的創(chuàng)建,但是鏈表的常用操作還有查找、插入、刪除等沒有講解,刪除操作與插入操作類似,就不在這里一一講解了,在此我們以查找和插入為例進(jìn)行講解,但是讀者在編寫刪除操作的時(shí)候別忘了把刪除的結(jié)點(diǎn)釋放掉。接下來我們就來看看一段查找和插入的操作。

代碼功能為在查找到的結(jié)點(diǎn)后面添加一個新的結(jié)點(diǎn)。

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <stdlib.h>

#define N 3 /*N為人數(shù)*/

//#undef  _EXAM_ASSERT_TEST_    //禁用
#define  _EXAM_ASSERT_TEST_   //啟用
#ifdef _EXAM_ASSERT_TEST_     //啟用斷言測試
 void assert_report( const char * file_name, const char * function_name, unsigned int line_no )
{
 printf( "\n[EXAM]Error Report file_name: %s, function_name: %s, line %u\n",
         file_name, function_name, line_no );
  abort();
}

#define  ASSERT_REPORT( condition )       \
 do{       \
 if ( condition )       \
  NULL;        \
 else         \
  assert_report( __FILE__, __func__, __LINE__ ); \
 }while(0)

#else // 禁用斷言測試
#define ASSERT_REPORT( condition )  NULL
#endif /* end of ASSERT */

typedef enum _SListReturn
{
 SLIST_RETURN_OK,
}SListReturn;

typedef struct node
{
 char name[20];
 int score;
 struct node *link;
}stud;

stud * creat(int n)
{
 stud *p,*h,*s; 
 int i; 
 if((h=(stud *)malloc(sizeof(stud)))==NULL) 
 {
  printf("分配內(nèi)存空間失敗!");
  exit(0);
 }
 h->name[0]='\0'; /*把表頭結(jié)點(diǎn)的數(shù)據(jù)域置空*/
 h->score=0;
 h->link=NULL; /*把表頭結(jié)點(diǎn)的鏈域置空*/

 p=h; /*p指向表頭結(jié)點(diǎn)*/
 for(i=0;i<n;i++)
 {
  if((s= (stud *) malloc(sizeof(stud)))==NULL) 
  {
   printf("分配內(nèi)存空間失敗!");
   exit(0);
  }
 p->link=s; /*把s的地址賦給p所指向的結(jié)點(diǎn)的鏈域,這樣就把p和s所指向的結(jié)點(diǎn)連接起來了*/
 printf("請輸入第%d個人的姓名:",i+1);
 scanf("%s",s->name); /*姓名*/
 printf("請輸入第%d個人的成績:",i+1);
 scanf("%d",&s->score); /*分?jǐn)?shù)*/
 s->link=NULL;
 p=s;
 }
 return(h);
}

stud * search(stud *h,char *x) /*查找函數(shù)*/
{
stud *p;
char *y;
p=h->link;
while(p!=NULL)
{
y=p->name;
if(strcmp(y,x)==0)
return(p);
else p=p->link;
}
if(p==NULL)
printf("沒有查找到該數(shù)據(jù)!");
}

SListReturn insert(stud *p) /*插入函數(shù),在指針p后插入*/
{
char stu_name[20];
int stu_score;
stud *s; /*指針s是保存新結(jié)點(diǎn)地址的*/
if((s= (stud *) malloc(sizeof(stud)))==NULL)
{
printf("分配內(nèi)存空間失敗!");
exit(0);
}
printf("請輸入你要插入的人的姓名:");
scanf("%s",stu_name);
printf("請輸入你要插入的人的成績:");
scanf("%d",&stu_score);
strcpy(s->name,stu_name); /*把指針stuname所指向的數(shù)組元素拷貝給新結(jié)點(diǎn)的數(shù)據(jù)域*/
s->score=stu_score;
s->link=p->link; /*把新結(jié)點(diǎn)的鏈域指向原來p結(jié)點(diǎn)的后繼結(jié)點(diǎn)*/
p->link=s; /*p結(jié)點(diǎn)的鏈域指向新結(jié)點(diǎn)*/
return SLIST_RETURN_OK;
}

SListReturn destroy(stud* head)
{
 stud* tmp,*next;
 tmp=head;
 int i=0;
 while(tmp!=NULL)
 {
  next=tmp->link;
  tmp->link=NULL;
  free(tmp);
  tmp=next;
  i++;
  printf("第%d次釋放\n",i);
 } 
 
 return SLIST_RETURN_OK;
}

SListReturn print(stud* head)
{
 stud* tmp=head->link;
 while(tmp!=NULL)
 {
  printf("%s成績?yōu)?d\n",tmp->name,tmp->score);
  tmp=tmp->link;
 }
 return SLIST_RETURN_OK;
}

void main()
{
 int number; /*保存人數(shù)的變量*/
 char fname[10]; /*保存輸入的要查找的人的姓名*/
 stud *head,*searchpoint; /*head是保存單鏈表的表頭結(jié)點(diǎn)地址的指針*/
 number=N;
 head=creat(number); /*把所新建的單鏈表表頭地址賦給head*/

 printf("請輸入你要查找的人的姓名:");
 scanf("%s",fname);
 searchpoint=search(head,fname); /*查找并返回查找到的結(jié)點(diǎn)指針*/
 insert(searchpoint); /*調(diào)用插入函數(shù)*/

 print(head);
 destroy(head);
 //ASSERT_REPORT(print(head)==SLIST_RETURN_OK);
 //ASSERT_REPORT(destroy(head)==SLIST_RETURN_OK);
}

運(yùn)行結(jié)果為:

以防圖片打開失敗,在此特地加上文字描述。

請輸入第1個人的姓名:rewq
請輸入第1個人的成績:123
請輸入第2個人的姓名:fdsa
請輸入第2個人的成績:456
請輸入第3個人的姓名:vcxz
請輸入第3個人的成績:789
請輸入你要查找的人的姓名:fdsa
請輸入你要插入的人的姓名:ghjk
請輸入你要插入的人的成績:369
rewq成績?yōu)?23
fdsa成績?yōu)?56
ghjk成績?yōu)?69
vcxz成績?yōu)?89
第1次釋放
第2次釋放
第3次釋放
第4次釋放
第5次釋放
Press any key to continue

代碼中的關(guān)鍵部分都加了注釋來進(jìn)行說明,所以在此就不做一一講解了,只說幾個值得注意的地方,那就是destroy()函數(shù)的實(shí)現(xiàn),可能有很多人對這兒的操作不是很熟悉,因?yàn)閷τ卺尫懦晒εc否都沒有能夠直觀的顯示出來,就算寫對了也還是不太確信,這個時(shí)候我們就要自己來加點(diǎn)東西了。所以在此特地教讀者一個方法,來進(jìn)行簡單的驗(yàn)證,通過i++;、 printf("第%d次釋放\n",i);語句來實(shí)現(xiàn)打印釋放了多少個結(jié)點(diǎn),和我們創(chuàng)建的結(jié)點(diǎn)數(shù)目進(jìn)行比較即可,在本代碼中我們一開始創(chuàng)建了4個結(jié)點(diǎn)(注意:包括頭結(jié)點(diǎn)),之后又插入了一個結(jié)點(diǎn),所以總需要釋放5個結(jié)點(diǎn),看看打印的結(jié)果就知道我們的函數(shù)實(shí)現(xiàn)正確了,當(dāng)然還有很多驗(yàn)證的方法,在此僅是例舉一個簡單的方法。

總不能沒玩沒了的寫下去吧,所以暫時(shí)到此為止,到下一篇博客我們接著講。由于本人水平有限,博客中的不妥或錯誤之處在所難免,殷切希望讀者批評指正。同時(shí)也歡迎讀者共同探討相關(guān)的內(nèi)容,如果樂意交流的話請留下你寶貴的意見。

分享到:
4
0
查看評論
3樓 cheneydog 6天前 10:14發(fā)表 [回復(fù)]
剛發(fā)現(xiàn)您的博客,果斷訂閱了。
2樓 sailchou 2011-07-24 10:03發(fā)表 [回復(fù)]
我很喜歡樓主的文章,受益匪淺,支持樓主~~
Re: bigloomy 2011-07-24 10:06發(fā)表 [回復(fù)]
回復(fù)sailchou:呵呵……謝謝!前面的文章我沒有注重代碼的格式,可能你們看起來有些吃力,但是都可以自己復(fù)制過去運(yùn)行,接下來我都會把代碼加到代碼框中去,使得代碼看起來更加的悅目。
1樓 zhuyunxianghu 2011-07-23 18:59發(fā)表 [回復(fù)]
很好,看了你前面的很多文章。提一點(diǎn)點(diǎn)不成熟的小建議:
調(diào)一下格式,把代碼搞得代碼框中去。這樣好看一些。
Re: bigloomy 2011-07-23 20:23發(fā)表 [回復(fù)]
本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
單鏈表的建立(C語言完整程序)
靜態(tài)鏈表(網(wǎng)上的)
C數(shù)據(jù)結(jié)構(gòu)與算法02——線性表(傳統(tǒng)鏈表/單向鏈表)
嵌入式大雜燴周記 | 第 3 期
鏈表的簡單創(chuàng)建
【轉(zhuǎn)】C語言 鏈表操作
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服