简体   繁体   中英

why using a local pointer wouldn't work

inline void insert(node *root, int value)
{
    if(!root)
    {
        root = new node();
        root->value = value;
    }
    else
    {
        node *itr = root;
        while(1)
        {
            if(itr->value > value)
                itr = itr->left;
            else
                itr = itr->right;
            if(!itr)
            {
                itr = new node();
                itr->value = value; 

                break;
            }

        }
    }
}

//call insert function like this

node *head = 0;
insert(head, 5);
insert(head, 10);
insert(head, 3);
insert(head, 1);
insert(head, 4);

I know this code wouldn't work because 'itr' in insert function is a local variable, thus, it wouldn't reflect the tree outside of the method. However, I don't clearly understand why it wouldn't work. Although 'itr' is a local variable, 'itr' points to the same location where 'root' points to. Also, I am dereferencing it to move 'left' or 'right' so I think it should work.

I think this is a basic problem of passing a pointer by value vs pointer but I couldn't find a clear explanation of why I can't change the tree using a local variable of a pointer.

Yes, you really need to use references in this c++ code.

node * treeRoot = 0;
insert( treeRoot , 4 );  // treeRoot needs to be changed for this to work. So a reference is required. 

The function should in this case be declared as

void insert(node* &root, int value);

The alternative would be to use a double pointer.

node * treeRoot = 0;
insert( &treeRoot , 4 );  // treeRoot needs to be changed for this to work. So a pointer to the pointer will work. 

The function should in this case be declared as

void insert(node** root, int value);

Also this is an excellent opportunity to explore a compact and elegant recursive solution. (The inlining does not work here though)

void insert(node* &root, int value)
{
    if(!root)
    {
        root = new node();
        root->value = value;
    }
    else
    {
        if(root->value > value)
            insert( root->left, value );
        else
            insert( root->right, value);
    }
}

There are two issues here. First, in your example usage, you have:

node *head = 0;

If you run this, you'll end up creating a new node every invocation because

if (!root) {}

Will always be true. Secondly, as you've pointed out, when you update the tree, you're creating a new node but not actually inserting it into the tree. To correct both of these issues, you can do the following:

node* insert(node *root, int value) {
    if(!root) {
        root = new node();
        root->value = value;
    } else {
        node *itr = root;
        node **where = 0;
        while(1) {
            if(itr->value > value) {
                where = &itr->left;
                itr = itr->left;
            } else {
                where = &itr->right;
                itr = itr->right;
            }
            if(!itr) {
                itr = new node();
                itr->value = value; 
                // Insert the new node, to fix the second issue
                *where = itr;
                break;
            }
            prev = itr;    
        }
    }
    return root; // This always returns the tree, solves problem 1, which can also be solved using references or ptr-to-ptr as other people have mentioned
}

Then your example would be:

node* head = insert(0, 5);
insert(head, 10);
insert(head, 3);
insert(head, 1);
insert(head, 4);

Though there's still the issue of inserting the same number twice, which isn't handled properly here. And for testing sake, it's a better practice to avoid creating new nodes within a function like this. It'd be better to create the node then give the node with the new value to insert, and letting insert figure out where to link it into the tree (that includes the head).

Pointers in C (and C++) are not really that difficult if you think about them the right way.

Let me demonstrate with the following code:

void foo(int i) {
    i = i + 5;
}

int main()
{
    int i = 5;

    foo(i);
    printf("i is %d\n", i);

    return 0;
}

Q. What will be the value of i in main() after foo() is called?

A. i is passed to foo() by value, so the i in main() is not modified by foo() . i is still 5.

Now, let's change the code a bit:

void foo(int* i) {
    i = malloc(sizeof(int));
}

int main()
{
    int *i = 0;

    foo(i);
    printf("i is %p\n", i); /* printf a pointer with %p */

    return 0;
}

Q. What will be the value of i in main() after foo() is called?

A. i is passed to foo() by value, so the i in main() is not modified by foo() . i is still 0.

