繁体   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