Инициализация структуры с использованием массива

У меня есть пара массивов:

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 или С++ для решения вышеперечисленного?

Ответ 1

Это не должно быть слишком сложно. Ваша первая проблема заключается в том, что у вас нет массива с фиксированным размером, поэтому вам нужно будет передать размер массива или то, что я предпочел бы, чтобы вы делали массивы 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 (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 ...
}

EDIT: вы, вероятно, должны использовать long вместо int и atol вместо atoi, потому что ваш prl_id имеет тип long. Во-вторых, если после '=' могут быть ошибочно сформированные числа, вы должны использовать strtol, который может ловить ошибки.

Ответ 2

Вероятно, я не понял это правильно, но очевидными решениями являются разделение каждого элемента массива на key и value, а затем запись 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 sufffix - для них вам нужно создать немного другой макрос, который вставляет _id суффикс

Ответ 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 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.

Ответ 4

Действительно, как многие ответили, необходимо отделить проблему синтаксического анализа от проблемы построения объекта. Шаблон Factory подходит для этого.

Библиотека 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;
    }
  };

Ответ 5

Проблема в том, что у вас нет метаинформации для ссылки на элементы структуры во время выполнения (что-то вроде 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)? Существуют алгоритмы, в которых вы просто вводите свои ключевые слова, и функции выходят, и, к счастью, кто-то даже запрограммировал их: найдите инструмент/пакет gperf в своей любимой ОС/дистрибутиве. Там вы просто вводите 6 имен элементов, и он выводит вам готовый к использованию код C для идеальной хэш-функции (по умолчанию создается функция "хеш", которая возвращает хеш, и функцию "in_word_set", которая решает, ключ находится в списке слов). Поскольку хеш находится в другом порядке, вы, конечно, должны инициализировать массивы offsetof и size в порядке хэшей.

Другая проблема, которую вы имеете (и которую другие ответы не учитывают), - это преобразование типа. Другие выполняют задание, у меня (не лучше) memcopy. Здесь я бы предложил вам изменить массив размеров на другой массив:

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

Где каждая строка описывает модификатор sscanf для его чтения. Таким образом вы можете заменить назначение/копию на

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

Конечно, вы можете здесь варьироваться, включив в строку или элемент "element =". Таким образом, вы можете поместить всю строку в значение и не должны предварительно обрабатывать ее, я думаю, что это сильно зависит от остальной части вашей процедуры разбора.

Ответ 6

Если бы я сделал это прямо в 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 и при первом поиске, построил хэш.

Ответ 7

попробовал вашу идею и получил

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

и т.д. и т.д.............