我正在创建一个链表,其中包含一副牌。它只有一个头,因为我不需要尾巴。当我添加第一张卡时,一切都很好,当我添加第二张卡时它将自己设置为下一张,并基本上创建了一个无限列表。
我不知道为什么会发生这种事。我已经逐步完成了代码,每当在第二张卡上调用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有关,但帮助又被浪费了。
void Deck::addCard(Card card) {
card
是该类方法的一个参数。方法参数实际上是方法中的局部对象。这与在此方法中声明一个名为card
的对象没有什么不同。唯一的区别是,实际对象是从调用该方法的人那里复制的,但在所有其他方面,它与在该方法中声明的名为card
的变量没有什么不同。
就像在这个方法中声明的任何其他变量一样,当它返回这个名为card
的对象时,就会被销毁。它会消失的。不会再有了。它将不复存在。它将是一个ex对象。
this->head = &card;
addCard()
的这一部分在head
成员中存储指向card
的指针。到目前为止还不错。但是,不久之后,这个card
对象将不复存在。请参见上文。这将成为指向已销毁对象的指针。下一次addCard()
被呼叫时,随之而来的是欢乐。
要解决这个问题,你需要重读C++书中的一章,该章解释了C++中的对象是如何工作的,何时创建,何时销毁。如果这个excersize的目标是自己练习实现链接列表,并且您不希望利用C++库的容器,那么您将需要使用new
和delete
来动态创建对象,因此当这个方法返回时,这些对象将不会消失。他们不会再出现了。它们不会停止存在。它们不会是旧对象。
您的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);
不过,现在我们出现了内存泄漏;CCD_ 16的析构函数可能不知道节点是动态分配的并且不释放它们。
假设我们在函数终止之前放入代码来释放节点,比如:
while (!li.empty()) {
node_class *pn = li.dequeue_head();
delete pn;
}
不过,异常安全存在问题;如果new
抛出,则函数将被放弃而不执行此循环;则只调用CCD_ 18的析构函数。