【问题标题】:Changing pointer to class from one class to another将指向类的指针从一个类更改为另一个类
【发布时间】:2014-01-13 06:27:04
【问题描述】:

我对 C++ 还很陌生,并且无法将指针从一个类指向另一个类。这就是我所拥有的,它编译没有错误,但没有按我想要的方式工作。

JungleMap *Map;
class JungleMap
{
    public:
    void goNorth()
    {
        cout << "You are going north towards the river.\n";
        delete[] Map;
        RiverMap *Map;
    }
}

class RiverMap
{
    public:
    void goNorth()
    {
        cout << "You are going north away from the river.\n";
        delete[] Map;
        JungleMap *Map;
    }
}
int main()
{
    Map->goNorth();
    Map->goNorth();
}

输出如下:

You are going north towards the river.
You are going north towards the river.

这就是我想要的输出:

You are going north towards the river.
You are going north away from the river.

我如何实现这一目标?这真的让我很烦,特别是因为它编译没有问题。

【问题讨论】:

  • 您的代码无效,不应编译。
  • 您可能希望在定义JungleMap 之前将RiverMap 声明为一个类。声明为class RiverMap;
  • 如何编译没有问题,这段代码是自包含的吗?
  • 仅供参考:ideone.com/GnNFnk
  • 抛开编译错误,您缺少的是地图的基类。阅读更多关于 C++ 继承的内容。

标签: c++ class pointers delete-operator


【解决方案1】:

仅创建 JungleMap* 不会创建 JungleMap。你形成了一个指针,但没有指向任何地方!

这是特别危险的,因为您随后取消引用它,然后尝试通过它删除。是的,这可以编译,因为编译器在一般情况下无法诊断(并且永远不需要尝试),但是您将在运行时得到一切,从无声的虚无到崩溃,再到核爆炸。

您还试图通过更改指针的类型(没有任何继承)在两个不同的类中调用不同的函数,这根本不可能,将 阻止你的代码编译,即使你试图通过在本地重新声明变量来绕过它。我可以列出一大堆误解,但足以说明是时候阅读a good introductory C++ book了。

如果我知道您想要实现什么,我会建议将继承和动态分配相结合。 SO 上的一个常见错误是提供无意义的代码,然后期望我们从这些无意义的代码中知道您的目标是什么;不幸的是,我们对您真正想要做什么的想法和 C++ 编译器一样多!

【讨论】:

  • 核爆炸?从来没有这个...我上次尝试朝鲜未能发射导弹。巧合?我不知道
  • @Geoffroy:你永远不知道!
【解决方案2】:

您可以通过创建一个继承自 JungleMap 和 RiverMap 的基类来完成这项工作(至少在最小程度上)。然后,您将拥有一个指向基类的指针,该指针指向派生类之一的实例。您还需要重新排列代码以使其能够编译。

class Map {
public:
    virtual void goNorth() { cout<<"Sorry, you can't go that way"; }
    virtual void goSouth() { cout<<"Sorry, you can't go that way"; }
};

Map *map; 

class RiverMap;

class JungleMap : public Map {
public:
    void goNorth();
};

class RiverMap : public Map {
public:
    void goSouth();
};

void JungleMap::goNorth() {
    cout<<"You are going north towards the river.\n";
    delete map;
    map=new RiverMap;
}

void RiverMap::goSouth() {
    cout<<"You are going south towards the jungle.\n";
    delete map;
    map=new JungleMap;
}

注意:在这里我只是想说尽可能接近您的原始设计,并且仍然有一些代码可能至少某种可以工作。我当然不会把它作为一个典型的设计,甚至接近它(因为,坦率地说,它不是)。

【讨论】:

  • 当我这样做时(我将函数放在派生类内部而不是外部,我可以这样做吗?)执行 CurrentMap = new JungleMap 时出现错误。错误是:错误:“JungleMap *”类型的值不能分配给“Map *”类型的实体
  • 另外,当我编译它时,它说 JungleMap 和 RiverMap 没有合适的默认构造函数,但添加默认构造函数并不能消除错误。
【解决方案3】:

您应该做的是坐下来思考您要解决的问题,并做出适当的设计。在您的情况下,您有两个“位置”,“玩家”应该能够在这些位置之间移动。从那开始,我们已经确定了两种可能的类别(LocationPlayer)和一种行为(玩家可以从一个位置移动到另一个位置)。

有了以上信息,你可以这样做:

class Location
{
public:
    void setNorth(Location* loc)
    {
        north_ = loc;
    }

    Location* getNorth() const
    {
        return north_;
    }

    void setSouth(Location* loc)
    {
        south_ = loc;
    }

    Location* getSouth() const
    {
        return south_;
    }

    void setDescription(const std::string& descr)
    {
        description_ = descr;
    }

    const std::string& getDescription() const
    {
        return description_;
    }

protected:
    Location() {}  // Made protected to prevent direct creation of Location instances

private:
    Location* north_;
    Location* south_;
    std::string description_;
};

