初始化结构体,使用数组

4

我有一些数组:

const string a_strs[] = {"cr=1", "ag=2", "gnd=U", "prl=12", "av=123", "sz=345", "rc=6", "pc=12345"};
const string b_strs[] = {"cr=2", "sz=345", "ag=10", "gnd=M", "prl=11", "rc=6", "cp=34", "cv=54", "av=654", "ct=77", "pc=12345"};

我需要将其解析为'='并将值放入结构体中(rc键映射到结构体中的fc键),格式如下:

struct predict_cache_key {
    pck() :
        av_id(0),
        sz_id(0),
        cr_id(0),
        cp_id(0),
        cv_id(0),
        ct_id(0),
        fc(0),
        gnd(0),
        ag(0),
        pc(0),
        prl_id(0)
    { }

    int av_id;
    int sz_id;
    int cr_id;
    int cp_id; 
    int cv_id;
    int ct_id;
    int fc;
    char gnd;
    int ag;
    int pc;
    long prl_id;
};

我遇到的问题是数组不按照结构体字段的顺序排列,因此我需要检查每个数组并想出一种将其放入结构体中的方案。
有关使用C或C ++解决上述问题的任何帮助?
7个回答

6

可能我没有理解正确,但显然的解决方案是将每个数组元素拆分为keyvalue,然后编写冗长的if-else-if-else ...序列,例如:

if (!strcmp(key, "cr"))
   my_struct.cr = value;
else if (!strcmp(key, "ag"))
   my_struct.ag = value;
...

您可以通过C预处理器自动创建这种序列,例如: #define PROC_KEY_VALUE_PAIR(A) else if (!strcmp(key,#A)) my_struct.##A = value 由于前面带有else关键字,您应该按照以下方式编写代码:
if (0);
PROC_KEY_VALUE_PAIR(cr);
PROC_KEY_VALUE_PAIR(ag);
...

唯一的问题是,你们中的一些结构字段有 _id 后缀 - 对于它们,您需要创建一个稍微不同的宏,以粘贴 _id 后缀。


谢谢,我会试一下。是的,我认为没有一个优雅的答案。 - gagneet
KISS - 通过让它变得更加复杂,你得到了什么? - BIBD

3

这应该不难。你的第一个问题是你没有一个固定大小的数组,所以你需要传递数组的大小,或者我更喜欢你把数组设置为NULL结尾,例如:

const string a_strs[] = {"cr=1", "ag=2", "gnd=U", NULL};

然后我会编写一个(私有)辅助函数来解析字符串:


bool
parse_string(const string &str, char *buffer, size_t b_size, int *num)
{
    char *ptr;

    strncpy(buffer, str.c_str(), b_size);
    buffer[b_size - 1] = 0;

    /* find the '=' */
    ptr = strchr(buffer, '=');

    if (!ptr) return false;

    *ptr = '\0';
    ptr++;

    *num = atoi(ptr);

    return true;
}

那么你可以按照qrdl的建议进行操作。

通过一个简单的for循环:


