簡體   English   中英

使用數組初始化struct

[英]Initializing struct, using an array

我有幾個數組:

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

我遇到的問題是數組不是與struct字段順序或順序相同。 因此,我需要檢查每個,然后想出一個方案,將相同的結構放入結構中。

有什么幫助使用C或C ++來解決上述問題?

可能我沒有正確理解,但顯而易見的解決方案是將每個數組元素拆分為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后綴

這不應該太難。 你的第一個問題是你沒有固定大小的數組,所以你必須傳遞數組的大小,或者我更喜歡你使數組以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,它可以捕獲錯誤。

我寫了一些允許你初始化字段的小代碼,而不必過多擔心你的字段是否在初始化時出現故障。

以下是您在自己的代碼中使用它的方法:

/* 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從類中獲取它。

實際上,正如許多人所回答的那樣,需要將解析問題與對象構造問題分開。 工廠模式非常適合。

Boost.Spirit庫還以非常優雅的方式解決了parse-> function問題(使用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;
    }
  };

嘗試了你的想法,得到了一個

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

等'等'......

問題是你沒有在運行時引用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 =”包括在字符串或類似字符串中。 因此,您可以將完整的字符串放入值中,而不必預處理它,我認為這很大程度上取決於您解析常規的其余部分。

如果我在直接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並在第一次搜索時構建哈希。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM