【问题标题】:C++ Blackjack FunctionC++二十一点函数
【发布时间】:2012-06-23 06:50:42
【问题描述】:

我已经开始自学 C++ 并且一直在尝试编写二十一点程序。我正在尝试使用类来表示卡片、甲板和手。我相信到目前为止,除了dealCardToHand() 方法外,程序中的一切都在工作。

void dealCardToHand(deck& d, hand& h){
    h.setCard(h.getCardsInHand(), d.dealCard());
    h.setCardsInHand(h.getCardsInHand() + 1);
}

它似乎正确地增加了手牌的数量,但没有使用正确的数据调用 setCard() 方法。任何帮助,将不胜感激。我包括相关的类和方法:

class deck{
    int topCard;
    card * cards[52];
 public:
     deck();
     void shuffle();
     void printDeck();
     card dealCard();
};

card deck::dealCard(){//returns top card of deck and increments top card one
    return *cards[topCard++];
}

class hand{
    card * handCards[12];
    int cardsInHand;
public:
    hand();
    card getCard(int i){ return *handCards[i]; }
    void setCard(int i, card c) { handCards[i] = &c; }
    int getCardsInHand() { return cardsInHand; }
    void setCardsInHand(int i) { cardsInHand = i; }
    void printHand();
};

【问题讨论】:

  • STL 容器会让您的生活更轻松。还有一个std::random_shuffle 算法,如果它能让你的船浮起来的话。

标签: c++ blackjack


【解决方案1】:

这很危险(可能至少是您问题的一部分):

void setCard(int i, card c) {handCards[i]=&c;}

在这里,setCard(...) 按值传递了一个 card 对象。这意味着在临时位置创建调用者card 的新副本。 setCard() 作用的正是这个副本 (c)。通过设置handCards[i]=&c;,您可以保存这个临时对象的位置。但是当setCard() 返回时,该临时对象不再有效。

然后你继续在getCard() 中取消引用handCards[i]。这会产生未定义的行为。理论上,你应该期待恶魔开始从你的鼻子里飞出来。在实践中,您将看到从getCard() 返回的总垃圾。或者崩溃。或者,如果你够倒霉,最后一个值可能会传入setCard()

总体而言,看起来您在使用指针时玩得又快又松。我建议使用以下两种方法之一来解决问题:

  1. 在任何地方都使用指针,永远不要按值传递或返回。这可能会导致其他问题,但它们可能并不那么神秘。
  2. 在任何地方都不能使用指针。按值传递和返回所有内容。

当然,这些不是唯一的选择,但它们可能暂时让您的生活更轻松。

【讨论】:

  • @chris:基本同意。但是,如果这个想法是学习 C++……好吧,据说“烧伤的手教得最好”。 ——托尔金(?)
  • 当然,一本好书会有更好的例子来练习指针,而不是在根本不需要指针的项目中毫无意义地使用它们。
  • @Managu 你是在说故意给出不好的建议,这样提问者会很难学会吗?与放弃指针相比,这更有可能导致放弃 C++。放弃指针也很糟糕,尽管它们从来都不是第一个选择,但它们仍然有用。
  • @Potatoswatter:不是这样。从建模的角度来看,为一副牌中的每张牌设置一个 card 对象并在发牌时更改牌的所有者是有意义的。也许比打印一张与牌库顶牌相同的新牌,将副本交给玩家,然后撕碎原牌更有意义。使用指针来建模这种场景并不荒谬,特别是如果 c++11 不在桌面上。事实上,在不同的情况下,人们可能会在实践中这样做。
  • @Managu 在这个程序中没有明显的理由使用指针,因此最好的做法是避免使用它们。 “不荒谬”不是一个好的生活标准。
【解决方案2】:

正如其他人所指出的,问题在于您将临时变量的地址存储在指针变量中,并在临时变量超出范围(并已被删除)后取消引用该指针变量。

让我的回答更深入一点:

在 C++ 中,我们区分了值类和多态类。你会发现它们有不同的名称,你还会发现两者之间的区别并不像人们想象的那么明显,但大致是这样的:

  1. 值类实例的状态彼此不同。如果两个实例的状态相等,则实例的行为也将相等。

    值类的示例有std::string、所有 STL 容器、std::complex<> 等。

    您可以像使用 ints 一样使用它们:在堆栈上创建它们:

    std::string s = "Hello, World"; // NOT std::string * s = new std::string;
    

    按值聚合它们:

    class Widget {
        std::complex<double> m_value; // NOT std::complex<double> * m_value;
    public:
        // ...
    };
    

    您通常可以比较它们是否相等,复制它们,将它们存储在 容器:

    std::vector<std::string> vec;
    std::string s = "Hello, World";
    assert( s == "Hello, World" );
    vec.push_back( s );
    

    并且,与您的问题最相关的是,您通过 (const-)reference(或按值,特别是如果它们非常小)传递它们,并且您也按值返回它们:

    void func(const std::vector<double> & vec); // ok, just reading 'vec'
    void func(std::vector<double> & vec); // ok, possibly writing to 'vec'
    void func(std::vector<double> vec); // not so good, expensive in C++03; ok in C++11 in some situations
    std::vector<double> func(); // ok, return value optimisation (look it up!) will make this fast
    
  2. 多态类的行为不同,而不是状态。多态类的两个实例可能具有相同的状态,但行为仍然完全不同。多态类可能的状态也不同,但重点在于它们的行为。这就是 OOP(面向对象编程)的意义所在。

    从著名的 C++ 库“Qt”中借用示例类,QLineEditQPushButton 都是 QWidgets。它们可能具有相同的状态(大小、位置……),但是当您用鼠标单击每个状态时,它们之间会发生完全不同的情况。

    在 C++ 中,要使用多态行为,您必须调用 virtual 函数,并且必须通过指针或对公共基类的引用(上面的QWidget)来调用。因此,多态类通常分配在堆上:

    QLineEdit * le = new QLineEdit();
    QPushButton * pb = new QPushButton();
    
    QWidget * leAsWidget = le; // works
    QWidget * pbAsWidget = pb; // works
    

    并在(智能)指针变量中存储和传递:

    class MyWidget : public QWidget {
        QLineEdit * m_lineEdit;
        QPointer<QPushButton> m_pushButton; // QPointer is a smart pointer
    public:
        // ...
    };
    

对于您的程序,您必须决定是否需要 deckhand 是多态的。

如果是,在堆上创建它们(使用new),通过指针存储和传递它们,当你完成它们时不要忘记再次删除它们(或者看看智能指针)。

如果没有,给他们关系运算符(bool operator==(const deck &amp;lhs, const deck &amp;rhs),...),复制语义(deck(const deck&amp;)deck &amp;operator=(const deck&amp;)),在堆栈上创建它们并按值存储它们。通过 (const) 引用传递它们。您不需要删除它们,编译器会为您完成。

【讨论】:

    猜你喜欢
    • 2023-03-21
    • 1970-01-01
    • 2016-12-29
    • 1970-01-01
    • 2016-06-21
    • 2017-02-27
    • 2012-01-03
    • 2011-11-09
    • 2016-11-09
    相关资源
    最近更新 更多