for (const string *cur_str = array; *cur_str; cur_str++)
{
   char key[128];
   int value = 0;

   if (!parse_string(*cur_string, key, sizeof(key), &value)
       continue;

   /* and here what qrdl suggested */
   if (!strcmp(key, "cr")) cr_id = value;
   else if ...
}

编辑:你应该使用long而不是int和atol而不是atoi,因为你的prl_id是长整型。其次,如果在'='后面可能有格式错误的数字,你应该使用strtol,它可以捕获错误。


3
我写了一些小代码,可以让你初始化字段,而不必过多地担心你的字段是否与初始化顺序不一致。
以下是如何在您自己的代码中使用它:
/* clients using the above classes derive from lookable_fields */
struct predict_cache_key : private lookable_fields<predict_cache_key> {
    predict_cache_key(std::vector<std::string> const& vec) {
        for(std::vector<std::string>::const_iterator it = vec.begin();
            it != vec.end(); ++it) {
            std::size_t i = it->find('=');
            set_member(it->substr(0, i), it->substr(i + 1));
         }
    }

    long get_prl() const {
        return prl_id;
    }

private:

    /* ... and define the members that can be looked up. i've only
     * implemented int, char and long for this answer. */
    BEGIN_FIELDS(predict_cache_key)
        FIELD(av_id);
        FIELD(sz_id);
        FIELD(gnd);
        FIELD(prl_id);
    END_FIELDS()

    int av_id;
    int sz_id;
    char gnd;
    long prl_id;
    /* ... */
};

int main() {
    std::string const a[] = { "av_id=10", "sz_id=10", "gnd=c",
                              "prl_id=1192" };
    predict_cache_key haha(std::vector<std::string>(a, a + 4));
}

框架如下。
template<typename T>
struct entry {
    enum type { tchar, tint, tlong } type_name;

    /* default ctor, so we can std::map it */
    entry() { }

    template<typename R>
    entry(R (T::*ptr)) {
        set_ptr(ptr);
    }

    void set_ptr(char (T::*ptr)) {
        type_name = tchar;
        charp = ptr;
    };

    void set_ptr(int (T::*ptr)) {
        type_name = tint;
        intp = ptr;        
    };

    void set_ptr(long (T::*ptr)) {
        type_name = tlong;
        longp = ptr;        
    };

    union {
        char (T::*charp);
        int  (T::*intp);
        long (T::*longp);
    };
};

#define BEGIN_FIELDS(CLASS)       \
    friend struct lookable_fields<CLASS>; \
    private:                      \
    static void init_fields_() {   \
        typedef CLASS parent_class;

#define FIELD(X) \
    lookable_fields<parent_class>::entry_map[#X].set_ptr(&parent_class::X)

#define END_FIELDS() \
    }                                                                              

template<typename Derived>
struct lookable_fields {
protected:
    lookable_fields() {
        (void) &initializer; /* instantiate the object */
    }

    void set_member(std::string const& member, std::string const& value) {
        typename entry_map_t::iterator it = entry_map.find(member);
        if(it == entry_map.end()) {
            std::ostringstream os;
            os << "member '" << member << "' not found";
            throw std::invalid_argument(os.str());
        }

        Derived * derived = static_cast<Derived*>(this);

        std::istringstream ss(value);
        switch(it->second.type_name) {
        case entry_t::tchar: {
            /* convert to char */
            ss >> (derived->*it->second.charp);
            break;
        }
        case entry_t::tint: {
            /* convert to int */
            ss >> (derived->*it->second.intp);
            break;
        }
        case entry_t::tlong: {
            /* convert to long */
            ss >> (derived->*it->second.longp);
            break;
        }
        }
    }

    typedef entry<Derived> entry_t;
    typedef std::map<std::string, entry_t> entry_map_t;
    static entry_map_t entry_map;

private:
    struct init_helper {
        init_helper() {
            Derived::init_fields_();
        }
    };

    /* will call the derived class's static init function */
    static init_helper initializer;
};

template<typename T> 
std::map< std::string, entry<T> > lookable_fields<T>::entry_map;

template<typename T> 
typename lookable_fields<T>::init_helper lookable_fields<T>::initializer;

它使用较少为人知的数据成员指针,你可以使用语法&classname::member从类中获取。


1
对于所有这些工作给你加一分。 (顺便说一句,我正在从您最早回答的问题走到最新的问题。很有趣看到您的发帖风格如何改变,更微妙的是,您的代码风格也在变化 :)) - GManNickG
@GMan,谢谢 :) 不过我真的很想在这个答案中使用boost::variant :) 我的新姿势不会再像这样搞乱联合体了xD 如果你发现我的回答有任何有趣的趋势,请告诉我,伙计。 - Johannes Schaub - litb

2

确实,许多人都认为需要将解析问题与对象构建问题分开。工厂模式非常适合这种情况。

Boost.Spirit库也以非常优雅的方式解决了解析-函数问题(使用EBNF符号)。

我总是喜欢将“业务逻辑”与框架代码分开。

