C語言實現雙連結串列的(終端)新增和查詢

jaysun發表於2019-05-10

文章如果有寫的不對的地方,歡迎指正^^

雙向連結串列也叫雙連結串列,是連結串列的一種,它的每個資料結點中都有兩個指標,分別指向直接後繼和直接前驅。所以,從雙向連結串列中的任意一個結點開始,都可以很方便地訪問它的前驅結點和後繼結點。一般我們都構造雙向迴圈連結串列。雙連結串列wiki

通過雙連結串列可以快速的瞭解c語言中的指標和記憶體的關係

細節

  • 指標變數儲存的是記憶體的地址,可以理解為一個儲存記憶體地址的符號

  • 宣告的是型別,定義的是變數。宣告的時候不分配記憶體,int i;屬於定義。

  • 結構體宣告時不分配記憶體,定義變數的時候才分配struct linked_list link_1;

  • 指標變數相互賦值,傳遞的是所代表的記憶體地址

  • 函式接受字串的時候,形參要用char *來代表字串陣列的首地址

  • -> 用來表示呼叫結構體指標所代表的變數的域,如果用.則代表結構體變數呼叫自己的域

  • 判斷一個指標是否為空,用== NULL

連結串列實現方案

/*
describe linked_list
 _
| |__ _ _ __ _
| `_ | | | |/ _` |
| |_) | |_| | (_| |
|_.__/ \__,_|\__, |
             |___/
*/

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

struct linked_list {
    struct linked_list *next, *prev;
    char *title;
};
/* 宣告結構體指標變數,會在記憶體中開闢空間,儲存指標 */
struct linked_list * current,* prev, *head;

void list_add(char *);
void list_search();
void list_reverse_search();

int main(void) {
    list_add("-");
    list_add("--");
    list_add("---");
    list_add("----");
    list_add("-----");
    list_search();
    list_reverse_search();
}

void list_add(char *title) {
    /* 將新申請的記憶體的首地址給結構體指標變數 */
    current = (struct linked_list *)malloc(sizeof(struct linked_list));
    /* 初始化當前結構體,新增元素的next始終是NULL */
    current->next = NULL; 
    current->title = title;
    /* prev元素是否已經存在了,是否存在前輩元素,來判斷是否是首次新增元素*/
    if(prev ==     NULL) {
        /* 如果不存在說明,current是第一個新增進來的元素 */
        current->prev = NULL;
        /* 設定為首元素,這裡current和head現在指向的是同一個記憶體地址 */
        head = current;
    }else {
        /* 如果存在前輩元素,將當前元素的prev設定為前輩元素的地址,並且將前輩元素的next設定為當前元素的地址 */
        current->prev = prev;
        prev->next = current;
    }
    /* 將當前指標所代表元素的地址,交給prev指標符號 ,現在head和prev指向同一個地址*/
    prev = current;
    /* 首次新增元素head是沒有next的,第二次新增的時候,prev 的next設定為了新增元素地址,所以head的next也發生了改變 ( 新增第2個元素的時候,才會重寫上個元素的next值)*/
}

