簡體   English   中英

指向鏈表中指針的指針

[英]Pointer to pointer in linked list

有人可以解釋一下為什么這段代碼給我的結果是空列表:

typedef struct str_node{
int data;
struct str_node *next;
}node;


void begin(node *head);
void display_list(node *head);


int main(){

node *head;
int i;

head = NULL;

for(i=0;i<5;i++) {
    begin(head);
}
display_list(head);




return 0;
}

void begin(node *head){
node *new;
int value;
new = (node*) malloc(sizeof(node));
printf("Insert the element to add at the beginning of the list: ");
scanf("%d",&value);
new->data = value;
new->next = head;
head = new;
}

但是,如果我用指向指針的指針更改 begin() function 它會給我正確的列表嗎?

void begin(node **head){
node *new;
int value;
new = (node*) malloc(sizeof(node));
printf("Insert the element to add at the beginning of the list: ");
scanf("%d",&value);
new->data = value;
new->next = *head;
*head = new;
}

你能解釋一下為什么當我將節點頭傳遞給 function 開始時,我必須將它作為“&head”傳遞嗎? 不再是“頭”

在此代碼片段的第一個程序中

head = NULL;

for(i=0;i<5;i++) {
    begin(head);
}

指針head通過值傳遞給begin 即main中聲明的指針head的值的副本被創建並分配給與function開頭的同名參數

void begin(node *head);

因此,在 function 中,參數head最初保存了更改的原始指針head的副本。 其值分配給參數的原始指針head沒有被更改。

要更改 main 中聲明的原始指針頭,您必須通過指向指針頭的指針間接引用將其傳遞給 function,就像在第二個程序中所做的那樣。

所以 function 應該聲明為

void begin(node **head);

而且您必須通過指向它的指針間接傳遞指針頭

begin( &head );

在這種情況下,取消引用傳遞的指針 function 將直接訪問 main 中聲明的原始指針頭並可以更改它(不是其值的副本,因為它發生在第一個 function 定義中)

new->next = *head;
*head = new;

為了更清楚,考慮這個簡單的演示程序。

#include <stdio.h>

typedef int T;

void f( T t )
{
    t = 10;
}

int main(void) 
{
    T t = 0;
    
    printf( "Before calling f t is equal to %d\n", t );
    
    f( t );
    
    printf( "After  calling f t is equal to %d\n", t );

    return 0;
}

它的 output 是

Before calling f t is equal to 0
After  calling f t is equal to 0

由於 function f 處理傳遞參數值的副本,因此 main 中聲明的變量t的值沒有改變。

所以你需要通過指針通過引用傳遞原始變量t

#include <stdio.h>

typedef int T;

void f( T *t )
{
    *t = 10;
}

int main(void) 
{
    T t = 0;
    
    printf( "Before calling f t is equal to %d\n", t );
    
    f( &t );
    
    printf( "After  calling f t is equal to %d\n", t );

    return 0;
}

現在程序 output 是

Before calling f t is equal to 0
After  calling f t is equal to 10

在這些演示程序中,名稱T用作int類型的別名,而 object t主要具有這種類型。

現在讓我們假設名稱 T 是類型 int * 的別名。

typedef int * T;

在這種情況下, main 中的聲明例如

T t = NULL;

表示變量t具有指針類型int * 也就是說它相當於

int * t = NULL;

因此,要將其傳遞給必須更改原始變量 t 的 function,我們需要通過引用傳遞它,例如

f( &t );

這意味着相應的 function 的參數類型應聲明為

void f( T *t );

但由於Tint *的別名,因此這意味着 function 具有int **類型的參數。

void f( int * *t );

因為head (實際上)是一個局部變量,所以更改它在 function 之外沒有任何影響,而更改*head會更改head指向的內容,因此會更改。

如果您希望 function 能夠更改int變量(例如x )中的值,則可以將指向x的指針傳遞給它,該指針的類型為int* ,您將使用&x獲得指向x的指針。 無論x是什么類型,都一樣。

聲明可能會引起一些混亂

    node        *head;

代替

    node*       head;

您正在聲明head head是變量,它是一個指針。 它不是一個節點。 另請注意,節點不是鏈表:鏈表是節點的集合,可能還有其他東西,以便有一個有用的實現。 更多關於這個稍后在最后。

事實是您在main()中聲明了head ,只是一個node* 節點本身甚至還不存在。 您將begin()聲明為

    void begin(node *head);

我想你會更清楚地看到它

    void begin(node*  parameter);

parameternode*

begin()中,您會獲得指針的副本,並且更改指針不會更改main()中的原始指針。 在您的情況下,它將在main()中永遠指向NULL

重要的是指針就像任何變量:指針有一個地址。 和一個內容。 當您按值傳遞時,就像您所做的那樣, begin()中的指針以NULL ,即來自main()的 VALUE。 但是它們之間的聯系在調用中結束:初始值。

當您將指針傳遞給begin()時,使用運算符 'address of' 並編寫&head事情會發生變化:您將使用運算符'*'更改它,這意味着您將更改它指向的地址,因此它將在main() 由於headnode*指向它的指針將被聲明為node**