您可以通过以非常便捷的方式开始编写“您想要做什么”,然后从那里开始思考“如何做到”来实现这一点。

  const CMemberSetter<predict_cache_key>* setters[] = 
  #define SETTER( tag, type, member ) new TSetter<predict_cache_key,type>( #tag, &predict_cache_key::##member )
  { SETTER( "av", int, av_id )
  , SETTER( "sz", int, sz_id )
  , SETTER( "cr", int, cr_id )
  , SETTER( "cp", int, cp_id )
  , SETTER( "cv", int, cv_id )
  , SETTER( "ct", int, ct_id )
  , SETTER( "fc", int, fc )
  , SETTER( "gnd", char, gnd )
  , SETTER( "ag", int, ag )
  , SETTER( "pc", int, pc )
  , SETTER( "prl", long, prl_id )
  };

  PCKFactory<predict_cache_key> factory ( setters );

  predict_cache_key a = factory.factor( a_strs );
  predict_cache_key b = factory.factor( b_strs );

实现这一目标所需的框架如下:

  // conversion from key=value pair to "set the value of a member"
  // this class merely recognises a key and extracts the value part of the key=value string
  //
  template< typename BaseClass >
  struct CMemberSetter {

    const std::string key;
    CMemberSetter( const string& aKey ): key( aKey ){}

    bool try_set_value( BaseClass& p, const string& key_value ) const {
      if( key_value.find( key ) == 0 ) {
        size_t value_pos = key_value.find( "=" ) + 1;
        action( p, key_value.substr( value_pos ) );
        return true;
      }
      else return false;
    }
    virtual void action( BaseClass& p, const string& value ) const = 0;
  };

  // implementation of the action method
  //
  template< typename BaseClass, typename T >
  struct TSetter : public CMemberSetter<BaseClass> {
    typedef T BaseClass::*TMember;
    TMember member;

    TSetter( const string& aKey, const TMember t ): CMemberSetter( aKey ), member(t){}
    virtual void action( BaseClass& p, const std::string& valuestring ) const {
      // get value
      T value ();
      stringstream ( valuestring ) >> value;
      (p.*member) = value;
    }
  };


  template< typename BaseClass >
  struct PCKFactory {
    std::vector<const CMemberSetter<BaseClass>*> aSetters;

    template< size_t N >
    PCKFactory( const CMemberSetter<BaseClass>* (&setters)[N] )
      : aSetters( setters, setters+N ) {}

    template< size_t N >
    BaseClass factor( const string (&key_value_pairs) [N] ) const {
      BaseClass pck;

      // process each key=value pair
      for( const string* pair = key_value_pairs; pair != key_value_pairs + _countof( key_value_pairs); ++pair ) 
      {
        std::vector<const CMemberSetter<BaseClass>*>::const_iterator itSetter = aSetters.begin();
        while( itSetter != aSetters.end() ) { // optimalization possible
          if( (*itSetter)->try_set_value( pck, *pair ) )
            break;
          ++itSetter;
        }
      }

      return pck;
    }
  };

有趣的是C和C++版本有多大的不同。顺便问一下,应该是T value(0);而不是int value(0);吧? - quinmars

0

尝试了你的想法,得到了一个

error: ISO C++ forbids declaration of ‘map’ with no type

在Linux Ubuntu Eclipse CDT中。

我想提醒大家,在使用您的代码时,应该在“*.h”文件中包含<map>,以避免出现此错误消息。

#include <map>

// a framework

template<typename T>
struct entry {
    enum type { tchar, tint, tlong } type_name;

    /* default ctor, so we can std::map it */
    entry() { }

