Используйте typedef в struct для именования и индексации текстовых команд

#c #c #c-preprocessor

#c #c #c-препроцессор

Вопрос:

Я работаю с простым приложением командной строки, которое принимает текст ASCI и интерпретирует его как команду.

Я попытался минимизировать избыточность в этом приложении с помощью примера на http://gcc.gnu.org/onlinedocs/cpp/Concatenation.html .

например: рассмотрим программу на C, которая интерпретирует именованные команды. Вероятно, должна быть таблица команд, возможно, массив структур, объявленных следующим образом:

  struct command
 {
   char *name;
   void (*function) (void);
 };

 struct command commands[] =
 {
   { "quit", quit_command },
   { "help", help_command },
   ...
 };
 

Было бы чище не указывать имя каждой команды дважды, один раз в строковой константе и один раз в имени функции. Макрос, который принимает имя команды в качестве аргумента, может сделать это ненужным. Строковая константа может быть создана с помощью stringification, а имя функции — путем объединения аргумента с `_command’. Вот как это делается:

  #define COMMAND(NAME)  { #NAME, NAME ## _command }

 struct command commands[] =
 {
   COMMAND (quit),
   COMMAND (help),
   ...
 };
 

Теперь предположим, что я хочу иметь командную строку и значение индекса (т.Е.: int), а не строку и указатель на функцию.

  struct command
 {
   char *name;
   int command_idx;
 };
 

Теперь у меня есть средство для именования команд и какой-то индекс, который я могу использовать позже для программной идентификации каждой команды. Например, у меня есть оператор switch, который работает с индексом команды. Если я хочу работать с этими индексами, я должен сначала вручную установить значения.

Я могу вручную создать перечисляемый тип данных, но тогда я должен определить перечисленные константы в отдельном операторе enum. IE: команды перечисления { cmd_quit = 0, cmd_help } и, в конце концов, мне все равно приходится вводить имя каждой команды дважды: один раз с помощью макроса COMMAND() и снова в моем перечислении.

Существует ли какой-либо метод, использующий препроцессор C, который позволил бы мне создать макрос, создающий структуру «command» (с элементами string и int) и автоматически нумерующий значение int (command_idx), когда я добавляю больше команд с помощью макроса COMMAND()?

Я также знаю, что я могу просто использовать вызовы strcmp() для каждой возможной команды и сравнивать с вводом, предоставленным пользователем, но я хотел бы иметь прямое средство индексации в команды через значение command_idx, в отличие от strcmp’ing против огромного списка команд каждый раз (т.е.: O(1) вместо O(n) ). Я также хочу избежать необходимости вводить имя команды более одного раза любой ценой.

Спасибо!

Комментарии:

1. Зачем вам нужен индекс? Вам лучше использовать указатель на функцию…

2. Некоторые компиляторы имеют __COUNTER__ макрос. Вы могли бы использовать это.

Ответ №1:

Для достижения этой цели вы можете использовать переопределение макросов. Сначала вы создаете файл, в котором просто перечислены вызываемые вами команды commands.inc :

 COMMAND(quit)
COMMAND(help)
...
 

Затем в вашем исходном коде C вы можете #include "commands.inc" несколько раз, с разными определениями COMMAND() in effect, контролировать, как это работает. Например:

 struct command
{
   char *name;
   int command_idx;
};

#define COMMAND(NAME) CMD_ ## NAME,

enum command_enum {
#include "commands.inc"
};

#undef COMMAND

#define COMMAND(NAME) { #NAME, CMD_ ## NAME },

struct command commands[] =
{
#include "commands.inc"
};

#undef COMMAND
 

(Обратите внимание, что этот конкретный пример основан на улучшении C99, которое допускает , завершение в конце списков в enum объявлении и составном инициализаторе — вы можете легко обойти это в C89, добавив фиктивную запись в конце).

Комментарии:

1. Идеальный. Это именно то, что я хотел.

Ответ №2:

Вопрос:

Существует ли какой-либо метод, использующий препроцессор C, который позволил бы мне создать макрос, создающий структуру «command» (с элементами string и int) и автоматически нумерующий значение int (command_idx), когда я добавляю больше команд с помощью макроса COMMAND()?

Да, и поскольку вы также отметили вопрос как C :

 #include <iostream>
#include <map>
#include <string>
using namespace std;

map< string, int >     commands;

bool register_cmd( int id, string constamp; name )
{
    commands[name] = id;
    return true;
}

#define COMMAND( name ) 
    int const name ## _cmd = __LINE__; 
    bool const name ## _reg = register_cmd( name ## _cmd, #name )

COMMAND( exit );
COMMAND( help );
COMMAND( do_stuff );

int cmd_id( string constamp; name )
{
    auto const it = commands.find( name );
    return (it == commands.end()? -1 : it->second );
}

int main()
{
    for( auto it = commands.begin();  it != commands.end();    it )
    {
        cout << it->first << " => " << it->second << endl;
    }

    cout << "Gimme a command, please: ";
    string cmd;  getline( cin, cmd );
    switch( cmd_id( cmd ) )
    {
    case exit_cmd:
        cout << "You typed an EXIT command, which has id " << exit_cmd << endl;
        break;
    default:
        cout << "Hey, why not try an 'exit' command?" << endl;
    }
}
 

Я просто использовал map вместо fancy новую хэш-таблицу C 11, потому map что работает со старыми компиляторами и здесь нет реальной необходимости экономить наносекунды.

Приветствия и hth.,

Комментарии:

1. Спасибо. Теперь у меня есть как C99, так и C для достижения этой цели. Спасибо всем за вашу помощь!

2. Вероятно, вы могли бы исключить bool переменные, получив register_cmd() return id , а затем используя int const name ## _cmd = register_cmd(__LINE__, #name)