【问题标题】:Initializing struct, using an array使用数组初始化结构
【发布时间】:2010-09-22 03:10:58
【问题描述】:

我有几个数组:

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键映射到struct中的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++ 解决上述问题有何帮助?

【问题讨论】:

    标签: c++ c parsing


    【解决方案1】:

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

    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)) {
    

    等'等'......

    【讨论】:

      【解决方案2】:

      如果我在直接 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) - 这要求所有结构定义都标有 cmets 说,“不要在不更改类型或内容的情况下更改它们t_fieldDef 此结构的数组”。还需要NULL 检查。

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

      【讨论】:

        【解决方案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;
        

        它使用鲜为人知的数据成员指针工作,您可以使用语法 &amp;classname::member 从类中获取。

        【讨论】:

        • +1 完成所有工作。 (顺便说一句,我目前正在从你最古老的答案走向最新的答案。有趣的是看看你的姿势风格如何变化,更微妙的是你的代码风格。:))
        • @GMan,谢谢 :) 我真的很想念这个答案中的 boost::variant ,虽然 :) 我的新姿势风格不会再与这样的工会混为一谈了 xD 如果你发现了请告诉我伙计,我的答案中有任何有趣的趋势。
        【解决方案4】:

        可能我没有正确理解,但显而易见的解决方案是将每个数组元素拆分为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 后缀

        【讨论】:

        • 谢谢,会试试这个。是的,我认为没有一个优雅的答案。
        • KISS - 让它变得更复杂你会得到什么。
        【解决方案5】:

        确实,就像许多人回答的那样,需要将解析问题与对象构造问题分开。工厂模式非常适合。

        Boost.Spirit 库还以一种非常优雅的方式(使用 EBNF 表示法)解决了 parse->function 问题。

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

        您可以通过以一种非常方便的方式开始写“你想做什么”并从那里开始“你是怎么做的”来实现这一点。

          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 值(0);而不是 int 值 (0);?
        【解决方案6】:

        这应该不会太难。您的第一个问题是您没有固定大小的数组,因此您必须传递数组的大小,或者我希望您使数组以 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,它可以捕获错误。

        【讨论】:

          【解决方案7】:

          问题是您没有在运行时引用结构元素的元信息(类似于 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 不允许使用字符串进行数组索引。但是计算机科学找到了解决这个问题的方法:完美哈希。

          所以它后来看起来像这样:

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

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

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

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

          每个字符串都描述了要读入的 sscanf 修饰符。这样您可以将赋值/复制替换为

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

          当然,您可以在此处进行更改,将“element=”包含到字符串或类似内容中。因此,您可以将完整的字符串放入 value 而不必对其进行预处理,我认为这在很大程度上取决于您其余的解析例程。

          【讨论】:

          • 如果您担心这里的线性搜索,您也可以对 word_list 进行排序并进行 bsearch。我认为这应该足够好了。当然我不知道该功能是否/将成为瓶颈。
          猜你喜欢
          • 2015-07-29
          • 1970-01-01
          • 1970-01-01
          • 2012-02-08
          • 2011-03-24
          • 2020-07-18
          • 2015-06-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多