/* 連結串列正序搜尋 */
void list_search() {
    while(head) {
        printf("%s
", head->title);
        head = head->next;
    }
}
/* 倒序搜尋 */
void list_reverse_search() {
    while(current) {
        printf("%s
", current->title);
        current = current->prev;
    }
}

終端向連結串列新增元素

我們來強化一下,通過終端來不停地向連結串列中新增元素。並且每次回車之後,都列印出整個列表的元素。

要點

  • 宣告定義字串陣列的時候,值不能用變數

  • 我們要求每次都要儲存填寫的元素值,所以每次都需要重新開闢一個記憶體空間,給新的struct,由於個struct中都儲存的是title的指標,所以新增加的元素的title也需要重新開闢記憶體空間來儲存。

  • 由於每次都需要進行列印連結串列,所以head要注意不能被覆寫。所以重新定義一個struct linked_list變數來做一箇中間變數。

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

struct linked_list {
    struct linked_list *next, *prev;
    char *title;
};

struct linked_list *head,*current,*above;
struct linked_list head_cp;

void list_add(char *);
void list_search();

int main(int argc, char *argv[]) {

    char title[10];
    while(scanf("%s",title) > 0) {
        list_add(title);
        /* 每次新增到連結串列之後,都會進行列印整個連結串列 */
        list_search();
    }
    
    /*
    這樣初始化是錯誤的
    error: array initializer must be an initializer list or string literal
    “陣列的初始化,必須是一個列表或者是字面的字串”
    char title[] = *(argv+1);
    */
}

void list_add(char *t) {
    /* 分配記憶體 */
    current = (struct linked_list *)malloc(sizeof(struct linked_list));
    /* title 是一個記憶體空間的首地址,這裡重新分配記憶體的原因是,每次scanf都會重寫t指標下的內容,導致整個鏈式裡面所有的title都發生變化 */
    char *title = (char *)malloc(sizeof(*t));
    /* 將t指標指向的內容copy到title指標指向的記憶體中 */
    strcpy(title,t);

    /* 初始化結構體域 */
    current->prev = NULL;
    current->next = NULL;
    /* 指向新記憶體中的title */
    current->title = title;

    if(above == NULL) {
        head = current;
    }else {
        current->prev = above;
        above->next = current;
    }

    above = current;
}

void list_search() {
    /* 這將head指向的內容賦值給新開闢的結構體變數,這裡不是給的地址,給的是內容 */
    head_cp = *head;

    while(head_cp.title) {
        printf(">>%s
", head_cp.title);
        if(head_cp.next != NULL) {
            head_cp = *(head_cp.next);    
        }else {
            return;
        }
    }
}

操作結果

1  /* 回車 */
>>1
2  /* 回車 */
>>1
>>2
3  /* 回車 */
>>1
>>2
>>3
4  /* 回車 */
>>1
>>2
>>3
>>4

能不能不手動分配title的記憶體空間呢?

當然可以。下面程式碼中我們修改了struct的結構將原來的char *title修改為char title[10],這樣每次定義結構體變數的時候,記憶體作為struct的一部分一起分配好了。

那麼我們在add元素的時候,就需要將scanf獲取到的變數,通過strcpy(current->title,title)的方式,寫入到char title[10]開闢的記憶體空間中。

還需要修改list_search方法中的while判斷,不能再判斷指標是否為NULL的方式來判斷是否存在title內容,而是要判斷字串的長度strlen(head_cp.title) == 0

但是最後一種修改結構體的方式無法動態分配記憶體,每次生成的字串的長度都是10,會浪費記憶體。

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

struct linked_list {
    struct linked_list *next, *prev;
    char title[10];
};

struct linked_list *head,*current,*above;
struct linked_list head_cp;

void list_add(char *);
void list_search();

int main(int argc, char *argv[]) {

    char title[10];
    while(scanf("%s",title) > 0) {
        list_add(title);
        /* 每次新增到連結串列之後,都會進行列印整個連結串列 */
        list_search();
    }
    
    /*
    這樣初始化是錯誤的
    error: array initializer must be an initializer list or string literal
    “陣列的初始化,必須是一個列表或者是字面的字串”
    char title[] = *(argv+1);
    */
}

void list_add(char *title) {
    /* 分配記憶體 */
    current = (struct linked_list *)malloc(sizeof(struct linked_list));
    /* 初始化結構體域 */
    current->prev = NULL;
    current->next = NULL;
    /* 這裡current->title是字串陣列的首地址,是個指標 */
    strcpy(current->title,title);

    if(above == NULL) {
        head = current;
    }else {
        current->prev = above;
        above->next = current;
    }

    above = current;
}

void list_search() {
    /* 這將head指向的內容賦值給新開闢的結構體變數,這裡不是給的地址,給的是內容 */
    head_cp = *head;
    
    while(strlen(head_cp.title) != 0) {
        printf(">>%s
", head_cp.title);
        if(head_cp.next != NULL) {
            head_cp = *(head_cp.next);    
        }else {
            return;
        }
    }
    
}

相關文章