但是考慮使用以下方法更改鏈表的begin()聲明:

    node* begin(node* node);

邏輯是插入一個節點可以改變鏈表的頭部,所以你返回新地址,如

node* _insert_begin(int value, node* pNode)
{
    node* new = (node*)malloc(sizeof(node));
    new->data = value;
    new->next = pNode;
    return new;
}

是一種常用的寫法。 另一種是使用node**

我在這里描述的方式,任何可以改變列表頭部的操作都必須

  • 歸還新頭
  • 接收並更新指向頭部指針的指針

再次查看在列表開頭插入的這段代碼:

node* _insert_begin(int value, node* pNode)
{   // insert 'value' at the start of the list
    node* new = (node*)malloc(sizeof(node));
    (*new).data = value;
    new->next = pNode;
    return new;
}

返回new head得到更新。 你可以寫在main()

node* another = NULL;
display_list(another);

// inserts 5 to 0 at the beginning
for (int i = 5; i >= 0; i -= 1)
    another = _insert_begin(i, another);
printf("inserted 5..0 at the beginning\n");
display_list(another);

注意another = _insert_begin(i, another); 你會看到main()中的指針是如何更新的。

這是 output

empty list
inserted 5..0 at the beginning
       0        1        2        3        4
       5
list has 6 elements

使用display_list()的這個實現,每行打印 5 個值:

int display_list(node* p)
{
    if (p == NULL)
    {
        printf("empty list\n");
        return 0;
    };
    int count = 0;
    // not empty
    do
    {
        printf("%8d ", p->data);
        count++;
        if (count % 5 == 0) printf("\n");
        p = p->next;
    } while (p != NULL);
    if (count % 5 != 0) printf("\n");
    printf("list has %d elements\n", count);
    return count;
};

另一個例子:在末尾插入

注意最后插入也可以改變頭部,在列表為空的情況下,所以我們仍然需要返回頭部地址

node* _insert_end(int value, node* pNode)
{   // insert value at the end of the list
    node* new = (node*)malloc(sizeof(node));
    new->data = value;
    new->next = NULL;
    if (pNode == NULL) return new;
    node* p = pNode;
    while (p->next != NULL) p = p->next;
    p->next = new;
    return pNode;
}

另一種用途:按升序插入

當然,以升序插入也可以改變頭部,如

node* _insert_ordered(int value, node* pNode)
{   // insert value at ascending order in the list
    node* new = (node*)malloc(sizeof(node));
    new->data = value;
    new->next = NULL;
    if (pNode == NULL) return new;

    node* p = pNode;
    node* prev = NULL; // previous node: list if forward only
    while (p->next != NULL)
    {
        if (new->data < p->data)
        {
            // insert before first greater than value
            if (prev == NULL)
            {
                // new head
                new->next = p;
                return new;
            };  // if()
            prev->next = new;
            new->next = p;
            return pNode; // no change in head
        };
        prev = p; p = p->next; // updates pointers
    };  // while()
    // we are at the end: new will be the last?
    if (new->data < p->data)
    {
        if (prev == NULL)
            pNode = new;
        else
            prev->next = new;
        new->next = p;
    }
    else
    {
        p->next = new;
    };
    return pNode;
}   // _insert_ordered()

刪除列表

刪除列表還應該返回一個node*以使頭指針無效。 這是正常的。 當您習慣了它的機制時,這可以確保不會留下無效的指針。

請注意,此邏輯是協作的:您必須在每次可以更改頭部的調用時重新分配頭部指針

node* delete_list(node* H)
{
    if (H == NULL) return NULL;
    if (H->next == NULL)
    {   // single node
        free(H);
        return NULL; 
    };
    // more than one node
    do
    {   node* p = H->next;
        free(H);
        H = p;
    } while (H != NULL);
    return NULL;
};

一個正在運行的程序

示例程序的 Output

empty list
inserted 5..0 at the beginning
       0        1        2        3        4
       5
list has 6 elements
inserted 6 to 10 at the end
       0        1        2        3        4
       5        6        7        8        9
      10
list has 11 elements
inserted 0 to 10, ordered
       0        0        1        1        2
       2        3        3        4        4
       5        5        6        6        7
       7        8        8        9        9
      10       10
list has 22 elements
inserted -1 to -10, ordered
     -10       -9       -8       -7       -6
      -5       -4       -3       -2       -1
       0        0        1        1        2
       2        3        3        4        4
       5        5        6        6        7
       7        8        8        9        9
      10       10
list has 32 elements
inserted 11 to 20, ordered
     -10       -9       -8       -7       -6
      -5       -4       -3       -2       -1
       0        0        1        1        2
       2        3        3        4        4
       5        5        6        6        7
       7        8        8        9        9
      10       10       11       12       13
      14       15       16       17       18
      19       20
list has 42 elements
about to delete list
empty list

示例 C 程序

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