class Jungle : public Location
{
public:
    Jungle() : Location()
    {
        setDescription("You are in a jungle.");
    }
};

class River : public Location
{
public:
    River() : Location()
    {
        setDescription("You are close to a river.");
    }
};

// The actual "map"
std::vector<Location*> map

void createMap()
{
    map.push_back(new Jungle);
    map.push_back(new River);

    map[0]->setNorth(map[1]);
    map[1]->setSouth(map[0]);
}

class Player
{
public:
    Player(Location* initialLocation)
        : currentLocation_(initialLocation)
    {
        std::cout << currentLocation_->getDescription() << '\n';
    }

    ...
    // Other methods and members needed for a "player"

    void goNorth()
    {
        if (currentLocation_ && currentLocation_->getNorth())
        {
            currentLocation_ = currentLocation_->getNorth();
            std::cout << currentLocation_->getDescription() << '\n';
        }
    }

    void goSouth()
    {
        if (currentLocation_ && currentLocation_->getSouth())
        {
            currentLocation_ = currentLocation_->getSouth();
            std::cout << currentLocation_->getDescription() << '\n';
        }
    }

private:
    Location* currentLocation_;  // The players current location
};

int main()
{
    createMap();  // Create the "map"

    Player player(map[0]);  // Create a player and place "him" in the jungle

    // Move the player around a little
    player.goNorth();
    player.goSouth();
}

在上面的代码中,你有一个玩家对象,它有一个“当前位置”。当您移动播放器时,您只需更改该播放器的当前位置。播放器的当前位置充当您拥有的全局 Map 变量。

注意:我并不是说这是一个好的设计或代码,只是说它很简单。


但是,如果您真的是 C++ 新手,您可能应该从一些更简单的问题开始,包括关于指针和继承的教程。

【讨论】:

    【解决方案4】:

    您似乎将声明与赋值混淆了。

    下面这行代码叫做声明,它告诉编译器一个事物的属性和属性。

    JungleMap *Map;
    

    在这行代码之后,编译器知道“地图”是一个符号(名称),指的是指向丛林地图的指针。

    编译器不必对声明做任何事情,除非它会产生副作用,此时它变成了一个定义,这意味着该声明调用了一个非平凡的构造函数或提供了一个赋值:

    struct Foo {};
    struct Baz { Baz() { std::cout << "Baz is here\n"; } };
    

    这些是声明——它们不创建对象的实例,它们描述实例的布局和功能。在某些时候,您必须使用定义或对new 的调用来创建它们的具体实例。

    struct Foo {};
    struct Bar { Bar() { std::cout << "Bar is here\n"; } };
    struct Baz {};
    
    int main() {
        int i; // no side effects, i is trivial.
        char* p; // no side effects, p is a pointer (trivial) type
        std::string* sp; // trivial, pointer
    
        Foo f; // trivial
        Bar b; // non-trivial, baz has a user-defined ctor that has side-effects.
        Bar* bar; // trivial, unassigned pointer type.
        Bar* bar2 = new Bar(); // side effects.
        Bar bar(); // syntax error, "the most vexing parse"
    }
    

    在上面的代码中,我们从不使用“Baz”,也从不声明 Baz 类型的对象,因此编译器基本上将其丢弃。因为这么多变量都是微不足道的,没有副作用,上面的编译结果在功能上和我们写的一样:

    struct Foo {};
    struct Bar { Bar() { std::cout << "Bar is here\n"; } };
    
    int main() {
        Bar* bar2 = new Bar(); // side effects.
        Bar bar(); // syntax error, "the most vexing parse"
    }
    

    其余的都什么都不做。

    C++ 还允许您重复使用名称,只要它们位于不同的范围内,但这会创建一个新的、隐藏的(“影子”)事物:

    #include <iostream>
    int main() {
        int i = 1;
        if (i == 1) {
            float i = 3.141;
            std::cout << "inner i = " << i << '\n';
        }
        std::cout << "outer i = " << i << '\n';
        return 0;
    }
    

    因此,您编写的代码将被编译,因为它在每个 go 函数中声明了一个新的私有“映射”,然后根本不使用它们。

    请注意,上面我能够在内部范围内声明i,而不是在外部范围内。

    C++ 不允许您更改变量的类型 - 在上面的代码中有两个 变量称为i。当我们创建第二个 i 时,它是一个名为 i 的第二个变量,原来的变量没有改变

    为了做你想做的事,你需要学习“多态性”和“继承”,C++ 概念可以让你描述一个“房间”或“位置”,然后根据JungleMap 和 RiverMap 基于该基本定义,这样您就可以将指针指向核心概念 Room,并编写处理房间的通用代码,同时将 Jungle、River 或 BridgeMap 的细节移动到专门的函数中。但我认为这超出了这里的回复范围。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-09-08
      • 2015-06-19
      • 2012-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-13
      相关资源
      最近更新 更多