如何创建知道它的实例在另一个类的矩阵中的方法

How to create method which will know that its instance is in matrix of another class

本文关键字:另一个 实例 方法 何创建 创建      更新时间:2023-10-16

我是OOP(和c++)的绝对初学者。我试着利用大学为高年级学生提供的资源,以及我能找到的一堆网上的东西来自学。

我知道OOP的基本知识——我知道将东西抽象成类并使用它们来创建对象的全部要点,我知道继承是如何工作的(至少,可能是基本的),我知道如何创建操作符函数(尽管在我看来,这只是在某种意义上帮助代码变得更标准,更像语言),模板,以及诸如此类的东西。

所以我尝试了我的第一个"项目":编码扫雷(在命令行中,我以前从未创建过GUI)。我花了几个小时来创建这个程序,它按预期工作,但我觉得我错过了面向对象的一个重要点。

我有一个类"字段"有两个属性,布尔mine和字符forShow。我为它定义了默认构造函数,将实例初始化为空字段(minefalse), forShow.(表示尚未打开的字段)。我有一些简单的内联函数,如isMine, addMine, removeMine, setForShow, getForShow等。

然后我有类Minesweeper。其属性为numberOfColumns~ofRowsnumberOfMines,类型为Mine*的指针ptrGridnumberOfOpenedFields。我有一些明显的方法,如generateGrid, printGrid, printMines(用于测试目的)。

它的主要内容是一个函数openFiled,它写打开的字段周围的地雷的数量,另一个函数clickField,如果当前正在打开的字段有0相邻的地雷,则递归调用其周围的字段。然而,这两个函数接受一个实参——所讨论的字段的索引。如果我理解正确的话,这有点错过了OOP的要点。

例如,要调用当前字段的函数,我必须使用参数i+1来调用它。当我注意到这一点时,我想在我的Field类中创建一个函数,它将返回一个指向数字的指针……但是对于类Field本身,没有矩阵,所以我不能这样做!

这是可能做到的吗,以我目前的知识来说是不是太难了?或者是否有其他更面向对象的方法来实现它?

TLDR版本:

这是一个新手用c++实现的扫雷游戏。我有一个班MinesweeperFieldMinesweeper有一个指向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 &param);
};
// 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;
}

即使它很复杂,也有几个原因可以解释为什么要这样做。

  1. 将整个"通过位置获得"的想法与"获得邻居"的想法解耦。如前所述,它们本质上是不同的,因此公开不同的接口。

  2. 这样做可以让您有机会在以后以直接的方式扩展更多的查询类型。

  3. 您可以获得能够"存储"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
}