在C语言中转换结构项的有效方法,避免了switch-case

时间:2014-08-29 20:00:45

标签: c algorithm switch-statement

我有以下情况:有一个结构,其中包含大量不同类型的数据。还有另一个带有整数成员的结构,我将ID对应于我想要的第一个结构的数据。还有第三个结构应该携带数据。例如:

struct MyData
{
    char data1;
    float data2;
    short data3;
    //...
};

struct MyRequest
{
    int desiredDataId;
};

struct DataValue
{
    long data;
};

在系统中,我使用MyRequest发送消息给它的一部分,告诉“我想要数据X”。然后系统应该查看所请求的Id对应的哪个对应缓冲区(MyData成员);然后创建一个DataValue并将所需的数据(已转换)放到data成员。

关键点:最明显的方法是做系统根据MyRequest的Id确定哪个MyData成员被请求的部分是一个switch-case:

switch(MyRequest.desiredDataId)
{
    case 0: //Id correspondent to data1
        DataValue.data = (long) MyData.data1;
        break;
    case 1: //Id correspondent to data2
        DataValue.data = (long) MyData.data2;
        break;
    case 3: //Id correspondent to data3
        DataValue.data = (long) MyData.data1;
        break;
    //...
}

问题:当MyData内的成员数量很少时,这很好;但我需要一个非常大的MyData的解决方案 - 现在,有一个像370个成员的东西,什么会导致一个巨大的开关案例 - 370个案例!

问题:是否有其他方法可以使用较少的编码进行此类映射?

目前,我将创建一个void*数组,将指针映射到MyData(在我的情况下全局声明)。因此,不是巨大的开关情况,我必须首先设置一个巨大的数组,然后调用

DataValue.data = *(long)pointerArray[MyRequest.data]

问题是,尽管避免使用巨大的交换机箱,现在我必须面对一个巨大的阵列初始化,所以不是“主解决方案”。

还有另一种可行的选择吗?

注意:如果我写错了C语法,请原谅我;我急着写这个问题:)

2 个答案:

答案 0 :(得分:1)

你的问题似乎是一个经典的情况,使用像哈希表这样的键/值存储(在c中有例如ghashuthash),其中MyRequest.data(又名MyRequest。 desiredDataId)将是键,DataValue.data将是值。使用void指针的解决方案几乎是最简单的实现:因为所有键都是整数,所以hash函数是identity函数。

我不确定你为什么要寻找替代方案,但如果它是速度而且你确实找到了比哈希表更快的东西,我相信世界会想知道。我认为你的void *解决方案的速度和它一样快(我希望你实际上并没有使用char,int和float,但那些只是示例,并且长转换是类似序列化例程的抽象形式 - 如果它不是一个长数组,转换为赋值,并保存间接)。

但是,正如我猜测的那样,您不希望在实现数据结构和维护数据时遇到问题,您应该能够使用哈希表获得相对快速的解决方案。

答案 1 :(得分:0)

假设有以下要求:

  1. struct MyData必须包含具有不同类型的所有数据字段,具体针对每个字段。
  2. MyRequest::desiredDataId标识连续范围内的数据字段,或至少具有可填充虚拟字段的非常小的间隙。
  3. 请求必须返回struct DataValue,并将字段值转换为long中的DataValue::data
  4. 代码不得重复。
  5. 我建议以下实施:

    class DataField {
    public:
        virtual ~DataField() {}
        virtual long toLong() const = 0;
    };
    
    // Wrapper for T that implements toLong()
    template <typename T>
    class DataFieldImpl : public DataField {
    public:
        DataFieldImpl() = default;
        DataFieldImpl(T v) : value(v) {} // implicit construction from T
        DataFieldImpl& operator=(T v) {  // direct assignment from T
            value = v;
            return *this;
        }
        operator T() const     {return value;} // implicit conversion to T
        const T& value() const {return value;} // const accessor
        T& value()             {return value;} // mutable accessor
    
        virtual long toLong() const {
            return static_cast<long>(value);
        }
    
    private:
        T value;
    };
    
    struct MyData {
        static const size_t N = 370;
        DataField* data[N];
    
        // Implement constructor and destructor to create and destroy the fields.
    } myData;
    
    void doRequest(const MyRequest& request, DataValue* response) {
        if (request.desiredDataId < MyData::N) {
            response->data = myData.data[request.desiredDataId].toLong();
        }
    }
    

    关于“热门”void*解决方案:您无法将void转换为long。这将需要有关每个字段的元数据信息,指示每个字段的类型以及如何将每个void*转换为long,如简单的operator*,然后转换为long willn'工作。您可以设计这些元数据,这将是一个有效且高效的解决方案(也是您在C中的唯一选择)。虚拟方法和模板实际上只是使用C ++语言功能的那些元数据的实现。虚函数调用的开销(至少在理论上)相当于在类似C的解决方案中获取元数据,相当于在big switch语句中进行分支(假设switch子句的编译器优化正确)。