指针和参考问题

时间:2013-04-20 10:32:28

标签: c++

我正在创建类似于结构列表的东西。在main的开头我声明了一个空指针。然后我多次调用insert()函数,传递对该指针的引用,以添加新元素。

然而,似乎有些不对劲。我无法显示列表的元素,std::cout只是打破了程序,即使编译器没有警告。

#include <iostream>

struct node {
    node *p, *left, *right;
    int key;
};

void insert(node *&root, const int key)
{
    node newElement = {};
    newElement.key = key;

    node *y = NULL;
    std::cout << root->key; // this line
    while(root)
    {
        if(key == root->key) exit(EXIT_FAILURE);
        y = root;
        root = (key < root->key) ? root->left : root->right;
    }

    newElement.p = y;

    if(!y) root = &newElement;
    else if(key < y->key) y->left = &newElement;
    else y->right = &newElement;
}

int main()
{
    node *root = NULL;
    insert(root, 5);
        std::cout << root->key; // works perfectly if I delete cout in insert()
    insert(root, 2);
        std::cout << root->key; // program breaks before this line
    return 0;
}

如您所见,我在insert函数中创建了新的结构元素并将其保存在根指针中。在第一次调用中,while循环甚至没有启动,因此它可以工作,并且我能够在main函数中显示root的元素。

但是在第二次调用中,while循环已经有效,我得到了我描述的问题。

root->key语法有问题,因为即使我把它放在第一次调用中它也不起作用。

出了什么问题,原因是什么?

,我总是看到通过这样的指针插入新列表的元素:

node newElement = new node();
newElement->key = 5;
root->next = newElement;

此代码是否等于:

node newElement = {};
newElement.key = 5;
root->next = &newElement;

?它会更清洁,不需要删除内存。

4 个答案:

答案 0 :(得分:2)

问题是因为您正在从函数中传递指向局部变量的指针。取消引用此类指针是未定义的行为。您应该使用newElement分配new

此代码

node newElement = {};

创建一个局部变量newElement。函数结束后,newElement的范围结束,其内存被破坏。但是,您将指向已销毁内存的指针传递给函数外部。一旦函数退出,对该存储器的所有引用都将变为无效。

此代码另一方面

node *newElement = new node(); // Don't forget the asterisk

在免费商店分配对象。在明确delete之前,此类对象仍然可用。这就是为什么你可以在创建它们的函数退出后使用它们。当然,由于newElement是指针,因此您需要使用->来访问其成员。

答案 1 :(得分:1)

您需要了解的关键是 stack 分配的对象和 heap 分配的对象之间的区别。在插入函数中,node newElement = {}是堆栈分配的,这意味着它的生命周期由封闭范围决定。在这种情况下,这意味着当函数退出对象时会被销毁。那不是你想要的。您希望树的根存储在node *root指针中。为此,您需要从堆中分配内存。在C ++中,通常使用new运算符完成。这允许您将指针从一个函数传递到另一个函数,而不会将其生命周期确定为它所在的范围。这也意味着您需要注意管理堆分配对象的生命周期。

答案 2 :(得分:0)

你的同样评论有一个问题。第二个可能更干净,但这是错误的。你需要新的内存并删除它。否则,您最终会得到指向不再存在的对象的指针。这正是新解决的问题。

另一个问题

void insert(node *&root, const int key)
{
    node newElement = {};
    newElement.key = key;

    node *y = NULL;
    std::cout << root->key; // this line

在第一个插入根目录仍为NULL,因此该代码将使程序崩溃。

答案 3 :(得分:0)

已经解释过你必须动态分配对象(使用new),但这样做会带来危险(内存泄漏)。

有两个(简单)解决方案:

  1. 拥有所有权计划。
  2. 使用竞技场放置节点,并保留对它们的引用。
  3. 1所有权计划

    在C和C ++中,有两种形式可以获取存储对象的内存:自动存储和动态存储。例如,当您在函数中声明变量时,自动就是您使用的,但是这些对象仅在函数的持续时间内存在(因此,之后使用它们时会遇到问题,因为内存可能被其他内容覆盖)。因此,您经常必须使用动态内存分配。

    动态内存分配的问题是您必须明确地将其返回给系统,以免泄漏。在C中,这非常困难,需要严谨。在C ++中,虽然通过使用智能指针使其变得更容易。那就让我们用吧!

    struct Node {
        Node(Node* p, int k): parent(p), key(k) {}
    
        Node* parent;
        std::unique_ptr<Node> left, right;
        int key;
    };
    
    // Note: I added a *constructor* to the type to initialize `parent` and `key`
    // without proper initialization they would have some garbage value.
    

    请注意parentleft的不同声明?父母拥有其子女(unique_ptr),而孩子只是指其父母。

    void insert(std::unique_ptr<Node>& root, const int key)
    {
        if (root.get() == nullptr) {
            root.reset(new Node{nullptr, key});
            return;
        }
    
        Node* parent = root.get();
        Node* y = nullptr;
    
        while(parent)
        {
            if(key == parent->key) exit(EXIT_FAILURE);
            y = parent;
            parent = (key < parent->key) ? parent->left.get() : parent->right.get();
        }
    
        if (key < y->key) { y->left.reset(new Node{y, key}); }
        else              { y->right.reset(new Node{y, key}); }
    }
    

    如果你不知道unique_ptr是什么,get()它只包含一个用new分配的对象,get()方法返回一个指向该对象的指针。您还可以reset其内容(在这种情况下,它可以正确处理已包含的对象,如果有的话)。

    我会注意到我对你的算法不太确定,但是,嘿,这是你的:)

    2 Arena

    如果这种处理内存的问题让你的头脑变得糊涂,那一开始就很正常,这就是为什么有时竞技场可能更容易使用。使用竞技场的想法非常普遍;而不是一个接一个地打扰内存所有权,你使用“东西”来保持内存,然后只操纵引用(或指针)。你只需要记住,只要竞技场是,那些引用/指针才会存在。

    struct Node {
        Node(): parent(nullptr), left(nullptr), right(nullptr), key(0) {}
    
        Node* parent;
        Node* left;
        Node* right;
        int key;
    };
    
    void insert(std::list<Node>& arena, Node *&root, const int key)
    {
        arena.push_back(Node{});         // add a new node
        Node& newElement = arena.back(); // get a reference to it.
        newElement.key = key;
    
        Node *y = NULL;
        while(root)
        {
            if(key == root->key) exit(EXIT_FAILURE);
            y = root;
            root = (key < root->key) ? root->left : root->right;
        }
    
        newElement.p = y;
    
        if(!y) root = &newElement;
        else if(key < y->key) y->left = &newElement;
        else y->right = &newElement;
    }
    

    记住两件事:

    • 你的竞技场一旦死亡,你所有的引用/指针都指向以太,如果你试图使用它们会发生不好的事情
    • 如果您只是将内容推入arena,它会一直增长,直到消耗掉所有可用内存并导致程序崩溃;在某些时候你需要清理