In other words, nothing has changed! The fact that i is now a pointer does not change that it is being passed by value.

Actually, in C, all function parameters are by value. So, how do we get a function to modify a variable?

If you want to pass a variable to a function for that function to modify it, you must pass the address of that variable to the function. (This is true for C. In C++ you can also use references, but I'm speaking only about pointers here.)

When you pass the address of a variable, you are doing two things:

  • You are calculating the memory address of a variable

  • You are passing that memory address by value to the function.

The memory address can be used to modify the memory to which the memory address points. Since the memory address inside the function is the same as the one outside the function call (because it's passed by value), the variable they point to is the same one!

This is really the trickiest concept, so let's draw some ascii.

|            |
+------------+ <- 0x04762198
|            |
|     i      |
|            |
|            |
+------------+ <- 0x0476219C
|            |

Let me introduce you to int i . i is 4 bytes (on this system) starting at memory address 0x04762198 . All variables are stored somewhere in memory and will be at memory addresses such as this.

If we assign a value to i , the value will be stored in the above block of memory.

If we pass i to a function, the value of i will be copied to somewhere else in memory for use by the function. The value of that memory will be the same as our original i , but the memory address of that variable will be somewhere else.

Here's the ingenious bit. If we instead pass 0x04762198 to a function, that function now has access to the memory location of the original i ! This is a pointer, so called because it points to an address in memory. If we want to modify the original i inside the function using the pointer, we dereference it (eg. *ptr = 5; ). What we are actually doing is saying "please store this value (5) in the memory pointed to by ptr " ).

Let's change the code again to implement this:

/*
 * The address of an int* is int**
 */
void foo(int** i) {
    /* dereference to access and modify the original `i` */
    *i = malloc(sizeof(int));
}

int main()
{
    int *i = 0;

    /*
     * Pass the address of i, so foo() can modify i
     */    
    foo(&i);
    printf("i is %p\n", i); /* printf a pointer with %p */

    return 0;
}

See the difference?

Now, can you see what you are doing wrong in your own program?

NOTE: I have left out the usual error checking (eg. checking that malloc() does not return NULL) for brevity.

Suppose you had

int x = 0;
int y = x;
y = 3478;

Would you expect that x also contain 3478 ?
I knew you wouldn't, and the same is true for your root and itr .

This is a classic pencil-and-paper problem (most pointer problems are) and it's definitely worth pulling out some dead tree when you run into problems like this.

Here's an ASCII version for one of your cases, where you want to insert to the right, and right is NULL.
The arrows show where the respective variables are pointing.

Start of function:

           ____
root ---> |    |
          ------
           / \
          /   \
       left   NULL

itr = root;    
           ____
root ---> |    | <--- itr
          ------
           / \
          /   \
       left   NULL

itr = itr->right;
           ____
root ---> |    | 
          ------
           / \
          /   \
       left   NULL <--- itr

if (!itr)
    itr = new node();

           ____
root ---> |    | 
          ------
           / \
          /   \                   ____
       left   NULL      itr ---> |    |
                                  ----

As you can see, the input tree hasn't been modified at all, you just allocated a new node outside of it and left it there.

This would work:

           ____
root ---> |    | <--- itr
          ------
           / \
          /   \
       left   NULL

   if (!itr->right)
   {
       itr->right = new node()
   }


           ____
root ---> |    | <--- itr
          ------
           / \
          /   \
       left   ____
             |    |
              ----

Pencil and paper is the best way to figure out pointers.

you made a node but didn't insert it to the tree . try this code :

inline void insert(node *root, int value)

{

if(!root)

{
    root = new node();
    root->value = value;
}
else
{
    node *itr = root , *prev;
    while(itr)
    {
        prev=itr;
        if(itr->value > value)
            itr = itr->left;

        else
            itr = itr->right;
    }

        itr = new node();
        itr->value = value;

        if(value < prev->value)
        prev->left=itr;
        else
        prev->right=itr;
}

}

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