【问题标题】:How do I Implement the Big Three Correctly: Singly Linked List (C++)如何正确实现三巨头:单链表(C++)
【发布时间】:2014-06-03 11:52:10
【问题描述】:

我正在编写一个包含一个单链表来保存购物清单的程序。每个节点都有项目名称、数量和数量描述(即鸡蛋的打数)。除析构函数外,程序中的所有内容都可以找到。我似乎无法找到它有什么问题。

驱动程序将执行到代码为return 0; 的末尾,然后调用析构函数并在delete current; 行停止并显示以下消息:

“项目 14.exe 中 0x0FC7A9E8 (msvcr120d.dll) 的未处理异常:0xC0000005:访问冲突读取位置 0xFEEEFEE2。”。

我已经发布了下面三大功能的实现。默认构造函数将两个指针(firstlast)初始化为null,将nodeCount 初始化为0

我似乎找不到问题所在。有什么帮助吗?

List::List(const List& b)
{
    Node* newNodePtr = new Node;
    Node* nodeCopy = b.first;
    newNodePtr = nodeCopy;
    first = newNodePtr;
    last = newNodePtr;
    nodeCount++;
    nodeCopy = nodeCopy->getNext();
    while (last != b.last)
    {
        Node* newNode = new Node;
        newNode = nodeCopy;
        Node* currentNode = last;
        currentNode->setNext(newNode);
        last = newNode;
        nodeCount++;
        nodeCopy = nodeCopy->getNext();
    }
}

List::~List()
{
    Node* current = first;
    while (current != nullptr)
    {
        Node* _next = current->getNext();
        delete current;
        current = _next;
    }
    first = nullptr;
    last = nullptr;
}

List& List::operator=(const List& rho)
{
    Node* current = first;
    while (current != nullptr)
    {
        Node* _next = current->getNext();
        delete current;
        current = _next;
    }
    first = nullptr;
    last = nullptr;

    Node* newNodePtr = new Node;
    Node* nodeCopy = rho.first;
    newNodePtr = nodeCopy;
    first = newNodePtr;
    last = newNodePtr;
    nodeCount++;
    nodeCopy = nodeCopy->getNext();
    while (last != rho.last)
    {
        Node* newNode = new Node;
        newNode = nodeCopy;
        Node* currentNode = last;
        currentNode->setNext(newNode);
        last = newNode;
        nodeCount++;
        nodeCopy = nodeCopy->getNext();
    }
    return *this;
}

编辑:我还添加了我的 push_back 函数:

void List::push_back(Node* newNode)
{
if (first == nullptr)
{
    first = newNode;
    last = newNode;
}
else
{
    Node* currentNode = last;
    currentNode->setNext(newNode);
    last = newNode;
}
nodeCount++;
}

好吧,我想我已经想通了。这段代码似乎有效,它适合我教授提供的驱动程序。下面我列出了三大函数以及它们调用的所有其他函数:

List::List(const List& b)
{
    this->copyList(b);
}

List::~List()
{
    this->clearList();
}

List& List::operator=(const List& rho)
{
    this->clearList();
    this->copyList(rho);
    return *this;
}

void List::clearList()
{
    Node* current = first;
    while (current != nullptr)
    {
        current = pop_front();
        delete current;
        current = first;
    }
    first = nullptr;
    last = nullptr;
}

void List::copyList(const List& b)
{
    first = nullptr;
    last = nullptr;
    nodeCount = 0;
    Node *headNode = b.getFirst();
    while (headNode != nullptr)
    {
        string des = headNode->getDescription();
        string qNa = headNode->getQuantityName();
        int qNu = headNode->getQuantityNumber();
        Node* newNode = new Node(qNu, qNa, des);
        push_back(newNode);
        headNode = headNode->getNext();
    }
}

Node* List::pop_front()
{
    Node* saveFirst = first;
    first = first->getNext();
    nodeCount--;
    return saveFirst;
}

void List::push_back(Node* newNode)
{
    if (nodeCount == 0)
    {
        first = newNode;
        last = newNode;
    }
    else
    {
        Node* currentNode = last;
        currentNode->setNext(newNode);
        last = newNode;
    }
    nodeCount++;
}

