用于多级数据组织的Struct与数组

时间:2014-08-01 15:30:00

标签: c++ arrays data-structures

我有一些我需要组织的数据。我应该注意到我是C和C ++的新手。

我有一个建筑物清单,每个建筑物都有一定数量和与之相关的资源费率数组。我需要遍历建筑物列表,将每个建筑物的数量乘以其资源差异,并将它们相加以得出总资源差异。

例如:

冰井:[ - 100次方,+ 50水],n = 2

太阳能电池阵列:[+150电力],n = 2

总计: [+ 100电力,+ 100水]

我对如何整理数据感到困惑。如果我使用结构,循环将是一个挑战,但一旦建筑类型的数量增加,阵列将会混淆。在这一点上,我最好的猜测是将枚举与数组耦合,以获得具有数组循环功能的结构的命名功能。我应该怎么做呢?

3 个答案:

答案 0 :(得分:8)

您可能需要考虑这样的方法:(实例:http://ideone.com/xvYFXp

#include <iostream>
#include <vector>
#include <memory>

class Building {
  public:
    virtual int getPower() = 0;
    virtual int getWater() = 0;
    virtual ~Building() {}
};

class IceWell : public Building {
  public:
    virtual int getPower() {return -100;}
    virtual int getWater() {return 50;}
};

class SolarArray : public Building {
  public:
    virtual int getPower() { return 150; }
    virtual int getWater() { return 0; }
};

int main()
{
    std::vector<std::shared_ptr<Building>> inventory;
    inventory.emplace_back(new IceWell);
    inventory.emplace_back(new SolarArray);
    inventory.emplace_back(new IceWell);
    inventory.emplace_back(new SolarArray);

    int total_power = 0;
    int total_water = 0;
    for (auto building : inventory)
    {
        total_power += building->getPower();
        total_water += building->getWater();
    }

    std::cout << "Total power: " << total_power << "\n";
    std::cout << "Total water: " << total_water << "\n";

    return 0;
}

基类Building定义接口;基本上它列出了您的建筑物可能产生或消耗的资源。派生类IceWellSolarArray然后实现该接口,定义一个这样的建筑物将来源或接收的资源量。最后,你有一个包含一些派生对象实例的向量(保存在共享指针中,为你处理内存管理!)。您可以遍历这些并累积每个资源的总值。

如果生产/消费值可能随时间而变化,您可以将这些值保存为每个派生类的(私有)成员变量,并允许某些机制修改它们。您甚至可以getWater()计算其值作为一定时间的函数,因此井可能会干涸&#34;随着时间的推移。


在发布我的原始答案(上图)之后,我发现自己正在考虑替代方案。我写了以下内容,试图解释一些设计选项,它们的优点和缺点,以及如何对这样的系统设计进行推理。我希望它有所帮助。


在C ++中很少能找到一个正确的解决方案&#34;。通常有几种合理的方法,每种方法都有自己的一套相对优点和缺点。

当您使用C ++设计程序时,您将不可避免地要考虑一些可能的解决方案,并根据其优点选择一个。让我们尝试分析这里所说的问题......

您有一些建筑物,这些建筑物具有定义其资源使用情况的属性。一些建筑物是净生产者,而其他建筑物是特定资源的净消费者。这立即识别出资源&#34;作为系统内的实体。我们可能直接进入并编写一些封装了我们净资源使用概念的东西:

struct ResourceFootprint {
    int power;
    int water;
};

现在,您可以通过适当地实例化该结构来表示不同级别的资源消耗:

(实例:http://ideone.com/ubJv8m

int main()
{
    ResourceFootprint ice_well = {-100, +50};
    ResourceFootprint solar_array = {+150, 0};

    std::vector<ResourceFootprint> buildings;
    buildings.push_back(ice_well);
    buildings.push_back(ice_well);
    buildings.push_back(solar_array);
    buildings.push_back(solar_array);

    ResourceFootprint total = {0, 0};
    for (const ResourceFootprint& r : buildings)
    {
        total.power += r.power;
        total.water += r.water;
    }

    std::cout << "P: " << total.power << ", W: " << total.water << "\n";

    return 0;
}

这看起来不错;我们已经打包了资源,他们有方便的名字,我们可以通过提供相关的价值来创建新的建筑类型。这意味着我们可以拥有存储在文件中的建筑物清单,其中可能包含以下内容:

IceWell,-100,50
SolarArray,150,0
HydroElectricPlant,150,150

我们所要做的就是读取文件并创建ResourceFootprint的实例。这似乎是一个合理的解决方案;毕竟,不同种类的资源可能是合理固定的,而生产和消费它们的不同种类的建筑可能经常发生变化。您可能想要添加医院,加油站,餐厅和农场。

但等等;医院也消耗医药,并生产医疗废物。加油站肯定需要石油,餐馆将消耗食物,水和电力,农场可能生产食物,但他们将需要石油,电力和水这样做。

现在我们处于不同类型的资源必须改变的情况。目前的机制仍然可行;我们可以将资源添加到我们的结构中:

struct ResourceFootprint {
    int power;
    int water;
    int oil;
    int food;
    int medicine;
    int medical_waste;
};

在不必关心之前没有使用新资源的事情(虽然定义可能需要将额外的资源字段清零),并且可以在以下内容中定义使用新资源的事物。他们的条款:

    ResourceFootprint ice_well = {-100, +50, 0, 0, 0, 0};
    ResourceFootprint solar_array = {+150, 0, 0, 0, 0, 0};
    ResourceFootprint hospital = {-100, -50, 0, -50, -50, 50};
    ResourceFootprint gas_station = {-10, 0, -100, 0, 0, 0};
    ResourceFootprint restaurant = {-20, -20, 0, -100, 0, 0};
    ResourceFootprint farm = {-10, -30, -10, 200, 0, 0};

更好的是,计算总功率和用水量的现有代码仍然可以正常工作。我们可以只为其他资源添加代码:

(实例:http://ideone.com/rukqaz

    ResourceFootprint total = {0, 0, 0, 0, 0, 0};
    for (const ResourceFootprint& r : buildings)
    {
        total.power += r.power;
        total.water += r.water;
        total.oil += r.oil;
        total.food += r.food;
        total.medicine += r.medicine;
        total.medical_waste += r.medical_waste;
    }

到目前为止,这么好。但这种方法的缺点是什么?

嗯,一个明显的问题是资源占用空间只是普通数据。当然,我们可以改变这些值,但我们无法做任何复杂的事情,比如随着时间的推移让IceWell干涸,或者允许SolarArray根据是白天还是晚上产生不同的功率。为此,我们需要以某种方式计算资源足迹,这种方式可能因建筑类型而异。

答案的原始部分探讨了一种解决方案,其中每个建筑物都有自己的类型,其成员函数返回相关资源的当前消耗。正如我们刚刚探讨的那样,我们可能需要扩展我们的资源集。我们可以通过结合这两个想法来做到这一点;每个建筑都有一个类,以及一个用于保存资源使用的结构。然后,每个构建类可以决定如何实现其资源使用。

基类看起来像这样:

class Building {
    public:
        virtual ~Building() {}

        virtual ResourceFootprint currentResourceLevel() = 0;
};

我们选择按值返回ResourceFootprint(而不是返回引用或任何其他方法),因为这允许我们轻松地更改实现。讨论如下......

在最简单的情况下,水力发电厂可能只使用恒定的水供应,并产生恒定的电力供应。在这里,它会将ResourceFootprint对象保存为(可能是const)成员变量,并在询问其资源消耗时返回它的副本:

class HydroElectricPlant : public Building {
    public:
        HydroElectricPlant(const ResourceFootprint& r)
         : resources(r) {}

        virtual ResourceFootprint currentResourceLevel() { return resources; }

    private:
        const ResourceFootprint resources;
};

IceWell可能会做一些更复杂的事情:

class IceWell : public Building {
    public:
        IceWell(const ResourceFootprint& initial)
         : resources(initial) {}

        virtual ResourceFootprint currentResourceLevel() { return resources; }

        void useWater(int amount) { resources.water -= amount; }

    private:
        ResourceFootprint resources;

};

SolarArray可能会:

class SolarArray : public Building {
    public:
        SolarArray(const ResourceFootprint& r)
         : day_resources(r), night_resources(r)
        {
            night_resources.power = 0;
        }

        virtual ResourceFootprint currentResourceLevel()
        {
            if (is_day())
            {
                return day_resources;
            }
            else
            {
                return night_resources;
            }
        }

    private:
        ResourceFootprint day_resources;
        ResourceFootprint night_resources;

};

设计现在允许:

  • 添加新类型的资源:当新字段添加到结构时,上面的代码将继续工作(尽管您可能需要对结构的初始化进行一些更改以考虑这些新字段)。
  • 添加新类型的构建:您可以编写另一个类,继承自Building,并根据需要定义其资源使用情况。

这不包括什么?

  • 动态创建建筑类型。第一个解决方案,其中一个建筑物纯粹根据ResourceFootprint字段的值定义,允许我们创建新类型的建筑,只需通过makiung创建具有适当值的ResourceFootprint的新实例。这非常适合于允许您制作自定义建筑的系统,但在这些值随时间变化的方式上并没有很大的灵活性。
  • 处理新资源类型而无需添加更多代码。每个新资源类型在结构中都有一个字段,因此您需要按名称引用它。您可以只保持一个std :: vector,其元素将按特定顺序进行解释(例如res [0]为water,res [1]为power)以实现此目标,但您牺牲代码清晰度来实现此目标。 / LI>
  • 我还没有发现过无数其他的可能性!

当然,还有其他解决方案,但我希望这能为您在使用C ++设计系统时需要采用的思维过程提供一些洞察力。很可能你会想出一个你满意的设计,通过你的程序中途,然后意识到你已经碰壁,你的设计根本无法工作。这很好,它发生了。回去再次进行设计阶段,掌握第一个出错的地方。

答案 1 :(得分:2)

有一个关于你的问题的功能值得多关注:你还不知道最后会使用什么资源。您甚至不知道是否要在未来的版本2.0中添加一两个资源,也许允许您的用户定义自己的资源等等。所以我认为,您现在应该考虑更通用的方法。

那么您的建筑需要什么样的行为?他们需要能够告诉他们如何修改未知名称的资源。他们可能或者可能不必知道他们修改了哪些资源,但资源更新代码应该能够获取一些资源列表及其关联状态,并从中计算结果状态。

因此,我可能会编写类似这样的代码:

class Building {  //might be abstract
    public:
        virtual int getResourceDiff(std::string resource);  //might be pure virtual
};

和其他地方

//the header
class SomeGameWorldClass {
    private:
        std::unordered_map<std::string, int> resources;
        std::vector<std::shared_ptr<Building> > buildings;
    public:
        void updateResources();
};

//the .cpp file
void SomeGameWorldClass::updateResources() {
    for(resourcePair : resources) {
        for(buildingPtr : buildings) {
            resourcePair.second += buildingPtr->getResourceDiff(resourcePair.first);
        }
    }
}

这使您可以随意定义新资源,并且可以自由地以您想要的任何方式计算资源更新,具体取决于建筑物的类型。哎呀,你甚至可以定义一个Building - 子类来运行一些用户提供的场景控制代码来进行计算。

当然,我现在只使用哈希映射的字符串/整数对来存储名称和资源的当前数量。最有可能的是,这还不够,您可能需要在资源中添加更多行为,例如设置上限存储限制等等。因此,您很可能最终会使用另一个类来描述您的资源:

class Resource {
    public:
        ...
    private:
        int curAmount, maxAmount;
        std::string name;
        ...
};

答案 2 :(得分:1)

尽量使用字符串来表示建筑类型:

struct Building
{
    Building(std::string n, int p, int w) : name(n), power(p), water(w) {}
    std::string name;
    int power;
    int water;
};

int main()
{
    Building iceWell("Ice Well", -100, 50);
    Building solar("Solar Array", 150, 0);
    Building buildings[4] = { iceWell, iceWell, solar, solar };
    int totalPower = 0;
    int totalWater = 0;
    for (int i = 0; i < 4; ++i)
    {
        totalPower += buildings[i].power;
        totalWater += buildings[i].water;
    }
}

这可以改进,例如使用std::vector或任何适合您的问题的集合而不是数组。