從陣列到鍊錶2
理想的情況是,使用者可以不確定地新增資料(或者不斷新增資料直到用完記憶體量),而不是先指定要輸入多少項,也不用程式分配多餘的空間。這可以通過在輸入每一項後呼叫malloc()分配正好能儲存該項的空間。如果使用者輸入3部影片,程式就呼叫malloc()3次;如果使用者輸入300部影片,程式就呼叫malloc()300次。
不過我們又製造了乙個麻煩。比較一下一種方法是呼叫malloc()一次,為300個filem結構請求分配足夠的空間;另一種方法是呼叫malloc()300次,分別為每個file結構請求分配足夠的空間。前者分配的是連續的記憶體塊,只需要乙個單獨的指向struct變數的指標,該指標指向已分配塊中的第乙個結構。簡單的陣列表示法讓指標訪問塊中的每個結構,如前面**段所示。第二種方法的問題是,無法保證每次呼叫malloc()都能分配到連續的記憶體塊。這意味著結構不一定被連續儲存。如圖1,因此,與第一種方法儲存乙個指向300個結構塊的指標相比,你需要儲存300個指標,每個指標指向乙個單獨儲存的結構。
一種解決方法是建立乙個大型的指標陣列,並在分配新結構時逐個給這些指標賦值,但是我們不打算使用這種方法:
#define tsize 45
//儲存片名的陣列大小
#define fmax 500
//影片的最大數量
struct film;.
..struct film *movies[fmax]
;//結構指標陣列
int i;..
.movies[i]=(
struct film *
)malloc
(sizeof
(struct film)
);
如果用不完500個指標,這種方法節約了大量的記憶體,因為內含500個指標的陣列比內含500個結構的陣列所佔的記憶體少得多。儘管如此,如果用不到500個指標,還是浪費了不少空間。而且,這樣還是有500個結構的限制。
還有一種更好的方法。每次使用malloc()為新結構分配空間時,也為新指標分配空間。但是還得需要另乙個指標來跟蹤新分配的指標,用於跟蹤新指標的指標本身,也需要乙個指標來跟蹤,以此類推。要重新定義結構才能解決這個潛在的問題,即每個結構中包含指向next結構的指標。然後,當建立新結構時,可以把該結構的位址儲存在上乙個結構中。簡而言之,可以這樣定義film結構:
#define tsize 45
//儲存片名的陣列大小
struct film
;
雖然結構不能含有與本身型別相同的結構,但是可以含有指向同型別結構的指標。
這種定義是在定義鍊錶linked list的基礎,鍊錶中的每一項都包含著在何處能找到下一項的資訊。
從概念上了解了鍊錶的工作原理,接著我們來實現它。用鍊錶來儲存電影資訊。
/* films2.c -- 使用結構鍊錶*/
#include
#include
//提供strcpy()原型
#include
//提供malloc()原型
#define tsize 45
//儲存片名的陣列大小
struct film
;char
*s_gets
(char
*st,
int n)
;int
main
(void
)//顯示電影列表
if(head ==
null
)printf
("no data entered");
else
printf
("here is the movie list:\n");
current = head;
while
(current !=
null
)//完成任務,釋放已分配的記憶體
current = head;
while
(current !=
null
)printf
("bye!\n");
return0;
}char
*s_gets
(char
* st,
int n)
return ret_val;
}
我們來討論這段**:
顯示鍊錶
顯示鍊錶從設定乙個指向第乙個結構的指標(名為current)開始。由於頭指標(名為head)已經指向鍊錶中的第乙個結構,所以可以用下面的**來完成
current = head ;
然後,可以使用指標表示法訪問結構的成員:
printf(「movie: %s rating: %d\n」, current->title, current->rating);
下一步是根據儲存在該結構中next成員中的資訊,重新設定current指標指向鍊錶中的下乙個結構。**如下
current =current->next;
完成這些之後,再重複整個過程。當顯示到鍊錶中最後乙個項時,current將被設定為null,因為這是鍊錶最後乙個結構中next成員的值。
while(current != null)
遍歷鍊錶時,為何不直接使用head指標,而要重新建立乙個新指標current?這是因為如果使用head會改變head中的值,程式就找不到鍊錶的開始處
建立鍊錶
如無必要不用建立乙個結構,所以程式使用臨時儲存區(input陣列)獲取使用者輸入的電影名。如果使用者通過鍵盤模擬eof或輸入一行空行,將退出下面的迴圈:
while(s_gets(input, tsize) != null&& input[0] != 『\0』 )
如果使用者進行輸入,程式就分配乙個結構的空間,並將其位址賦給指標變數current:
current = (struct film )malloc (sizeof(struct film));
鍊錶中第乙個結構的位址應儲存在指標變數head中。隨後每個結構的位址應儲存在其前乙個結構的next成員中。因此,程式要知道它處理的是否是第乙個結構。最簡單的方法是在程式開始時,把head指標初始化null。然後,程式可以使用head的值進行判斷:
if(head == null)
head = current;
else
prev ->next =current;
在上面的**中,指標prev指向上一次分配的結構。
接下來,必須為結構成員設定合適的值。尤其是,把next成員設定為null,表明當前結構時鍊錶的最後乙個結構。還要把input陣列中的電影名拷貝到tile成員中,而且要給rating成員提供乙個值。
由於s_gets()限制了只能輸入tsize-1個字元,所以用strcpy()函式把input陣列中的字串拷貝到title成員很安全。
最後,還要為下一次輸入做好準備。尤其是,要設定prev指向當前結構。因為在使用者輸入下一部電影且程式為新結構分配空間後,當前結構將成為新結構的下乙個結構,所以程式在迴圈末尾這樣設定該指標:
prev = current ;
釋放鍊錶
在許多環境中,程式結束時都會自動釋放malloc()分配的記憶體。但是,最好還是成對呼叫malloc()和free()。因此,程式在清理記憶體時為每個已分配的結構都呼叫了free()函式:
current = head;
while (current !=null)
反思總結
該程式還存在不足。例如,程式沒有檢查malloc()是否成功請求到記憶體,也無法刪除鍊錶中的項。這些不足可以彌補。例如,新增**檢查malloc()的返回值是否是null(返回null說明未獲得所需記憶體)。如果程式要刪除鍊錶中的項,還要編寫更多的**。
這種用特定方法解決特定問題,並且在需要時才新增相關功能的程式設計方式通常不是最好的解決方案。另一方面,通常都無法預料程式要完成的所有任務。隨著程式設計專案越來越大,乙個程式設計師或程式設計團隊事先計畫好一切模式,越來越不現實。很多成功的大型程式都是由成功的小型程式逐步發展而來。
如果要修改程式,首先應該強調最初的設計,並簡化其他細節。程式**中沒有遵循這個原則,它把概念模型和**細節混在一起。
例如,該程式的概念模型是在乙個鍊錶中新增項,但是程式卻把一些細節(如malloc()和current->next指標)放在最明顯的位置,沒有突出介面。
如果程式能以某種方式強調給鍊錶新增項,並隱藏具體的處理細節(如呼叫記憶體管理函式和設定指標)會更好。把使用者介面和**細節分開的程式,更容易理解和更新。
注:本部落格內容是從《c primer plus》中摘錄並自我總結學習理解的。
侵刪。
表 陣列 鍊錶
陣列 陣列是個表,是個連續儲存的表。在c裡面,首先我們知道陣列容量是固定的,使用前先分配大小,所以使用後就不是很方便調整容量 解決這個問題在c裡面一般是預先估計陣列的大小,然後用雙倍的容量建立陣列,是不是很麻煩?正因為陣列是連續儲存和容量固定,所以注定陣列不方便進行增刪這種改變容量的操作。原因是假如...
題目 從煉表中點反轉鍊錶
題目 對於鍊錶list,從中點開始進行反轉鍊錶的後半部分,對於奇數個數的鍊錶,從 n 1 2開始反轉,對於偶數個數的鍊錶,從n 2 1開始反轉。分析 注意的是這個題目不同於倒序列印鍊錶,所以先考慮整個反轉鍊錶的問題。要定義三個節點,ppre,pnext,pnode,記錄每乙個節點的pre和next。...
陣列模擬鍊錶(靜態鍊錶)
為了模擬鍊錶的操作,設定了乙個date 資料 以及cur 相當於指標域 typedef struct list list s size size為陣列大小 我們用s size 1 cur來起頭指標的作用,指向鍊錶的第乙個元素的下標。因為不能直接malloc出來乙個空間,需要s 0 來儲存乙個空的陣列...