简体   繁体   中英

Pointer to pointer in linked list

Can someone explain me why this code give me as result the empty 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;
}

But if i change the begin() function with the pointer to pointer it gives to me the right list?

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;
}

Can you also explain me why when i pass in the main the node head to the function begin i have to pass it as "&head"? and no more as "head"

In the first program in this code snippet

head = NULL;

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

the pointer head is passed to the function begin by value. That is a copy of the value of the pointer head declared in main is created and is assigned to the parameter with the same name of the function begin

void begin(node *head);

So within the function it is the parameter head that holds initially a copy of the original pointer head that is changed. The original pointer head the value of which was assigned to the parameter is not being changed.

To change the original pointer head declared in main you have to pass it to the function by reference indirectly through a pointer to the pointer head as it is done in the second program.

So the function should be declared like

void begin(node **head);

And you have to pass the pointer head indirectly through a pointer to it

begin( &head );

In this case dereferencing the passed pointer the function will get a direct access to the original pointer head declared in main and can change it (not a copy of its value as it takes place in the first function definition)

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

To make it more clear consider this simple demonstrative program.

#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;
}

Its output is

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

As the function f deals with a copy of the value of the passed argument the value of the variable t declared in main was not changed.

So you need to pass the original variable t by reference through pointer like

#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;
}

Now the program output is

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

In these demonstrative programs the name T is used as an alias for the type int and in main the object t has this type.

Let's now assume that the name T is an alias for the type int *.

typedef int * T;

In this case a declaration in main as for example

T t = NULL;

means that the variable t has the pointer type int * . That is it is equivalent to

int * t = NULL;

So to pass it to a function that must change the original variable t we need to pass it by reference like

f( &t );

that means that the corresponding function shall have the parameter type declared like

void f( T *t );

but as T is an alias for int * hence it means that the function has a parameter of the type int ** .

void f( int * *t );

Because head is (effectively) a local variable, so changing it has no effect outside of the function, whereas changing *head changes what head points to, and thus does.

If you wanted a function to be able to change the value in an int variable (say, x ), you would pass it a pointer to x , which would have the type int* and you would get the pointer to x by using &x . The same holds no matter what type x is.

A bit of confusion may come from declaring

    node        *head;

instead of

    node*       head;

You are declaring head . head is the variable and it is a pointer. It is not a node. Note also that a node is not a linked list: a linked list is a collection of nodes and possibly something else in order to have an useful implementation. More on this later at the end.

Fact is you have in main() declared head , just a node* . The node itself does not even exist yet. You declared begin() as

    void begin(node *head);

and I think you will see it more clearly as

    void begin(node*  parameter);

parameter is node* .

Inside begin() you get a copy of the pointer and changing the pointer will not change the original pointer in main() . In your case it will in main() forever point to NULL .

What matters is that a pointer is like any variable: A pointer has an address. And a content. When you pass by value, just like you did, the pointer in begin() starts with NULL , the VALUE that came from main() . But the bond between them ends int the call: the initial value.

When you pass a pointer to begin() , using the operator 'address of' and writing &head things change: you will change it using the operator '*' meaning that you will change the address it points to, so it will change in main() . Since head is node* a pointer to it will be declared as node**

But consider changing the declaration of begin() for a linked list using:

    node* begin(node* node);

The logic is that inserting a node can change the head of the list, so you return the new address, as in

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

is a common way to write this. Another is to use node** .

The way I am describing here, any operation that can change the head of the list must

  • return the new head
  • receive and update a pointer to the pointer of the head

See again this code that inserts at the beginning of the list:

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;
}

returning new you get head updated. And you can write in 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);

Note the line another = _insert_begin(i, another); and you see how the pointer in main() gets updated.

This is the output

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

Using this implementation of display_list() , that prints 5 values per line:

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;
};

Another example: inserting at the end

note that inserting at the end can also change the head, in the case that the list is empty, so we still need to return the head address

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;
}

Another use: inserting in ascending order

Sure, inserting in ascending order can also change the head, as in

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()

Deleting a list

Delete a list should also return a node* in order to invalidade the head pointer. It is usual. As you get used to the mechanic of it this ensures that an invalid pointer does not remain around.

Note that this logic is cooperative: you must assign the head pointer back at every call that can change the head

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;
};

A running program

Output of the example program

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

The example C program

#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;
};

An arguably more useful Linked List structure

Consider the following

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;

This way a linked list is defined as a container of nodes.

  • It has even an optional name .
  • size is always available and up to date
  • a size limit can be implement as capacity
  • insert at the end and at the beginning does no require you to follow all other nodes, since the list encapsulates pointers to both head and tail
  • a node has pointers to next AND previous nodes so some data such as playlists or collections like that can be iterated more easily.
  • a program can have any number of lists since each one encapsulates all of this metadata.
  • a list can contains anything since the data is a pointer to void, void*
  • functions like empty() or size() can be implemented easily
  • all functions use a pointer to the list
    Linked_list  ll_one;
    Linked_list  many_ll[20];
    Linked_list* pLL = &ll_one;

regarding:

void begin(node *head){

Changing head only changes the call stack 'head', what is needed is to change where 'head' in the caller's function points to. TO do that, the caller must pass the address of 'head'. The fact that 'head' is,itself, a pointer doesn't help with the clarity of what needs to be done,

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM