由于这种新方法不同于我以前的修订,因此不需要引用它们。
在接受了CR chat的指导之后,我最终利用了以下几点:
enums用于Rank和Suit而不是数组namespace的enums和Card类to_string()s代表军衔和服装在operator<<中扮演std::stringsoperator<<作为friendstd::vectors为Deck级(用于在重置时保留原甲板)我想知道是否还有更多的事情可以做,我有一些问题:
enums做些其他的事情?enums是内置的,但const&仍然适合Card的S构造器吗?empty()是否足够好,以避免类内异常处理的复杂性?to_string()s的排名和西服能否缩短,同时仍像一张查表?任何其他反馈也是非常感谢的。除了这些方面之外,我希望能够适应不同的游戏(例如添加一个小丑)的整体可维护性。因此,我不想损害基本的设计,只与几个游戏相关的功能。
#ifndef CARD_H
#define CARD_H
#include <iostream>
#include <string>
namespace card
{
enum Rank {Ace,Two,Three,Four,Five,Six,Seven,Eight,Nine,Ten,Jack,Queen,King};
enum Suit {Clubs,Diamonds,Hearts,Spades};
class Card
{
private:
Rank m_rank;
Suit m_suit;
std::string to_string(Rank const&) const;
std::string to_string(Suit const&) const;
public:
Card(Rank const&, Suit const&);
std::string to_string() const;
Rank rank() const {return m_rank;}
Suit suit() const {return m_suit;}
};
}
card::Rank& operator++(card::Rank&);
card::Suit& operator++(card::Suit&);
std::ostream& operator<<(std::ostream&, card::Card const&);
#endif#include "Card.h"
#include <stdexcept>
card::Card::Card(Rank const& rank, Suit const& suit)
: m_rank(rank), m_suit(suit) {}
std::string card::Card::to_string(Rank const& rank) const
{
switch (rank)
{
case Ace: return "A";
case Two: return "2";
case Three: return "3";
case Four: return "4";
case Five: return "5";
case Six: return "6";
case Seven: return "7";
case Eight: return "8";
case Nine: return "9";
case Ten: return "T";
case Jack: return "J";
case Queen: return "Q";
case King: return "K";
default: throw std::logic_error("Invalid Rank");
}
}
std::string card::Card::to_string(Suit const& suit) const
{
switch (suit)
{
case Clubs: return "C";
case Diamonds: return "D";
case Hearts: return "H";
case Spades: return "S";
default: throw std::logic_error("Invalid Suit");
}
}
std::string card::Card::to_string() const
{
return ("[" + to_string(m_rank) + to_string(m_suit) + "]");
}
card::Rank& operator++(card::Rank& r)
{
return r = card::Rank(static_cast<int>(r)+1);
}
card::Suit& operator++(card::Suit& s)
{
return s = card::Suit(static_cast<int>(s)+1);
}
std::ostream& operator<<(std::ostream& out, card::Card const& obj)
{
return out << obj.to_string();
}#ifndef DECK_H
#define DECK_H
#include <vector>
class Deck
{
private:
std::vector<card::Card> m_original; // retains original cards when resetting deck
std::vector<card::Card> m_playable; // cards are drawn from this one
public:
typedef std::vector<card::Card>::size_type SizeType;
void reset();
void shuffle();
void add(card::Card const&);
card::Card draw();
SizeType size() const {return m_playable.size();}
bool empty() const {return m_playable.empty();}
};
#endif#include "Card.h"
#include "Deck.h"
#include <algorithm>
void Deck::reset()
{
// is this already exception-safe by itself?
m_playable = m_original;
}
void Deck::shuffle()
{
// my tests tell me that an empty vector will not cause this to fail
std::random_shuffle(m_playable.begin(), m_playable.end());
}
void Deck::add(card::Card const& card)
{
// new cards always added to both vectors
m_original.push_back(card);
m_playable.push_back(card);
}
card::Card Deck::draw()
{
// still not exception-safe, unless not needed if handled in client
card::Card topCard(m_playable.back());
m_playable.pop_back();
return topCard;
}发布于 2013-08-03 21:59:08
to_strings for Rank和Suit不属于Card类。将它们从类中移出并移至card命名空间。它们也可以是客户端代码的有用函数。m_前缀。通常,您只需使用普通变量名即可。如果您必须有一个成员变量的命名方案,我认为foo_更易读。这样,我的眼睛就不必放弃m_,然后才能找到真名。enum定义放在多行上。cases )缩进一个switch里,只有单行的情况.可读性胜过一致性:开关(套装){ case俱乐部:返回"C";case钻石:返回"D";大小写心:返回"H";大小写黑桃:返回"S";默认情况:抛出std::logic_error(“无效西装”);}Desk::reset:std::vector::operator=的异常安全性,如果赋值操作符抛出,则基本例外安全保障:对象将处于有效状态。由于m_playable已经具备了所需的容量,所以我想不出它应该抛出什么原因。Deck::draw()是异常安全的:std::list::pop_back永远不会抛出。见下文。Card.h头应该包括<ostream>而不是<iostream>。因为您只需要std::ostream声明,所以只需在Card.cpp中包含<iosfwd>和<ostream>。Card没有出现在card名称空间中?使用两个向量可以有效地保存牌面(而不是让消费代码负责将卡片退回到牌面)吗?
我想是的。要求客户代码返回卡太苛刻了。
如果用户不知道西服的顺序是哪一种(当用卡片填满甲板时),是否应该对
enums做些其他的事情?
客户端代码应该能够很好地处理这个问题。如果它不想使用增量操作符,它只需在填充面板时定义四个循环,甚至可以实现自己的函数来迭代这些循环。
虽然
enums是内置的,但const&仍然适合Card的S构造器吗?
通过enums的价值。
用户调用
empty()是否足够好,以避免类内异常处理的复杂性?
是。实际上,Deck::draw()有很强的例外安全保证:如果出了问题,它将被回滚回原来的状态。在本例中,您可以免费获得此服务:std::list::pop_back()没有抛出保证。这意味着draw()中唯一可能失败的操作是调用m_playable.back(),而这不会修改容器。只要调用成功,整个函数也会成功。
两名
to_string()s的排名和西服能否缩短,同时仍像一张查表?
我认为你这样做很好。您可以使用std::map,但与您的方法相比,该方法没有优势。如果你想这样做,你可以这样做:
std::string to_string(Rank rank)
{
static const map<Rank, string> rank_names = { {Ace, "A"}, {Two, "2"}, {Three, "3"} /* ... */ };
return rank_names[rank];
}请注意,这有O(n log )查找时间,而您的实现可能是O(1)。还可以定义数组并将enum值用作索引。这也是O(1),但仍然没有意义--这种方法违背了抽象。
最后,我有一个问题要问你:你将如何实现一个小丑?
https://codereview.stackexchange.com/questions/29348
复制相似问题