我正在创建一个将卡片存放在卡座中的链表。它只有一个头,因为我不需要尾巴。当我添加第一张卡时,一切正常,当我添加第二张卡时,它会将自己设置为下一张并创建一个无限列表。
我不知道为什么会这样。我已经遍历了代码,每当在第二张卡上调用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有关,但是帮助再次受到重视。
答案 0 :(得分:3)
void Deck::addCard(Card card) {
card
是此类方法的参数。方法参数实际上是方法中的局部对象。与在此方法中声明一个名为card
的对象没有什么不同。唯一的区别是,实际对象是从调用此方法的人那里复制的,但是在所有其他方面,它与将在此方法中声明的名为card
的变量没有什么不同。
就像在此方法中声明的任何其他变量一样,当它返回此名为card
的对象时,它也会被销毁。它会消失的。不会了。它将不复存在。这将是事前对象。
this->head = &card;
addCard()
的此部分在card
成员中存储指向head
的指针。到目前为止,一切都很好。但是,不久之后,该card
对象将不再存在。往上看。这成为指向被破坏对象的指针。下次addCard()
被呼叫时,就会出现欢闹。
要解决此问题,您需要重新阅读C ++书籍中的章节,其中介绍了C ++中的对象如何工作,何时创建以及何时销毁。如果您的目标是练习自己实现链接列表,并且不想使用C ++库的容器,那么您将需要使用new
和delete
来创建对象动态,因此当此方法返回时,这些对象将不会消失。他们不会再没有了。它们将不复存在。它们不会是前对象。
答案 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
的析构函数。