typedef struct str_node
{
    int             data;
    struct str_node* next;
}   node;

void    begin(node* pNode);
node*   delete_list(node*);
int     display_list(node*);
node*   _insert_begin(int, node*);
node*   _insert_end(int, node*);
node*   _insert_ordered(int, node*);

int main()
{
    node* another = NULL;
    display_list(another);

    // insert 5 to 0 at the beginning
    for (int i = 5; i >= 0; i -= 1)
        another = _insert_begin(i, another);
    printf("inserted 5..0 at the beginning\n");
    display_list(another);

    // insert 6 to 10 at the end
    for (int i = 6; i <= 10; i += 1)
        another = _insert_end(i, another);
    printf("inserted 6 to 10 at the end\n");
    display_list(another);

    // insert 0 to 10 ordered
    for (int i = 0; i <=10; i += 1)
        another = _insert_ordered(i, another);
    printf("inserted 0 to 10, ordered\n");
    display_list(another);

    // insert -1 to -10 ordered
    for (int i = -1; i >= -10; i -= 1)
        another = _insert_ordered(i, another);
    printf("inserted -1 to -10, ordered\n");
    display_list(another);

    // insert 11 to 20 ordered
    for (int i = 11; i <= 20; i += 1)
        another = _insert_ordered(i, another);
    printf("inserted 11 to 20, ordered\n");
    display_list(another);

    printf("about to delete list\n");
    another = delete_list(another);
    display_list(another);
    return 0;
}

node* delete_list(node* H)
{
    if (H == NULL) return NULL;
    if (H->next == NULL)
    {   // single node
        free(H);
        return NULL; 
    };
    // more than one node
    do
    {   node* p = H->next;
        free(H);
        H = p;
    } while (H != NULL);
    return NULL;
};

node* _insert_begin(int value, node* pNode)
{   // insert 'value' at the start of the list
    node* new = (node*)malloc(sizeof(node));
    (*new).data = value;
    new->next = pNode;
    return new;
}

node* _insert_end(int value, node* pNode)
{   // insert value at the end of the list
    node* new = (node*)malloc(sizeof(node));
    new->data = value;
    new->next = NULL;
    if (pNode == NULL) return new;
    node* p = pNode;
    while (p->next != NULL) p = p->next;
    p->next = new;
    return pNode;
}

node* _insert_ordered(int value, node* pNode)
{   // insert value at ascending order in the list
    node* new = (node*)malloc(sizeof(node));
    new->data = value;
    new->next = NULL;
    if (pNode == NULL) return new;

    node* p = pNode;
    node* prev = NULL; // previous node: list if forward only
    while (p->next != NULL)
    {
        if (new->data < p->data)
        {
            // insert before first greater than value
            if (prev == NULL)
            {
                // new head
                new->next = p;
                return new;
            };  // if()
            prev->next = new;
            new->next = p;
            return pNode; // no change in head
        };
        prev = p; p = p->next; // updates pointers
    };  // while()
    // we are at the end: new will be the last?
    if (new->data < p->data)
    {
        if (prev == NULL)
            pNode = new;
        else
            prev->next = new;
        new->next = p;
    }
    else
    {
        p->next = new;
    };
    return pNode;
}   // _insert_ordered()

int display_list(node* p)
{
    if (p == NULL)
    {
        printf("empty list\n");
        return 0;
    };
    int count = 0;
    // not empty
    do
    {
        printf("%8d ", p->data);
        count++;
        if (count % 5 == 0) printf("\n");
        p = p->next;
    } while (p != NULL);
    if (count % 5 != 0) printf("\n");
    printf("list has %d elements\n", count);
    return count;
};

一個可以說更有用的鏈表結構

考慮以下

struct no
{
    void*       item;
    struct no*  next;
    struct no*  prev;
};  // no
typedef struct no Node;

typedef struct
{   // example, more flexible
    char*       name;
    unsigned    size;
    unsigned    capacity;
    Node*       head;
    Node*       tail;
}   Linked_list;

這樣,鏈表就被定義為節點的容器。

  • 它甚至還有一個可選的name
  • size始終可用並且是最新的
  • 大小限制可以作為capacity來實現
  • 在末尾開頭插入不需要您跟隨所有其他節點,因為列表封裝了指向頭和尾的指針
  • 一個節點具有指向下一個和前一個節點的指針,因此可以更輕松地迭代播放列表或 collections 等一些數據。
  • 一個程序可以有任意數量的列表,因為每個列表都封裝了所有這些元數據。
  • 列表可以包含任何內容,因為數據是指向 void、 void*的指針
  • empty() 或 size() 等函數可以輕松實現
  • 所有函數都使用指向列表的指針
    Linked_list  ll_one;
    Linked_list  many_ll[20];
    Linked_list* pLL = &ll_one;

關於:

void begin(node *head){

更改head只會更改調用堆棧的“head”,需要更改調用者的 function 中的“head”指向的位置。 為此,調用者必須傳遞“head”的地址。 'head' 本身就是一個指針這一事實無助於明確需要做什么,

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM