#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()
returnid
, а затем используяint const name ## _cmd = register_cmd(__LINE__, #name)