Reputation:
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"
Upvotes: 1
Views: 800
Reputation: 16540
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,
Upvotes: 0
Reputation: 1765
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
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.
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;
};
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;
}
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()
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;
};
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
#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;
};
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.
name
.size
is always available and up to datecapacity
void*
Linked_list ll_one;
Linked_list many_ll[20];
Linked_list* pLL = &ll_one;
Upvotes: 0
Reputation: 49803
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.
Upvotes: 0
Reputation: 310990
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 );
Upvotes: 1