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

J.T*_*J.T 4 c++ arrays data-structures

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

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

例如:

冰井:[-100功率,+ 50水],n = 2

太阳能阵列:[+150功率],n = 2

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

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

And*_*rew 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;
}
Run Code Online (Sandbox Code Playgroud)

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

如果生产/消费值可能随时间而变化,则可以将这些值保存为每个派生类的(私有)成员变量,并允许某些机制对其进行修改.您甚至可以getWater()根据某个时间度计算其值,因此随着时间的推移,井可能会"干涸".


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


在C++中很少能找到"单一正确的解决方案".通常有几种合理的方法,每种方法都有其自身的一套相对优点和缺点.

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

您有一些建筑物,这些建筑物具有定义其资源使用情况的属性.一些建筑物是净生产者,而其他建筑物是特定资源的净消费者.这会立即将"资源"标识为系统中的实体.我们可能直接进入并编写一些封装了我们的净资源使用概念的东西:

struct ResourceFootprint {
    int power;
    int water;
};
Run Code Online (Sandbox Code Playgroud)

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

(实例: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;
}
Run Code Online (Sandbox Code Playgroud)

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

IceWell,-100,50
SolarArray,150,0
HydroElectricPlant,150,150
Run Code Online (Sandbox Code Playgroud)

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

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

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

struct ResourceFootprint {
    int power;
    int water;
    int oil;
    int food;
    int medicine;
    int medical_waste;
};
Run Code Online (Sandbox Code Playgroud)

之前没有使用新资源的事情不必关心(尽管定义可能需要将额外的资源字段归零),并且使用新资源的事物可以用它们来定义:

    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};
Run Code Online (Sandbox Code Playgroud)

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

(实例: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;
    }
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但这种方法的缺点是什么?

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

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

基类看起来像这样:

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

        virtual ResourceFootprint currentResourceLevel() = 0;
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

        virtual ResourceFootprint currentResourceLevel() { return resources; }

    private:
        const ResourceFootprint resources;
};
Run Code Online (Sandbox Code Playgroud)

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;

};
Run Code Online (Sandbox Code Playgroud)

而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;

};
Run Code Online (Sandbox Code Playgroud)

该设计现在允许:

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

这不包括什么?

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

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