【问题标题】:Linked list prints unexpected value链表打印出意外的值
【发布时间】:2018-04-23 15:43:34
【问题描述】:

无论我检查了多少参考资料,我总是发现我的实现是合理的。 但是,这个程序不起作用,我不知道为什么。 请帮忙。谢谢你。 我有这门课

class intNode
{
    int x;
    intNode * next;
    public:
        intNode();
        intNode(int y, intNode *p);
        setNode(int y, intNode *p);
        int getX();
        void setX(int y);
        void setNext(intNode *p);
        intNode* getNext();
};

还有这个类

class intList
{
    private:
       intNode * head;
    public:
       intList(); //sets head=NULL
       void push( int x);
       void print();
 }

推送如下

      void intList::push(int x)
      {
        intNode *newNode;
        newNode->setX(x);
        newNode->setNext(head);
        head = newNode;
      }

打印如下

void intList::print()
{
    intNode *current = head;
    cout << "Printing list" << endl;
    while(current != NULL)
    {
        cout << current->getX() << "\t";
        current = current->getNext();
    }
    cout << endl;
}

但不知何故,这段代码在main

intList l;
l.push(5);
l.print();

返回这个奇怪的值:6946556

【问题讨论】:

  • 在您的intList::push 函数中,您有一个指针变量newNode但是你永远不会让它指向任何地方!
  • 你永远不会创建节点。此外,您的老师似乎还停留在 90 年代初。
  • 无论我检查了多少引用,我总是发现我的实现是正确的。 -- 这是指针必须指向某个有效位置才能使用它的基本原则,我很惊讶你正在尝试编写一个链表类。
  • std::list&lt;&gt; 模板呢?
  • @S.Toonsi -- 评论部分是给 cmets 的,所以你会收到关于你的代码的 cmets。答案在“答案”框中。几乎所有 C++ 中的链表实现都表明链表中的节点是使用 new 分配的——不知道你是否认为这样做没有必要。

标签: c++ class object linked-list


【解决方案1】:

intList::push 中,它不会创建新节点,而是为其使用不确定的指针值。

必须分配新节点:

void intList::push(int x) {
    head = new intNode(x, head);
}

您可能希望在启用警告的情况下编译您的代码,因为那将是一个编译器警告。对于g++,请使用-Wall -Wextra -Werror 命令行选项。

【讨论】:

  • @S.Toonsi 不要忘记删除你用new分配的内存。
【解决方案2】:

让我们检查一下您的 push 方法,看看哪里出了问题。

intNode *newNode;

您现在有一个名为newNode 的变量,它是一个指向intNode 的指针。 该指针当前未设置为任何值(我们称之为“未初始化”)。

试图访问一个未初始化的变量(例如取消引用它或调用它的方法)会导致undefined behavior,这是另一种说法“所有赌注都没有”——标准没有说明应该发生什么,所以你的编译器可以生成它想要的任何东西。

所以当你这样做时:

newNode->setX(x);

您现在正在对未初始化的指针newNode 调用方法。除此之外的任何事情都与我们对代码的检查几乎无关,因为编译器在优化程序时可能会无意中执行许多看似“疯狂”的事情。

例如,您的程序在g++ 6.4.0-O0 上运行良好。它打印 5。这是因为编译器没有优化任何东西,我们显然得到了“幸运”,而 newNode“恰好是”的值实际上是一个有效的地址。

但是,当我转到-O1 时,程序实际上根本没有输出任何值。我的怀疑是编译器确定 push 在所有代码路径上都会导致 UB,因此得出结论,绝对不能调用 push,并且根本不费心为该方法生成任何程序集。 (注意:我当时没有在程序集中验证这一点。)

这只是调用未定义行为时发生的疯狂事情之一 - 请参阅上面链接的文章了解其他一些内容。未定义的行为应该绝不在您的代码中发生。

现在,正确的做法是分配一个 new 节点并设置指针指向它。用

初始化它
intNode *newNode = new intNode();

,那么您的代码看起来不错。不要忘记new 是一个堆分配——你的工作是确保它在不再需要时是deleted(当你删除节点时),否则你会有杂散的内存没有被使用( memory leak)。

PS:如果你用-Wall 选项调用g++,它会警告你这个错误:

test.cpp: In member function ‘void intList::push(int)’:
test.cpp:30:17: warning: ‘newNode’ is used uninitialized in this function [-Wuninitialized]
 newNode->setX(x);

始终注意编译器警告 - 它们通常是有充分理由的!

【讨论】:

    【解决方案3】:

    Treeston 很好地解释了为什么这里会出现任意怪异现象,但我不喜欢他使用 new 的建议。

    您的 intNode 拥有 next 指针,并且它是 唯一 所有者,因此您应该改用 std::unique_ptr。同样 intList 拥有 head.

    class intNode {
        friend class intList;
        int x;
        std::unique_ptr<intNode> next;
    public:
        intNode();
        intNode(int _x, std::unique_ptr<intNode> _next); 
        int getX();
        intNode * getNext();
    };
    
    intNode::intNode() {}
    
    intNode::intNode(int _x, std::unique_ptr<intNode> _next)
        : x(_x), next(std::move(_next)) {}
    
    class intList {
        std::unique_ptr<intNode> head;
    public:
        void push(int x);
        void print();
    }
    
    void intList::push(int x) {
        head = std::make_unique(x, std::move(head));
    }
    
    void intList::print()
    {
        cout << "Printing list" << endl;
        for(intNode * current = head.get(); current; current = current->getNext())
        {
            cout << current->getX() << "\t";
        }
        cout << endl;
    }
    

    【讨论】:

    • 是的!无论如何,在实际应用程序中,这就是你要做的。但是,作为练习的 LL 实现通常还旨在教授基本的内存管理概念,因此我希望老师希望学生使用 new 并了解内存泄漏。
    • 我希望老师教学生使用new
    • 你最终会继续教学生不要这样做,是的。但是,在向他们解释为什么他们应该使用 STL 指针包装器之前,他们首先需要了解他们的目的是什么(通过了解内存管理的作用)。
    猜你喜欢
    • 1970-01-01
    • 2018-03-26
    • 1970-01-01
    • 1970-01-01
    • 2012-01-27
    • 2016-04-03
    • 2013-03-21
    • 2020-01-22
    • 1970-01-01
    相关资源
    最近更新 更多