在C中,使用結構能夠把不一樣類型的值存儲在一塊兒。數組是經過下標訪問,由於數組的元素長度相同,可是結構並非這樣,每一個結構成員都有本身的名字,它們是經過名字來訪問的。
結構變量屬於標量類型,並非指針類型,和其餘任何標題同樣,當結構名在表達式中做爲右值使用時,它表示存儲在結構中的值。當它做爲左值使用時,它表示結構存儲的內存位置。可是,當數組名在表達式中做爲右值使用時,它的值是一個指向數組第一個元素的指針,因爲它的值是一個常量指針,因此數組名不能做爲左值使用。能夠聲明指向結構的指針與結構數組。
struct 標識 { 成員列表} 變量列表 ;
struct {
inta;
charb;
floatc;
} x;
struct {
inta;
charb;
floatc;
} y[10], *z;
注意,上面是兩種大相徑庭的類型,因此不能這樣:z = &x
struct SIMPLE {
inta;
charb;
floatc;
};
該聲明並無提供變量列表,因此它並未建立任何變量,下面纔開始建立變量。
struct SIMPLE x;
struct SIMPLE y[10], *z;
但如今與上前面的聲明不一樣的是 x y z 都是同一種類型的結構變量了,因此z = &x能夠
typedefstruct {
inta;
charb;
floatc;
} Simple;
Simple如今是一個類型名而不是一個結構標籤,因此後續的聲明能夠是下面這樣:
Simple x;
Simple y[10], *z;
結構能夠嵌套其餘結構:
struct COMPLEX {
struct SIMPLE sa[10];
struct SIMPLE *sp;
} comp, *cp = ∁
結構的訪問
typedefstruct EX {
inta;
charb[3];
struct EX2 {
inta;
shortb[2]
} c;
struct EX *d;
} EX;
EX x = { 10, "Hi", { 5, { -1, 25 } }, 0 };
EX *px = &x;
EX y;
x.d = &y;
printf("%d", x.a);//10//結構體變量的訪問方式
printf("%d", (*px).a);//10//結構體指針的訪問方式
printf("%d", px->a);//10//此種爲上面結構指針的簡化訪問方式
printf("%d", *px->c.b);//-1//混合使用
printf("%d", px->d->c.b[1]);//30656
px->d->c.b[1]=11;
printf("%d", px->d->c.b[1]);//11
結構的自引用
struct SELF_REF1 {
inta;
struct SELF_REF1 b;//非法
};
----
struct SELF_REF2 {
inta;
struct SELF_REF2 *b;//合法
};
printf("%d", sizeof(a.a));//4
printf("%d", sizeof(a.b));//4
第一個聲明會嘗試遞歸下去,但最終結構的長度不能肯定下來而編譯時出錯誤;但第二個是一個指針,其在編譯時可以肯定結構體的長度,通常鏈表和樹都是用這種技巧實現。
在32位機上,一個指針一般佔4個字節,而無論它指向什麼類型的變量,由於指針是用來存儲存儲地址的:
char *p = NULL;
printf("%d", sizeof(p));//4
printf("%d", sizeof (char *));//4
書上說下面是非法的(緣由就是類型名直到聲明的末尾才定義,在結構聲明的內部它尚定義),但實際上編譯能夠經過:
typedefstruct {
inta;
struct SELF_REF3 *b;//合法
} SELF_REF3;
SELF_REF3 a = { 3, NULL };
printf("%d", a.a);//3
相互引用的結構
有時須要聲明一些相互之間存在依賴的結構,先被引用的結構須要使用不完整聲明(以下面的struct B;),來聲明一個做爲結構標籤的標識符,而後咱們能夠把這個標籤與用在結構指針的聲明中(以下面的struct B *partner;):
struct B;
struct A {
inta;
struct B *partner;
};
struct B {
struct A partner;
};
經實際測試,struct B;能夠註釋掉,編譯時都不會出錯,甚至將後面的B結構的定義一塊兒去掉都沒有問題,這也進一肯說聲明結構體指針時不須要知道它所指向的結構體的具體類型,甚至不須要定義均可以;
結構體的初始化
結構體的初始化相似於或多維數組的初始化:
struct A {
inta;
shortb[10];
Simplec;
} x = { 10, { 1, 2 }, { 25, 'x', 1.9 } };
結構的地址與第一個成員的地址是相同的:
struct A {
inta;
shortb[10];
} x;
printf("%p\n", &x);//0022FF38
printf("%p\n", &x.a);//0022FF38
printf("%p\n", &x);//0022FF38
printf("%p\n", &x.a);//0022FF38
結構的存儲分配
上面章節中(「結構的訪問」),顯示的是結構的邏輯存儲結構(圖中分配的空間好像不連續),那結構在內存中是如何實際存儲的呢?上面那張圖並不完整,實際上編譯器會按照成員列表的順序(聲明的順序)一個接一個地給每一個成員分配內存,只有當存儲成員時須要知足正確的邊界要求時,成員之間纔可能出現用於填充的額外內存空間(被浪費掉了的),以下面圖中的灰色地帶:
#include<stddef.h>
typedefstruct ALIGN {
chara;
intb;
charc;
}ALIGN;
printf("%d", offsetof(struct ALIGN,a));//0
printf("%d", offsetof(struct ALIGN,b));//4
printf("%d", offsetof(struct ALIGN,c));//8
printf("%d", sizeof (struct ALIGN));//12
若是編譯器有成員對齊這一要求時,會按照最長類型來分配每個成員,如上面的a成員,在整型類型長度爲4字節的機器上,因爲a是一個字符型,雖然自己只需一個字節,但也爲a分配了4個字節,這將浪費3個字節的空間,c也是同樣,此時須要總共12個字節的空間。若是將短的類型放在一塊兒,這將會節約空間(此時只須要8個字節的空間):
typedefstruct ALIGN {
intb;
chara;
char c;
}ALIGN;
printf("%d", offsetof(struct ALIGN,b));//0
printf("%d", offsetof(struct ALIGN,a));//4
printf("%d", offsetof(struct ALIGN,c));//5
printf("%d", sizeof (struct ALIGN));//8
下面與上面也是同樣節約空間:
typedefstruct ALIGN {
chara;
char c;
int b;
}ALIGN;
printf("%d", offsetof(struct ALIGN,a));//0
printf("%d", offsetof(struct ALIGN,c));//1
printf("%d", offsetof(struct ALIGN,b));//4
printf("%d", sizeof (struct ALIGN));//8
最後看看下面這個程序:
typedefstruct ALIGN {
chara;
charc;
chard;
shorte;
charf;
charj;
intb;
}ALIGN;
printf("%d", offsetof(struct ALIGN,a));//0
printf("%d", offsetof(struct ALIGN,c));//1
printf("%d", offsetof(struct ALIGN,d));//2
printf("%d", offsetof(struct ALIGN,e));//4
printf("%d", offsetof(struct ALIGN,f));//6
printf("%d", offsetof(struct ALIGN,j));//7
printf("%d", offsetof(struct ALIGN,b));//8
printf("%d", sizeof (struct ALIGN));//12
sizeof操做符可以得出一個結構體的總體長度,包括因邊界對齊而路過的那些字節,若是想獲得每一個成員偏離結構首的字節數,則可使用offsetof宏。
降序排列結構成員的聲明能夠最大限度地減小結構存儲中浪費的內存空間。sizeof返回的值包含告終構中浪費的內存空間。
參數結構體
把結構體做爲參數傳遞給一個函數是合法的,但這種作法效率低,由於在傳遞過程當中須要複製整個結構體。
void print(registerstruct Trans const * const trans);
在許多的機器中,你能夠把參數聲明爲寄存器變量,從而進一步提升指針傳遞方案的效率,這對於須要屢次訪問的變量有很大的效率提高。
位段
用signed或unsigned整數地聲明位段是個好主意,若是隻是聲明爲int類型,它究竟被解釋爲有符號數仍是無符號數是由編譯器決定的。
位段的聲明與結構相似,但它的成員是一個或多個位的字段,這些不一樣長度的字段實際上存儲於一個或多個整型變量中。
位段的成員必須聲明爲int、signed int、或unsigned int類型。其次,在成員名的後面是一個冒號和一個整數,這個整數指定該位段所佔用的位的數目。
struct CHAR {
unsignedch :7;
unsignedfont :6;
unsignedsize :19;
};
struct CHAR ch1;
該程序能夠處理128個不一樣的字符值、64種不一樣的字段、524288種字體大小。成員size位段過於龐大,沒法容納於一個短整型中,但其餘的位段成員又比一個字符還短,因此這裏可以利用存儲ch和font所剩餘的位來用在size位段上,這樣就可使用一個32位的整數來存儲整個內容了。上面程序在16位機上是非法的,由於最長的位段定義成了19,而最長也只可能爲16,但在32位機上,這個聲明將根據下面兩種可能的方法建立ch1:
位段的好處:它可以把長度不一樣的類型數據包裝在一塊兒,節省存儲空間。另外一個好處是,能夠很方便地訪問一個整型值的部份內容。下面是一個軟盤控制器寄存器,每位的結構以下:
在一個從右向左分配位段的機器上,下面的這個聲明容許程序方便地對這個寄存器的不一樣位段進行訪問:
struct DISK_REGISTER_FORMAT {
unsignedcommand :5;
unsignedsector :5;
unsignedtrack :9;
unsignederror_code :8;
unsignedhead_loaded :1;
unsignedwrite_protect :1;
unsigneddisk_spinning :1;
unsignederror_occurred :1;
unsignedready :1;
};
假如寄存器是在內存地址 0xc0200142 ,咱們能夠聲明下面的指針常量:
#define DISK_REGISTER ((struct DISK_REGISTER_FORMAT*)0xc0200142)
下面開始訪問:
/*
* 告訴控制器從哪一個扇區哪一個磁道開始讀取
*/
DISK_REGISTER->sector = new_sector;
DISK_REGISTER->track = new_track;
DISK_REGISTER->command = READ;
/*
* 等待,直到操做完成(ready變量變成真)
*/
while (!DISK_REGISTER->ready);
/*
* 檢查錯誤
*/
if(DISK_REGISTER->error_code){
}
使用位段只是基於方便的目的,任何能夠用位段實現的任務均可以使用移位和屏蔽來實現。下面的代碼功能和前面的賦值功能是同樣:
#define DISK_REGISTER ((unsignedint *)0xc0200142)
*DISK_REGISTER &= 0xfffffc1f;//將sector字段清零
*DISK_REGISTER |= (new_sector & 0x1f) << 5;//賦值
在源代碼中,用位段表示這個處理過程更簡單一些,但在目標中,這兩種方法並不存在任何區別,不管是否使用位段,相同的移位和屏蔽都是必需的。位段提供的惟一優勢是簡化了源代碼,但須要與位段的移植性較弱這個缺點進行權衡。
注重可移植性的程序應該避免使用位段,位段在不一樣的系統中可能有不一樣的結果:
一、 int位段被看成有符號數仍是無符號數。
二、 位段中字段的位最大數目。許多編譯器把位段成員的位數限制在一個整型的長度以內,因此可以運行於32位整數的機器上的位段聲明可能在16位整數的機器上沒法運行,如unsignederror_code :32; 確定是不能移植了。
三、 位段中的成員在內存中是從左向右分配的仍是從右向左分配的。
聯合
聯合的全部成員引用的是內存中的相同位置,當你想在不一樣的時刻把不一樣的東西存儲於一個位置時,就以使用聯合。經過訪問不一樣類型的聯合成員時,內存中相同的位組合能夠被解釋爲不一樣的東西。
union u_tag {
inti;
floatf;
char *s;
} u;
int main(int argc, char * argv[]) {
printf("%d\n", u.i);//0
u.i=1;
printf("%d\n", u.i);//1
printf("%f\n", u.f);//0.000000
u.f=1.1;
printf("%f\n", u.f);//1.100000
//打印的仍是最後一次存儲的內容
printf("%f\n", u.i);//1.100000
//float轉換成int
printf("%d\n", u.i);//1066192077
return 0;
}
聯合與結構能夠相互嵌套。
實際上,聯合就是一個結構,它的全部成員相對於基地址的偏移量都爲0,此結構空間要大到足夠容納最「寬」的成員。
在一個成員長度不一樣的聯合裏,分配給聯合的內存數量取決於它的最長成員的長度。若是成員的長度相關懸殊,會浪費不少的空間,在這種狀況下,最好的方法是在聯合中存儲指向不一樣成員的指針而不是直接存儲成員自己,由於全部指針的長度都是相同的,這樣就解決了內存浪費的問題了。
聯合會默認使用第一個成員類型的值進行初始化。
聯合初始化必須是聯合第1個成員的類型,並且它必須位於一對花括號裏:
union {
charc;
inta;
floatb;
} x={'a'};
printf("%c\n",x.c);//a
printf("%d\n",x.a);//97ide
當你聲明數組時,你必須用一個編譯時常量指定數組的長度,可是,數組的長度經常在運行時才知道,因此咱們一般採用的方法是聲明一個較大的數組,它能夠容納可能出現的最多元素。
malloc從內存池中提取一塊合適的內存,並向程序返回一個指向這塊內存的指針。分配與釋放的函數原型以下:
void *malloc(size_t size);
void free(void *pointer);
size_t是一個無符號類型,定義於stdlib.h中,該參數指定了須要分配的內存字節數。
malloc所分配的是一塊連續的內存。malloc實際分配的內存有可能比你請求的稍微多一點,這是由編譯器定義的。
若是操做系統沒法向malloc提供更多的內存,malloc就返回一個NULL指針。
malloc是不知道你所請求的內存是用來存儲整數、浮點數、結構仍是數組的,因此它返回的是一個類型爲void*的指針,而這個類型的指針能夠轉換爲其餘任何類型的指針,在某些老式的編譯器中,可能要求你在轉換時使用強制類型轉換。
另外還有兩個內存分配函數,原型以下:
void *calloc(size_t num_elements, size_t element_size);
void realloc(void *ptr, size_t new_size);
calloc與malloc之間的主要區別是前者在返回指向內存的指針以前把內存初始化爲0,另外一個區別是它們請求內存數量的方式不一樣,calloc的參數包括所須要元素的數量和每一個元素的字節數,根據這些值,它可以計算出總共須要分配的內存。
realloc函數用於修改一個碑已經分配的內存塊的大小,可使用一塊內存擴大或縮小。擴大時原先的內容依然保留,新增長的內存添加到原先內存塊的後面,新內存並未以任何方法進行初始化。縮小時,該內存塊尾的部份內存被拿掉,剩餘部份內存的原先內容依然保留。若是原先的內存沒法改變大小,realloc將分配另外一塊正確大小的內存,並把原先那塊內存的內容複製到新的塊上。所以,在使用realloc以後,你主不能再使用指向舊內存的指針,而是應該改用realloc所返回的新的指針。
int i, *pi, *pi2;
//這塊內存將被看成25個整型元素的數組,
//由於pi是一個指向整型的指針
pi = malloc(25 * sizeof(int));
if (pi == NULL) {
printf("Out of memory!\n");
exit(1);
}
pi2 = pi;
for (i = 0; i < 25; i++) {
*pi2++ = 0;
//或者使用下標,看成數組來使用 pi[i]=*(pi + i)
//pi[i]=0;
}
常見的動態內存錯誤:對NULL指針進行解引用操做、對分配的內存進行操做時越過邊界、釋放並不是動態分配的內存、試圖釋放一塊動態分配的內存的一部分、一塊動態內存被釋放以後被繼續使用。
使用MALLOC自定義宏來避免上面這些錯誤,下面程序由三部分組成:一個是定義接口alloc的頭文件alloc.h,第二個是接口,第三個是使用接口:
/*
* 定義一個不易發生錯誤的內存分配器接口
*/
#include<stdlib.h>
#define malloc 不要直接調用malloc!
#define MALLOC(num,type) (type*)alloc((num) * sizeof(type))
externvoid * alloc(size_t size);//接口
——alloc.h
/*
* 實現
*/
#include<stdio.h>
#include"alloc.h"
#undef malloc
void * alloc(size_t size) {
void * new_mem;
//請求所需的內存,並檢查確實分配成功
new_mem = malloc(size);
if (new_mem == NULL) {
printf("Out of memory!\n");
exit(1);
}
return new_mem;
}
——alloc.c
#include"alloc.h"
/*
* 使用
*/
void function() {
int * new_memeory;
//獲取一串整型數的空間
new_memeory = MALLOC(25,int);
}
——a_client.c
free的參數必需要麼是NULL,要麼是一個先前從malloc、calloc或realloc返回的值,向free傳遞一個NULL參數不會產生任何效果。free試圖釋放一塊動態分配內存的一部分也有可能引發問題:
pi = malloc(25 * sizeof(int));
//下面會引起問題
free(pi +5);
釋放一內存的一部分是不容許的,動態分配的內存必須整塊一塊兒釋放。可是,realloc函數能夠縮小一塊動態分配的內存,有效地釋放它尾部的內存。
不能訪問已經被free函數釋放了的內存。
分配內存但在使用完畢後不釋放將引發內存泄漏
動態分配實例
動態分配最多見的一個應用就是爲那些長度在運行時才知道的數組分配內存空間。下面是讀取一列整數,並排序:
/*
** 讀取、排序和打印一列整數.
*/
#include<stdlib.h>
#include<stdio.h>
/*
** 該函數由 qsort 用於比較整型值
*/
int compare_integers(voidconst *a, voidconst *b) {
registerintconst *pa = a;
registerintconst *pb = b;
return *pa > *pb ? 1 : *pa < *pb ? -1 : 0;
}
int main() {
int *array;
int n_values;
int i;
/*
** 觀察共有多少個值.
*/
printf("How many values are there? ");
if (scanf("%d", &n_values) != 1 || n_values <= 0) {
printf("Illegal number of values.\n");
exit(EXIT_FAILURE);
}
/*
** 分配內存,用於存儲這些值.
*/
array = malloc(n_values * sizeof(int));
if (array == NULL) {
printf("Can't get memory for that many values.\n");
exit(EXIT_FAILURE);
}
/*
** 讀取這些值.
*/
for (i = 0; i < n_values; i += 1) {
printf("? ");
if (scanf("%d", array + i) != 1) {
printf("Error reading value #%d\n", i);
exit(EXIT_FAILURE);
}
}
/*
** 調用庫函數進行排序.
*/
qsort(array, n_values, sizeof(int), compare_integers);
/*
** 輸出.
*/
for (i = 0; i < n_values; i += 1)
printf("%d\n", array[i]);
/*
** 釋放內存而且退出.
*/
free(array);
return EXIT_SUCCESS;
}
動態複製字符串:
/*
** 用動態分配內存製做一個字符串的一份拷貝。注意:
** 調用程序應該負責檢查這塊內存是否成功分配!這樣
** 作容許調用程序以任何它所但願的方式對錯誤做出反應
*/
#include<stdlib.h>
#include<string.h>
char *strdup(charconst *string) {
char *new_string;
/*
** 請求足夠長度的內存,用於存儲字符串和它的結尾NUL字節.
*/
new_string = malloc(strlen(string) + 1);
/*
** 若是咱們獲得內存,就複製字符串.
*/
if (new_string != NULL)
strcpy(new_string, string);
return new_string;
}
該程序將輸入的字符串存儲到緩衝區,每次讀取一行。調用此函數時才能夠肯定字符串的長度,而後就分配內存用於存儲字符串,最後字符串被複制到新內存,這樣緩衝區又能夠用於讀取下一個輸入行。這個函數很是方便,也很是有用,儘管標準沒有說起,但許多環境都把它做爲函數庫的一部分。
有些C編譯器提供了一個稱爲alloca的函數,它與malloc函數不現是在於它在棧上分配內存,而malloc是在堆上分配內存。在棧上分配內存的主要優勢是當分配內存的返回時,這塊內存會被自動釋放,這是由棧的工做方式決定的,它能夠保證不會出現內存泄漏,但這種方式存在缺點,因爲函數返回時被分配的內存將消失,因此它不能用於存儲那些回傳給調用程序的數據。
函數
單鏈表
/*
** 單鏈表的插入,鏈表爲升序
*/
#include<stdlib.h>
#include<stdio.h>
typedefstruct NODE {
struct NODE *link;
intvalue;
} Node;
#define FALSE 0
#define TRUE 1
/*
* 若是節點插在最前面,則須要使用根指針的值,
* 因此這裏使用了二級指針將根指針的地址也傳遞
* 過去,這樣便於修改根指針的指向
*/
int sll_insert(Node **rootp, int new_value) {
/*
* 前驅節點,會成爲新節點的前驅節點,也可能爲NULL
*/
Node *previous;
/*
* next節點,即第一個大於或等於新節點的節點,最後會
* 成會新節點的後繼節點,可能爲NULL
*/
Node *next;
Node *new;//新節點
previous = NULL;//剛開始時前驅節點指針指向NULL
//初始化後繼節點,剛開始時與根節點同樣指向第一個節點
next = *rootp;
/*
** 若是沒有到達鏈尾,且沒有找到一個大於或等於新節點
** 的節點時,繼續往下找
*/
while (next != NULL && next->value < new_value) {
previous = next;
next = next->link;
}
//動態建立新的節點
new = (Node *) malloc(sizeof(Node));
if (new == NULL)
return FALSE;
new->value = new_value;
//設定next域,讓next節點成爲新節點的下一節點
new->link = next;
//若是須要在最前面插入時
if (previous == NULL)
//此時須要修改根節點的指向,讓它指向新的節點
*rootp = new;
else//若是插入是在鏈表的中間或者末尾時
previous->link = new;
return TRUE;
}
int main(int argc, char **argv) {
Node *root = NULL;
Node **rootp = &root;
sll_insert(rootp, 2);
sll_insert(rootp, 5);
sll_insert(rootp, 3);
sll_insert(rootp, 0);
sll_insert(rootp, 1);
sll_insert(rootp, 4);
while (root != NULL) {
printf("%d", root->value);//012345
root = root->link;
}
} 測試
單鏈表的優化插入操做
看上去,把一個節點插入到鏈表的起始位置必須做爲一種特殊狀況進行處理,由於此時插入新節點須要修改的是指針是根指針,而對於其餘任何節點,修改的是前一個節點(previous)的link字段,這兩個看上去不一樣的操做其實是同樣的。
消除這種特殊狀況的關鍵在於:咱們必須認識到,鏈表中的每一個節點都有一個指向它的指針。對於第1個節點,這個指針是根指針;對於其餘節點,這個指針是前一節點的link字段,相同點是每一個節點都有一個指針指向它,至於該指針是否是位於一個節點內部則不重要,咱們徹底能夠將root與節點內部的link指針同等看待,root便可當作某個節點的link字段。 字體
讓咱們再次觀察這個鏈表,弄清這個概念,這是第1個節點和指向它的指針:優化
若是新值插入到第1個節點以前,這個指針就必須進行修改。下面是第2個節點和指向它的指針:spa
若是新值須要插入到第2個節點以前,那麼前一節點的link指針必須進行修改。操作系統
如今咱們只須要擁有一個指向插入位置的下一節點的指針(next),以及一個「指向next節點的link字段」的指針(linkp),除此外,咱們就再也不須要一個用來保存指向插入位置的前一節點的指針(previous),下面是賦值語句(next = *linkp)執行後的各變量狀況:指針
當移動到下一個節點時,咱們保存一個「指向next節點的link字段的」指針(linkp),而不是保存一個指向前一個節點的指針(previous):code
注,這裏的linkp並不指向節點自己,與上面實現不現的是,它是指向節點內部的link字段,這是簡化插入操做的關鍵所在,這能夠將root指針與節點內的link指針字段同等看待,咱們能夠用一種和修改節點的link字段徹底同樣的方式來修改root變量。插入函數的原型與上面的實現仍是同樣,只是將rootp的名稱改爲了linkp,由於這裏的實現是讓rootp能夠指向其餘任何節點內部的link字段,因此就形象的稱爲linkp,而不只僅是根指針了。咱們不再須要previous指針了,由於咱們的linkp指針能夠負責尋找到須要修改的link字段,下面是實現:
result = sll_insert(&root, 12);
int sll_insert(registerNode **linkp, int new_value) {
registerNode *next;
registerNode *new;
next = *linkp;//剛開始時next與root同指向第一個節點
//若是沒有到達鏈尾且next節點的值小於或等於新節點值時繼續
while (next != NULL && next->value < new_value) {
/*
* 這裏與上面的實現是不同的,這裏保存的是節點內link字段的
* 地址,而不像上面那樣保存的是節點自己,這是簡化的關鍵所在
*/
linkp = &next->link;
next = *linkp;
}
new = (Node *) malloc(sizeof(Node));
if (new == NULL)
return FALSE;
new->value = new_value;
//這個與上面的實現仍是同樣,讓當前節點成爲新節點的下一節點
new->link = next;
/*
* 即便在空鏈表時,能夠將root根指針與節點中的link字段同等看待
* 鏈表爲空時,修改的就是root的指向,不然修改的就是其餘節點的
* link指向
*/
*linkp = new;
return TRUE;
}
節點的刪除與查找也可使用上面這種簡化的操做來實現。
雙鏈表(非循環)
雙鏈表的根節點容許咱們能夠從鏈表的任何一端(第一個節點仍是最後一個節點)開始遍歷鏈表。根節點的fwd字段指向鏈表的第1個節點,根節點的bwd字段指向鏈表最後一個節點(若是隻有一個節點,則這兩個都指向第一個節點)。若是鏈表爲空,這兩個字段都爲NULL。鏈表第1個節點的bwd字段和最後一個節點的fwd字段都爲NULL。並將根節點與其餘節點等同看待,只是value沒有值而已。
一、 若是鏈表爲空,則新增節點的fwd與bwd字段都爲NULL。
二、 若是新增節點位於起始位置,則新增節點的bwd字段爲NULL,新節點的fwd字段指向下一節點(next指向的節點),下一節點(next指向的節點)的bwd字段指向這個新的節點。
三、 若是新增節點位於結束位置,則新增節點的fwd字段爲NULL,新節點的bwd字段指向前一節點(previous指向的節點),前一節點(previous指向的節點)的fwd字段指向這個新的節點。
四、 若是新增節點位於鏈表中間,則新增節點的fwd字段爲下一節點(next指向的節點),新節點的bwd指向前一節點(previous指向的節點),下一節點(next指向的節點)的bwd指向新節點,前一節點(previous指向的節點)的fwd也指向新節點。
/*
** 把一個值插入到一個雙鏈表,rootp是一個指向根節點的指針,
** value是欲插入的新值
** 返回值:若是欲插值已存在於鏈表中,函數返回0;若是內存不
** 足,返回-1;若是插入成功,函數返回1。
*/
#include<stdlib.h>
#include<stdio.h>
typedefstruct NODE {
struct NODE *fwd;
struct NODE *bwd;
intvalue;
} Node;
int dll_insert(Node *rootp, int value) {
Node *previous;//指向待插入位置的前一節點,
Node *next;//指向待插入位置的後一節點,
Node *new;//指向新的節點
previous = rootp;//初始化時指向根節點
//previous不能爲NULL,rootp不能傳遞爲NULL
next = previous->fwd;//初始化時指向第一個節點或NULL
/*
* 若是沒有達到鏈尾,且新的值比next值大時繼續向後找
*/
while (next != NULL && next->value < value) {
//查看value是否已經存在於鏈表中,若是是就返回
if (next->value == value) {
return 0;
}
previous = next;
next = previous->fwd;
}
new = (Node *) malloc(sizeof(Node));
if (new == NULL) {
return -1;
}
new->value = value;
if (rootp->fwd == NULL && rootp->bwd == NULL) {//若是鏈表爲空
new->fwd = NULL;
rootp->fwd = new;
new->bwd = NULL;
rootp->bwd = new;
} elseif (previous == rootp) {//若是插入位置爲鏈表首時
new->fwd = next;
rootp->fwd = new;
new->bwd = NULL;
next->bwd = new;
} elseif (next == NULL) {//若是插入位置爲鏈表尾時
new->fwd = NULL;
previous->fwd = new;
new->bwd = previous;
rootp->bwd = new;
} else {
//若是插入位置爲鏈表中間時
new->fwd = next;
previous->fwd = new;
new->bwd = previous;
next->bwd = new;
}
return 1;
}
int main(int argc, char **argv) {
// Node root = { NULL, NULL, 0 };
/*
* 爲了節省空間,root的值成員是多餘的,
* 可使用動態分配出來
*
* 若是不是動態分配出來的,則可使用如
* 下結構來實現:
* struct DLL_NODE;
* struct DLL_POINTERS{//根節點結構
* struct DLL_NODE * fwd;
* struct DLL_NODE * bwd;
* };
* struct DLL_NODE{//節點
* struct DLL_POINTERS pointers;
* int value;
* };
*
*/
Node* root2 = malloc(sizeof(Node) - sizeof(int));
root2->bwd = NULL;
root2->fwd = NULL;
dll_insert(root2, 2);
dll_insert(root2, 5);
dll_insert(root2, 3);
dll_insert(root2, 0);
dll_insert(root2, 1);
dll_insert(root2, 4);
//從前向後遍歷
Node* tmp = root2->fwd;
while (tmp != NULL) {
printf("%d", tmp->value);//012345
tmp = tmp->fwd;
}
//從後向前遍歷
tmp = root2->bwd;
while (tmp != NULL) {
printf("%d", tmp->value);//543210
tmp = tmp->bwd;
}
}