如何创建知道它的实例在另一个类的矩阵中的方法
How to create method which will know that its instance is in matrix of another class
我是OOP(和c++)的绝对初学者。我试着利用大学为高年级学生提供的资源,以及我能找到的一堆网上的东西来自学。
我知道OOP的基本知识——我知道将东西抽象成类并使用它们来创建对象的全部要点,我知道继承是如何工作的(至少,可能是基本的),我知道如何创建操作符函数(尽管在我看来,这只是在某种意义上帮助代码变得更标准,更像语言),模板,以及诸如此类的东西。
所以我尝试了我的第一个"项目":编码扫雷(在命令行中,我以前从未创建过GUI)。我花了几个小时来创建这个程序,它按预期工作,但我觉得我错过了面向对象的一个重要点。
我有一个类"字段"有两个属性,布尔mine
和字符forShow
。我为它定义了默认构造函数,将实例初始化为空字段(mine
是false
), forShow
是.
(表示尚未打开的字段)。我有一些简单的内联函数,如isMine
, addMine
, removeMine
, setForShow
, getForShow
等。
然后我有类Minesweeper
。其属性为numberOfColumns
、~ofRows
、numberOfMines
,类型为Mine*
的指针ptrGrid
和numberOfOpenedFields
。我有一些明显的方法,如generateGrid
, printGrid
, printMines
(用于测试目的)。
它的主要内容是一个函数openFiled
,它写打开的字段周围的地雷的数量,另一个函数clickField
,如果当前正在打开的字段有0
相邻的地雷,则递归调用其周围的字段。然而,这两个函数接受一个实参——所讨论的字段的索引。如果我理解正确的话,这有点错过了OOP的要点。
例如,要调用当前字段的函数,我必须使用参数i+1
来调用它。当我注意到这一点时,我想在我的Field
类中创建一个函数,它将返回一个指向数字的指针……但是对于类Field
本身,没有矩阵,所以我不能这样做!
这是可能做到的吗,以我目前的知识来说是不是太难了?或者是否有其他更面向对象的方法来实现它?
TLDR版本:
这是一个新手用c++实现的扫雷游戏。我有一个班Minesweeper
和Field
。Minesweeper
有一个指向Field
s矩阵的指针,但是通过字段的导航(向上,向下,无论在哪里)似乎不太oop。
我想做如下的事情:
game->(ptrMatrix + i)->field.down().open(); // this
game->(ptrMatrix + i + game.numberOfColumns).open(); // instead of this
game->(ptrMatrix + i)->field.up().right().open(); // this
game->(ptrMatrix + i + 1 - game.numberOfColumns).open(); // instead of this
有几种方法可以以面向对象的方式完成此操作。@Peter Schneider提供了一种这样的方法:让每个细胞知道它的邻居。
问题的真正根源在于,当您既需要字典式查找又需要邻近查找时,您正在使用字典(将精确的坐标映射到对象)。我个人不会在这种情况下使用"普通"OOP,我会使用模板。
/* Wrapper class. Instead of passing around (x,y) pairs everywhere as two
separate arguments, make this into a single index. */
class Position {
private:
int m_x, m_y;
public:
Position(int x, int y) : m_x(x), m_y(y) {}
// Getters and setters -- what could possibly be more OOPy?
int x() const { return m_x; }
int y() const { return m_y; }
};
// Stubbed, but these are the objects that we're querying for.
class Field {
public:
// don't have to use an operator here, in fact you probably shouldn't . . .
// ... I just did it because I felt like it. No justification here, move along.
operator Position() const {
// ... however you want to get the position
// Probably want the Fields to "know" their own location.
return Position(-1,-1);
}
};
// This is another kind of query. For obvious reasons, we want to be able to query for
// fields by Position (the user clicked on some grid), but we also would like to look
// things up by relative position (is the cell to the lower left revealed/a mine?)
// This represents a Position with respect to a new origin (a Field).
class RelativePosition {
private:
Field *m_to;
int m_xd, m_yd;
public:
RelativePosition(Field *to, int xd, int yd) : m_to(to), m_xd(xd),
m_yd(yd) {}
Field *to() const { return m_to; }
int xd() const { return m_xd; }
int yd() const { return m_yd; }
};
// The ultimate storage/owner of all Fields, that will be manipulated externally by
// querying its contents.
class Minefield {
private:
Field **m_field;
public:
Minefield(int w, int h) {
m_field = new Field*[w];
for(int x = 0; x < w; x ++) {
m_field[w] = new Field[h];
}
}
~Minefield() {
// cleanup
}
Field *get(int x, int y) const {
// TODO: check bounds etc.
// NOTE: equivalent to &m_field[x][y], but cleaner IMO.
return m_field[x] + y;
}
};
// The Query class! This is where the interesting stuff happens.
class Query {
public:
// Generic function that will be instantiated in a bit.
template<typename Param>
static Field *lookup(const Minefield &field, const Param ¶m);
};
// This one's straightforwards . . .
template<>
Field *Query::lookup<Position>(const Minefield &field, const Position &pos) {
return field.get(pos.x(), pos.y());
}
// This one, on the other hand, needs some precomputation.
template<>
Field *Query::lookup<RelativePosition>(const Minefield &field,
const RelativePosition &pos) {
Position base = *pos.to();
return field.get(
base.x() + pos.xd(),
base.y() + pos.yd());
}
int main() {
Minefield field(5,5);
Field *f1 = Query::lookup(field, Position(1,1));
Field *f0 = Query::lookup(field, RelativePosition(f1, -1, -1));
return 0;
}
即使它很复杂,也有几个原因可以解释为什么要这样做。
将整个"通过位置获得"的想法与"获得邻居"的想法解耦。如前所述,它们本质上是不同的,因此公开不同的接口。
这样做可以让您有机会在以后以直接的方式扩展更多的查询类型。
您可以获得能够"存储"
Query
以供以后使用的优势。如果它是一个非常昂贵的查询,可能会在不同的线程中执行,或者在其他事件之后处理事件循环,或者…
你会得到这样的结果:(c++ 11版本,请注意!)
std::function<Field *()> f = std::bind(Query::lookup<RelativePosition>,
field, RelativePosition(f1, -1, -1));
…等等,什么?
嗯,我们本质上想做的是"延迟"Query::lookup(field, RelativePosition(f1, -1, -1))
的执行。或者,更确切地说,我们希望"设置"这样的调用,但不实际执行它。
让我们从f
开始。什么是f
?从类型签名来看,它似乎是某种函数,签名是Field *()
。一个变量怎么可能是一个函数?它实际上更像是一个函数指针。(有很好的理由不称它为函数指针,但这在这里有点超前了。)
f
可以赋值给任何,当调用时,产生一个Field *
——而不仅仅是一个函数。如果你在一个类上重载operator ()
,它也完全可以接受。
为什么我们要生成一个没有参数的Field *
?这是查询的执行,不是吗?但是函数Query::lookup<RelativePosition>
有两个参数,对吧?
这就是std::bind
的由来。std::bind
本质上是接受一个n
参数函数,并将其与m <= n
一起转换为一个m
参数函数。因此,std::bind
调用接受一个二元函数(在本例中),然后修复它的前两个参数,留给我们…
…一个零参数函数,返回Field *
。
因此,我们可以将这个"函数指针"传递给另一个线程,在那里执行,存储它以供以后使用,或者甚至只是重复调用它,如果Field
s中的Position
由于某种原因神奇地改变了(不适用于这种情况),调用f()
的结果将动态更新。
所以现在我已经把一个2D数组查找变成了一堆模板…我们必须问一个问题:这值得吗?我知道这是一个学习练习,但我的回答是:有时候,数组实际上只是一个数组。
您可以通过指针或引用将四个相邻单元链接到单元格。这可能会在竞争环境建立之后发生。我不确定这是好还是坏的设计(我看到的魅力和你看到的一样)。对于大字段,这将大大增加内存占用,因为一个单元格可能不包含那么多的数据,除了这些指针:
class Cell
{
// "real" data
Cell *left, *right, *upper, *lower;
// and diagonals? Perhaps name them N, NE, E, SE, S...
};
void init()
{
// allocate etc...
// pseudo code
foreach r: row
{
foreach c: column
{
// bounds check ok
cells[r][c].upper = &cells[r-1][c];
cells[r][c].left = &cells[r][c-1];
// etc.
}
}
// other stuff
}
- 如何将模板的实例传递给另一个模板的另一个实例?
- 如何在另一个类中创建类的实例?
- 如果模板参数是另一个模板的实例化,则键入特征测试
- 如何在C++中将类的实例完全重新分配给同一类的另一个实例(然后删除原始对象)?
- 无法从派生类型的作用域访问另一个实例的受保护成员
- 包含另一个实例的特征实例,该实例持有固定大小的特征对象
- 从模板类的另一个实例创建模板类的实例时省略模板参数
- 为什么将FMTFLAG指定两次 - 作为枚举的一部分,而另一个实例为静态const变量
- 对成员变量的引用在向同一向量中添加另一个实例后断开
- Object 创建另一个实例,而不是修改指向的实例
- 模板类实例接收不同类型的相同模板类的另一个实例
- Qt5:阻止应用程序的另一个实例不再有效...!
- 如何使模板类的一个实例与同一模板的另一个实例成为朋友
- 一个实例影响另一个实例,尽管它不应该't
- 从Python中的另一个实例获取实例
- 如何在c++中从一个实例访问另一个实例的信息
- 检查程序的另一个实例是否已经运行
- emacs:在另一个实例中自动打开相应的文件
- 在 c++ 中将一个 ascii 字符的所有实例替换为另一个实例
- 如何从另一个实例的类型动态实例化新实例?C++