    template<typename R>
    entry(R (T::*ptr)) {

等等……


0
问题在于您没有元信息来在运行时引用结构元素(类似于structVar.$ElementName = ...,其中$ElementName不是元素名称,而是包含应使用的元素名称的(char?)变量)。 我的解决方案是添加这些元信息。 这应该是一个包含结构中元素偏移量的数组。
一种简单粗暴的解决方案是添加一个带有字符串的数组,生成的代码应该如下所示:
const char * wordlist[] = {"pc","gnd","ag","prl_id","fc"};
const int  offsets[] = { offsetof(mystruct, pc), offsetof(mystruct, gnd), offsetof(mystruct, ag), offsetof(mystruct, prl_id), offsetof(mystruct, fc)};
const int sizes[] = { sizeof(mystruct.pc), sizeof(mystruct.gnd), sizeof(mystruct.ag), sizeof(mystruct.prl_id), sizeof(mystruct.fc)}

要输入某些内容,您可以这样做:

index = 0;
while (strcmp(wordlist[index], key) && index < 5)
    index++;
if (index <5)
   memcpy(&mystructvar + offsets[index], &value, sizes[index]);
else
   fprintf(stderr, "Key not valid\n"); 

如果你有更大的结构,这个插入循环可能会变得很昂贵,但是C语言不允许使用字符串进行数组索引。但是计算机科学已经为这个问题找到了解决方案:完美哈希。

因此,它之后看起来会像这样:

hash=calc_perf_hash(key);
memcpy(&mystruct + offsets[hash], &value, sizes[hash]);

如何获取这些完美的哈希函数(我称之为calc_perf_hash)呢?有一些算法可以让您将关键字输入其中,然后就会得到相应的函数,并且幸运的是,有人已经编写了这些算法:在您喜欢的操作系统/发行版中查找“gperf”工具/软件包即可。在那里,您只需输入这6个元素名称,他就会输出用于完美哈希函数的C代码(默认生成一个名为“hash”的函数,它返回哈希值,以及一个名为“in_word_set”的函数,它用于确定给定的键是否在单词列表中)。因为哈希顺序不同,您当然需要按照哈希的顺序初始化offsetof和大小数组。
您可能还会遇到另一个问题(其他答案未考虑):类型转换。其他答案使用赋值语句,而我使用(不比其他方法更好的)memcopy。在这里,我建议您将大小数组更改为另一个数组。
const char * modifier[]={"%i","%c", ...

每个字符串描述了sscanf修饰符以便读取它。这样你就可以用它来替换赋值/复制操作。

sscanf(valueString, modifier[hash], &mystructVar + offsets(hash));

当然,您可以在此处进行变化,通过将“element =”包含到字符串中或类似方式。因此,您可以将完整的字符串放入值中,而无需预处理它。我认为这在很大程度上取决于您解析例程的其余部分。

如果你担心这里的线性搜索,你也可以对word_list进行排序并执行bsearch。我认为那应该足够好了。当然,我不知道那个函数是否/将成为瓶颈。 - quinmars

0
如果我用纯 C 语言来实现,我不会使用所有 if 语句的母版。相反,我会像这样做:
typedef struct {
    const char *fieldName;
    int structOffset;
    int fieldSize;
} t_fieldDef;

typedef struct {
    int fieldCount;
    t_fieldDef *defs;
} t_structLayout;

t_memberDef *GetFieldDefByName(const char *name, t_structLayout *layout)
{
    t_fieldDef *defs = layout->defs;
    int count = layout->fieldCount;
    for (int i=0; i < count; i++) {
        if (strcmp(name, defs->fieldName) == 0)
            return defs;
        defs++;
    }
    return NULL;
}

/* meta-circular usage */
static t_fieldDef metaFieldDefs[] = {
    { "fieldName", offsetof(t_fieldDef, fieldName), sizeof(const char *) },
    { "structOffset", offsetof(t_fieldDef, structOffset), sizeof(int) },
    { "fieldSize", offsetof(t_fieldDef, fieldSize), sizeof(int) }
};
static t_structLayout metaFieldDefLayout =
    { sizeof(metaFieldDefs) / sizeof(t_fieldDef), metaFieldDefs };

这样可以在运行时使用紧凑的结构布局集合按名称查找字段。这相当容易维护,但我不喜欢实际使用代码中的sizeof(mumble) - 这要求所有结构定义都带有注释,说“不要改变类型或内容,除非在此结构的t_fieldDef数组中进行更改”。还需要进行NULL检查。

我也希望查找是二进制搜索或哈希,但这对于大多数情况可能已经足够了。如果我要做哈希,我会将指向t_structLayoutNULL哈希表的指针放入,并在第一次搜索时构建哈希。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接