C ++链表节点引用自身

时间:2019-11-01 01:29:52

标签: c++ linked-list

我正在创建一个将卡片存放在卡座中的链表。它只有一个头,因为我不需要尾巴。当我添加第一张卡时,一切正常,当我添加第二张卡时,它会将自己设置为下一张并创建一个无限列表。

我不知道为什么会这样。我已经遍历了代码,每当在第二张卡上调用addCard()方法时,如果我不这样做,头部将神奇地更改为该卡。听起来很疯狂,但我找不到其他解释。非常感谢您的帮助。

标题

#pragma once
#include "Card.h"
class Deck {
public:
    bool isEmpty();
    ~Deck();
    void addCard(Card); //at top
    Card removeCard(); //from top
    void addAllCards(); //All 52 cards
    void print(); //print all the cards
private:
    int count = 0;
    Card* head{ NULL };
};

定义

#include "Deck.h"
#include <iostream>

bool Deck::isEmpty() {
    return this->count == 0;
}


void Deck::addCard(Card card) {
    if (isEmpty()) {
        this->head = &card;
    }
    else {
        card.next = this->head;
        this->head = &card;
    }
    count += 1;
}

Card Deck::removeCard() {
    if (!isEmpty()) {
        count -= 1;
        Card temp = *head;
        head = head->next;
        return temp;
    }
}

void Deck::addAllCards() {
    for (int suit = 0; suit < 4; suit++) {
        for (int rank = 0; rank < 13; rank++) {
            Card newCard = Card(suit, rank);
            this->addCard(newCard);
        }
    }
}

void Deck::print() {
    Card* current = head;
    while (current != NULL) {
        std::cout << current->stringSuitAndRank() << std::endl;
        current = current->next;
    }
}

Deck::~Deck() {
    while (!isEmpty()) {
        removeCard();
    }
}

驱动程序

#include <iostream>
#include <string>
#include "Card.h"
#include "Deck.h"

using namespace std;

int main()
{
    Card myCard1{ 2, 4 };
    Card myCard2{ 4, 5 };

    {
        Deck myDeck;

        myDeck.addCard(myCard1);
        myDeck.addCard(myCard2);

        cout << endl << "Printing deck..." << endl;
        myDeck.print();

        myDeck.removeCard();
        myDeck.removeCard();

        cout << endl << "Printing deck..." << endl;
        myDeck.print();

        cout << endl << "Adding all cards..." << endl << endl;
        myDeck.addAllCards();

        cout << endl << "Printing deck..." << endl;
        myDeck.print();
    }

    system("pause");
}

编辑:到达列表末尾时,在打印方法中也遇到读取访问冲突。我假设它与nullptr有关,但是帮助再次受到重视。

2 个答案:

答案 0 :(得分:3)

void Deck::addCard(Card card) {

card是此类方法的参数。方法参数实际上是方法中的局部对象。与在此方法中声明一个名为card的对象没有什么不同。唯一的区别是,实际对象是从调用此方法的人那里复制的,但是在所有其他方面,它与将在此方法中声明的名为card的变量没有什么不同。

就像在此方法中声明的任何其他变量一样,当它返回此名为card的对象时,它也会被销毁。它会消失的。不会了。它将不复存在。这将是事前对象。

this->head = &card;

addCard()的此部分在card成员中存储指向head的指针。到目前为止,一切都很好。但是,不久之后,该card对象将不再存在。往上看。这成为指向被破坏对象的指针。下次addCard()被呼叫时,就会出现欢闹。

要解决此问题,您需要重新阅读C ++书籍中的章节,其中介绍了C ++中的对象如何工作,何时创建以及何时销毁。如果您的目标是练习自己实现链接列表,并且不想使用C ++库的容器,那么您将需要使用newdelete来创建对象动态,因此当此方法返回时,这些对象将不会消失。他们不会再没有了。它们将不复存在。它们不会是前对象。

答案 1 :(得分:1)

您的addCard成员函数将Card作为值:

void Deck::addCard(Card card) {
    if (isEmpty()) {
        this->head = &card;
    }
    else {
        card.next = this->head;
        this->head = &card;
    }
    count += 1;
}

您要在链接列表中插入局部变量card,该局部变量在函数终止时会被破坏。

此问题的解决方法可能很简单:

void Deck::addCard(Card &card) { /*...*/ }
                        ^ pass reference

但是,我不这么认为,因为调用者也在自动存储中分配了这些对象;我们只会传递对调用方本地对象的引用,而不是动态分配的节点。

将本地对象放入链接列表是合法的,但是您必须确保在“超出范围”之前将其从列表中删除。可能出现以下情况:

{
  list_class li;
  node_class n1, n2, n3;
  li.add_by_reference(n1, n2, n3);
  // OK? maybe.
  // here n1, n2, n3 destructors get called then li.
  // if li doesn't touch the nodes, everything is cool
}

但这是不正确的:

{
  list_class li;

  for (int i = 0; i < 5; i++) {
    node_class n;
    li.add_by_reference(n);
  }

  // A new n object is created and destroyed for each loop iteration.
  // The list still has links to a destroyed node, and a new one is
  // being added to it.
}

程序中的情况(特别是Deck::addAllCards中的情况)类似于此处的第二个示例。第二个示例的解决方法是动态分配:

li.add_by_reference(*new node_class);

现在,我们发生了内存泄漏; list_class的析构函数可能不知道节点是动态分配的,并且不会释放它们。

假设我们在函数终止之前放入代码以释放节点,例如:

while (!li.empty()) {
  node_class *pn = li.dequeue_head();
  delete pn;
}

但是,异常安全性存在问题;如果抛出new,则该函数将被放弃而不执行此循环;仅会调用li的析构函数。