使用数组初始化struct

时间:2008-11-13 07:02:29

标签: c++ c parsing

我有几个阵列:

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"};

然后我需要解析'='然后将值放在struct中。 (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;
};

我遇到的问题是数组不是与struct字段顺序或顺序相同。因此,我需要检查每个,然后提出一个方案,将相同的结构放入结构中。

使用C或C ++解决上述问题有何帮助?

7 个答案:

答案 0 :(得分:6)

可能我没有正确理解,但显而易见的解决办法是将每个数组元素拆分为keyvalue,然后编写lo-o-ong 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后缀

答案 1 :(得分: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属于long类型。第二,如果在'='之后可能有错误的格式化数字,你应该使用strtol,它可以捕获错误。

答案 2 :(得分: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从类中获取。

答案 3 :(得分:1)

实际上,正如许多人所回答的那样,需要将解析问题与对象构造问题分开。工厂模式很适合。

Boost.Spirit库还以非常优雅的方式解决了parse-&gt;函数问题(使用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;
    }
  };

答案 4 :(得分:0)

问题是你没有在运行时引用struct元素的元信息(类似于structVar。$ ElementName = ...,其中$ ElementName不是元素名称而是包含元素的(char?)变量应该使用的名称)。 我的解决方案是添加这个元信息。 这应该是一个数组,其中包含结构中元素的偏移量。

Quick-n-Dirty解决方案:您添加一个包含字符串的数组,结果代码应如下所示:

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 doenst允许使用字符串进行数组索引。但是计算机科学找到了解决这个问题的方法:完美的哈希。

所以事后看起来会像这样:

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

但是如何获得这些完美的哈希函数(我称之为calc_perf_hash)? 有一些算法可以让你只填充你的关键字,并且功能出来了,幸运的是有人甚至对它们进行了编程:在你最喜欢的OS /发行版中寻找“gperf”工具/包。 在那里你只需要输入6个元素名称,然后输出你准备使用C代码的完美哈希函数(在默认情况下生成一个返回哈希值的函数“hash”)和一个“in_word_set”函数来决定是否给定键在单词列表中)。 因为哈希的顺序不同,所以你当然要按哈希的顺序初始化offsetof和size数组。

您遇到的另一个问题(以及其他答案未考虑的问题)是类型转换。其他人做了一个任务,我有(不是更好)记忆。 在这里,我建议您将sizes数组更改为另一个数组:

const char * modifier[]={"%i","%c", ...

每个字符串都描述了sscanf修饰符来读取它。这样你可以用

替换赋值/副本
sscanf(valueString, modifier[hash], &mystructVar + offsets(hash));

Cf当然,你可以在这里改变,通过将“element =”包含在字符串或类似字符串中。所以你可以将完整的字符串放入值中,而不必预处理它,我认为这很大程度上取决于你们其余的解析例程。

答案 5 :(得分:0)

如果我在直接C中这样做,我不会使用所有的母亲。相反,我会做这样的事情:

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检查。

我也更喜欢查找是二进制搜索还是哈希,但对于大多数情况来说这可能已经足够了。如果我要做哈希,我会将NULL哈希表的指针放入t_structLayout并在第一次搜索时构建哈希值。

答案 6 :(得分: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)) {

等'等'......