简体   繁体   English

为什么使用本地指针不起作用

[英]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. 我知道这段代码不起作用,因为插入函数中的'itr'是局部变量,因此,它不会反映方法之外的树。 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. 虽然'itr'是局部变量,'itr'指向'root'指向的相同位置。 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. 是的,你真的需要在这个c ++代码中使用引用。

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. 如果以正确的方式思考它们,C(和C ++)中的指针并不是那么困难。

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? 问:在调用foo()之后, main()i的值是多少?

A. i is passed to foo() by value, so the i in main() is not modified by foo() . A. i通过值传递给foo() ,因此main()i不会被foo()修改。 i is still 5. i还是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? 问:在调用foo()之后, main()i的值是多少?

A. i is passed to foo() by value, so the i in main() is not modified by foo() . A. i通过值传递给foo() ,因此main()i不会被foo()修改。 i is still 0. i还是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. i现在是一个指针的事实并没有改变它是通过值传递的。

Actually, in C, all function parameters are by value. 实际上,在C中,所有函数参数都是按值计算的。 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.) (对于C来说也是如此。在C ++中你也可以使用引用,但我只是在这里谈论指针。)

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. 这真是最棘手的概念,所以让我们画一些ascii。

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

Let me introduce you to int i . 我来介绍一下int i i is 4 bytes (on this system) starting at memory address 0x04762198 . i是4个字节(在这个系统上)从内存地址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. 如果我们为i赋值,则该值将存储在上面的内存块中。

If we pass i to a function, the value of i will be copied to somewhere else in memory for use by the function. 如果我们将i传递给函数,则i的值将被复制到内存中的其他位置以供函数使用。 The value of that memory will be the same as our original i , but the memory address of that variable will be somewhere else. 该内存的值将与我们的原始i相同,但该变量的内存地址将位于其他位置。

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 ! 如果我们将0x04762198传递给函数,那么该函数现在可以访问原始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; ). 如果我们想使用指针修改函数内部的原始i ,我们取消引用它(例如。 *ptr = 5; )。 What we are actually doing is saying "please store this value (5) in the memory pointed to by ptr " ). 我们实际在做的是说“请将此值(5)存储在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. 注意:为了简洁,我省略了通常的错误检查(例如,检查malloc()不返回NULL)。

Suppose you had 假设你有

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

Would you expect that x also contain 3478 ? 你期望x还包含3478吗?
I knew you wouldn't, and the same is true for your root and itr . 我知道你不会,你的rootitr也是如此。

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. 这是你的一个案例的ASCII版本,你要插入到右边,右边是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) 内联void插入(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;
}

} }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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