1

最近从 C# 转移到 C++,所以我对指针和引用等不熟悉。

我有一个像这样声明的指针到指针数组

enum Type
{
    Void,
    DeepWater,
    Water,
    ... etc }

Tile::Type** tiles;

TileManager::TileManager(int width, int height)
{
    this->tiles = new Tile::Type*[width];

    for (int w = 0; w < width; w++)
    {
        tiles[w] = new Tile::Type[height];

        for (int h = 0; h < height; h++)
        {
            tiles[w][h] = Tile::Type::Dirt;
        }
    }
}

现在我正在组合一个方法,该方法返回瓦片数组中单元格的邻居,并检查每个邻居是否不等于 NULL。然而,即使在检查它是否为 null 时似乎也会引发错误,所以我很难过。

Tile::Type * TileManager::GetNeighbours(int x, int y)
{
    Tile::Type neighbours[8];

    if(tiles[x][y+1] != NULL)
        neighbours[0] = tiles[x    ][y + 1];

    ...etc

    if (tiles[x - 1][y - 1] != NULL)    //<-- Error fires here
        neighbours[5] = tiles[x - 1][y - 1];

    return neighbours;
}

我知道为什么它会抛出错误,但不愿检查 X 和 Y 以查看它们是否超过限制或低于 0 ......我认为有一种更实用的方法可以防止这种情况发生,所以我想我最好问问。

编辑:

谢谢你,user4581301。我在其他地方找到了大部分代码,并对其进行了调整以反映您建议的更改。

std::array<Tile::Type, 8> TileManager::GetNeighbours(int c, int r)
{
    std::array<Tile::Type, 8> neighbours;

    const int y[] = { -1, -1, -1,  1, 1, 1,  0, 0 };// 8 shifts to neighbors
    const int x[] = { -1,  0,  1, -1, 0, 1, -1, 1 };// used in functions 

    for (int i = 0; i < 8; ++i)// visit the 8 spaces around it
        if (inField(r + y[i], c + x[i]))
            neighbours[i] = tiles[r + y[i]][c + x[i]];
        else
            neighbours[i] = Tile::Type::Void;

    return neighbours;
}

bool TileManager::inField(int r, int c)
{
    if (r < 0 || r >= 25) return false;
    if (c < 0 || c >= 25) return false;
    return true;
}
4

2 回答 2

2

tiles[x][y+1], 如果y是最大有效值, 不会是NULL. 这出界了,一旦你出界,所有的赌注都会被取消。您已经调用了未定义的行为,几乎任何事情都可能发生。甚至你期望发生的事情。

这同样适用于报告的崩溃现场,tiles[x - 1][y - 1].

编辑:遗漏的解决方案。没有帮助。

唯一的方法,除了从轨道上起飞和核对整个站点之外,是测试索引以确保它在使用数组上的索引之前不会刺穿数组边界。您可能需要一个函数来处理这个问题。

void assign_if(Type & neighbour, int x, int y)
{
    if(x >= 0 && x < width && y >= 0 && y < height)
    neighbour = tiles[x][y];
}

并称之为

assign_if(neighbours[0], x, y+1);

然后

assign_if(neighbours[0], x-1, y-1);

编辑:为了完整性,从 Bob__ 那里偷了这个

从函数返回原始数组是不可能的。数组超出范围,指向它的指针变得无效。要么将数组作为另一个参数传入,要么使用std::arrayor std::vector,两者都可以返回。感谢Copy Elision,智能编译器可能会消除复制成本。

例子:

std::array<Tile::Type, 8> TileManager::GetNeighbours(int x, int y)
{
    std::array<Tile::Type, 8> neighbours;
    ...
    return neighbours;
}

由原始海报编辑。这是我的解决方案:

std::array<Tile::Type, 8> TileManager::GetNeighbours(int c, int r)
{
    std::array<Tile::Type, 8> neighbours;

    const int y[] = { -1, -1, -1,  1, 1, 1,  0, 0 };// 8 shifts to neighbors
    const int x[] = { -1,  0,  1, -1, 0, 1, -1, 1 };// used in functions 

    for (int i = 0; i < 8; ++i)// visit the 8 spaces around it
        if (inField(r + y[i], c + x[i]))
            neighbours[i] = tiles[r + y[i]][c + x[i]];
        else
            neighbours[i] = Tile::Type::Void;

    return neighbours;
}

bool TileManager::inField(int r, int c)
{
    if (r < 0 || r >= 25) return false;
    if (c < 0 || c >= 25) return false;
    return true;
}

编辑:警告

这个答案直接解决了所要求的问题。有关更实用的解决方案的描述,请参阅 Kaz 的答案,该解决方案使用一些内存来完全消除测试和生成neighbours数组的需要。

于 2019-03-18T22:33:20.143 回答
2

更“实用”的方法(避免条件检查的更短代码)是创建 tile 数组,以便它包含有效区域周围的附加“边界”。如果任何瓦片位置在有效区域内,则为有效,则为有效。

您可以为只有它们拥有的边框图块设置一种特殊类型,并将这些图块简单地包含在“邻居”列表中。如果你的世界有墙壁,那么边界可以由墙壁材料组成。

不用说,你绝不能询问边界图块的邻居列表。这是通过逻辑来确保的,例如不允许边界图块成为任何东西的有效位置。

此图块位于边界内的有效区域中”是一个更容易检查的条件,在更少的地方,并且您的程序可以被结构化,以便该检查实际上只是一个可移动的断言(检查不应该发生的情况如果程序是正确的,而不是检查预期的情况)。

在 C 和 C++ 中,我们可以移动指针,使 position[0][0]仍然是有效区域的角,但越界坐标[-1][-1]是有效的索引,就像[w][h].

首先,为列数组分配比需要大的两个元素,并且指针加一。然后为列分配两个更大的元素,每个指针在分配到主数组之前增加一。

使用 释放数组时delete [],您必须记住将每个指针减一。

于 2019-03-18T23:24:15.027 回答