【问题讨论】:

  • Node 的析构函数呢?你能给我们看看吗?
  • 教授告诉我不要为 Node 类包含析构函数。
  • 这段代码异常不安全,而且泄漏得非常厉害。你的教授也禁止你使用std::shared_ptr吗?
  • 一个泄漏示例:Node* newNodePtr = new Node; Node* nodeCopy = b.first; newNodePtr = nodeCopy; 泄漏通过new 分配的内存。
  • @Arun - 没错,我同意。许多拥有这些链表分配并实现复制构造函数的发布者都很难做到。他们没有看到他们正在编写的所有代码都已经在其他函数中完成了。如果他们没有编写这些其他函数,那么他们编写的链表有什么用,例如,如果您无法从中获取任何信息,或者无法向其中添加项目。

标签: c++ list memory-management linked-list destructor


【解决方案1】:

这可能无法解决您的确切问题,但如果您有您提到的功能,那么复制构造函数的伪代码将如下所示。

List::List(const List& b)
{
   Node *headNode = b.getHeadNode();
   while (headNode != NULL)
   {
      push_back(headNode->getDataFromNode());
      headNode = headNode->getNextNode();
  }
}

所以基本上,简而言之就是整个复制构造函数。您基本上是从列表中的第一个节点开始,从该节点获取数据,然后调用 push_back() 来添加新数据。我假设 push_back() 完成了创建节点、向其添加数据并将其正确放置在列表末尾的所有棘手工作。

请注意这个实现是多么的小巧、紧凑和直观。我们知道,创建链表的副本所需要做的就是首先确保链表为空(它用于新对象),然后不断将旧链表中的项目添加到新链表中。由于 push_back() 将一个项目添加到列表中(并且具有创建节点并将其链接到结束节点的所有复杂性),我们以一种智能的方式使用它来创建我们的副本。

请注意,您还需要一个赋值运算符来配合复制构造函数。示例中的赋值运算符只是调用 clear()(如果您有这样的函数)在继续之前删除所有节点。

请记住,所有这些都需要您的 push_back() 函数完美运行。它应该知道如何正确处理在空列表和非空列表末尾的插入。

编辑:

如果您的驱动程序代码确实在 push_back 之前创建了新节点(因此 push_back 不分配新节点),则可以使用替代代码:

List::List(const List& b)
{
   Node *headNode = b.getHeadNode();
   while (headNode != NULL)
   {
      Node *newNode = new Node(headNode->getDataFromNode());
      push_back(newNode);
      headNode = headNode->getNextNode();
  }
}

在替代版本中,我假设可以使用数据作为参数来创建新节点的构造函数。我个人不喜欢让 push_back() 不能完成创建节点的所有工作的设计,但这是另一个问题。

【讨论】:

  • 感谢您的帮助!如果您不介意看一下,我将我的固定代码添加到帖子中。我没有在push_back 函数中动态分配新节点的唯一原因是驱动程序已经这样做了(我们不应该更改提供的驱动程序)。我通过在copyList 函数中分配一个新节点来弥补这一点。但是,在这个项目之外,我明白你在说什么,我会确保在类代码中创建节点。
  • @user3208991 - 我更新了我的答案以包括您当前的方案,即让 push_back() 只需在列表末尾添加节点。
【解决方案2】:

它至少部分取决于调用析构函数时首先指向的内容。

您的代码没有复制节点的内容。相反,它只是在操纵指针,所以正如 dyp 指出的那样,你有一个泄漏: 节点* newNodePtr = 新节点; 节点* nodeCopy = b.first; newNodePtr = nodeCopy;

您可能想了解一下复制交换习语。 What is the copy-and-swap idiom?

【讨论】:

    猜你喜欢
    • 2017-03-06
    • 2018-07-29
    • 2018-10-16
    • 1970-01-01
    • 2013-12-03
    • 2019-09-02
    • 1970-01-01
    • 2015-08-18
    • 2011-02-14
    相关资源
    最近更新 更多