- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我绝对是 OOP(和 C++)的初学者。尝试使用我的大学为高年级学生提供的资源以及我可以找到的一堆互联网资料来自学。
我知道关于 OOP 的基本知识 - 我知道将东西抽象到类中并使用它们创建对象的全部要点,我知道继承是如何工作的(至少,可能是基础知识),我知道如何创建运算符函数(尽管据我所知,这只会在代码变得更标准、更像语言)、模板和类似东西的意义上帮助提高代码可读性。
所以我尝试了我的第一个“项目”:编写扫雷程序代码(在命令行中,我以前从未创建过 GUI)。我花了几个小时来创建该程序,它按预期运行,但我觉得我在其中遗漏了一个重要的 OOP 点。
我有一个类“Field”,它有两个属性,一个 bool 值 mine
和一个字符 forShow
.我已经为它定义了默认构造函数来将实例初始化为空字段( mine
是 false
),并且 forShow
是.
(表示尚未打开的文件)。我有一些简单的内联函数,例如 isMine
, addMine
, removeMine
, setForShow
, getForShow
等
然后我得到了类 Minesweeper
.它的属性是numberOfColumns
, ~ofRows
, numberOfMines
, 一个指针 ptrGrid
类型 Mine*
, 和 numberOfOpenedFields
.我有一些明显的方法,例如 generateGrid
, printGrid
, printMines
(用于测试目的)。
它的主要功能是一个函数 openFiled
写入打开区域周围的地雷数量,另一个函数 clickField
如果当前打开的字段有 0
,它会递归地调用自身来获取周围的字段。邻居矿山。 但是,这两个函数有一个参数——相关字段的索引。如果我理解正确的话,这有点忽略了 OOP 的要点。
例如,要调用当前字段右侧的函数,我必须使用参数 i+1
来调用它.当我注意到这一点时,我想在我的 Field
中创建一个函数。类将返回一个指向它的数字的指针......但是对于类Field
本身,没有矩阵,所以我做不到!
有没有可能做到,对我目前的知识来说太难了吗?还是有另一种更面向对象的方式来实现它?
TLDR 版本:
这是一个菜鸟使用 C++ 实现的扫雷游戏。我上课了Minesweeper
和 Field
. Minesweeper
有一个指向 Field
矩阵的指针s,但是通过字段的导航(向上、向下,无论在哪里)似乎都不是 OOP-ishly。
我想做如下的事情:
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;
}
有几个原因可能会导致您想要这样做,即使它很复杂。
将整个“按位置获取”的想法与“获取邻居”的想法分离。如前所述,它们根本不同,因此公开不同的接口(interface)。
以这种方式进行操作让您以后有机会以直接的方式扩展更多查询类型。
您可以获得能够“存储”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
-argument 函数并将其转换为 m
-参数函数,带m <= n
.所以 std::bind
call 接受一个双位函数(在本例中),然后修复它的前两个参数,留给我们 . . .
. . .一个零参数函数,返回 Field *
.
因此我们可以将这个“函数指针”传递给一个不同的线程在那里执行,存储它以供以后使用,或者甚至只是为了好玩而重复调用它,如果 Position
的 Field
s 是由于某种原因神奇地改变了(在这种情况下不适用),调用 f()
的结果会动态更新。
现在我已经将 2D 数组查找变成了一堆模板。 . .我们不得不问一个问题:值得吗?我知道这是一个学习练习,但我的回答是:有时,数组真的只是一个数组。
关于c++ - 如何创建知道其实例在另一个类的矩阵中的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24683299/
随着互联网在中国将近20年的发展,内容领域也从原来傻大黑粗的拼流量,进入了垂直领域的精根细作时代。我相信很多做过互联网运营的小伙伴,一定接触过内容运营,或者专职做内容运营。但是,很多自以为做了很久内
我是一名优秀的程序员,十分优秀!