C++;参数数目未知的函数指针 我需要一些C++的帮助,请!p>

C++;参数数目未知的函数指针 我需要一些C++的帮助,请!p>,c++,function-pointers,C++,Function Pointers,我正在为一个基于文本的小游戏编写一个命令解析器,我遇到了一些问题。解析器应该读取和解析播放器输入的命令 最明显、最直接的解决方案可能是这样的(用伪代码编写): 因此,我希望我能清楚地表明我希望发生什么。玩家可以输入类似的内容 > go south 我本可以用如下方式完成代码: destination = strtok(NULL, " "); fptr(destination); while(1) { cin >> input; char * tok = strtok(i

我正在为一个基于文本的小游戏编写一个命令解析器,我遇到了一些问题。解析器应该读取和解析播放器输入的命令

最明显、最直接的解决方案可能是这样的(用伪代码编写):

因此,我希望我能清楚地表明我希望发生什么。玩家可以输入类似的内容

> go south
我本可以用如下方式完成代码:

destination = strtok(NULL, " ");
fptr(destination);
while(1) {
 cin >> input;
 char * tok = strtok(input, " ")
 functionpointer fptr = command_map.find(tok);
 if(tok == "go"){
    destination = strtok(NULL, " ");
    fptr(destination);
 } else if (tok == "attack") {
    target = strtok(NULL, " ");
    weapon = strtok(NULL, " ");
    fptr(target, weapon);
   }
}
基本上,从映射返回的值将是执行命令“go”的函数,该函数显然接受一个参数,即destination。同样,这是一些C++伪ish代码。所以我得到了“去”的命令。但是现在说我想得到以下命令:

> attack troll with sword
现在我觉得我需要做一些事情,比如:

destination = strtok(NULL, " ");
fptr(destination);
while(1) {
 cin >> input;
 char * tok = strtok(input, " ")
 functionpointer fptr = command_map.find(tok);
 if(tok == "go"){
    destination = strtok(NULL, " ");
    fptr(destination);
 } else if (tok == "attack") {
    target = strtok(NULL, " ");
    weapon = strtok(NULL, " ");
    fptr(target, weapon);
   }
}
同样,这是伪代码。你可能会看到我挂断的东西:我有函数指针的映射,但是因为我有可变数量的参数和参数类型,因为我想根据我作为输入得到的调用不同的函数,所以我可以在没有映射和函数指针的情况下这样做,就像我第一次向你展示的那样。有没有什么方法可以让它更一般化,而不必使用if-else子句来计算要传递多少个参数


我希望你能理解我需要的帮助:)谢谢你的阅读

不必让主循环读取“被动”函数所需的所有参数,您可以更改设计以遵循命令设计模式,并让函数/命令对象进行参数解析。这样您就不需要预先知道函数的签名

您可以使用责任链来找到适当的命令,并让该命令使用下一个令牌

一个例子,使用流代替Strutok(Heck,我们是C++,这里?)-警告:未编译的、未经测试的C++ ISE伪代码:

command <- read input from the player
if command == COMMAND1
    do command1
else if command == COMMAND 2
    do command2
...
struct ICommand {
   // if cmd matches this command,
   // read all needed tokens from stream and execute
   virtual bool exec( string cmd, istream& s ) = 0;
};

struct Command : public ICommand {
   string name;
   Command( string name ):name(name){}
   virtual bool exec( string cmd, istream& s ) {
      if( cmd != name ) return false;
      parse_and_exec( s );
      return true;
   }
   virtual void parse_and_exec( istream& s ) = 0;
};
已执行的命令:

struct Go : public Command {
   Go():Command("Go"){}

   virtual void parse_and_exec( istream& s ) {
        string direction;
        s >> direction;
        ... stuff with direction
   }
 };
还有一些主回路:

 ICommand* commands [] = 
 { new Go()
 , new Attack()
 , new PickUp()
 ...
 , NULL // a sentinel
 };

 string cmd;
 cin >> cmd;
 while( cmd != "quit" ) {
    // find a command that can handle it
    // note: too simple to handle erroneous input, end of stream, ...
    for( ICommand* c = commands; c != NULL && !c->exec(cmd, cin); ++c );
    cin >> cmd;
 }
你可以用更强大的效用函数等来完善这个想法


如果您希望语法非常难,那么最好使用“真正的”解析器框架,例如..

更好的解决方案是让所有函数都使用相同的参数。一个好主意是首先将输入完全标记化(例如,转换为字符串向量),然后将该数组传递给函数。然后可以使用关联容器(例如哈希表或
std::map
)将命令标记映射到处理程序函数

例如:

typedef std::vector<std::string> TokenArray;
typedef void (*CommandHandler)(const TokenArray&);
typedef std::map<std::string, CommandHandler> CommandMap;
void OnGo(const TokenArray& tokens)
{
    // handle "go" command
}
void OnAttack(const TokenArray& tokens)
{
    // handle "attack" command
}
// etc.

CommandMap handlers;
handlers["go"] = &OnGo;
handlers["attack"] = &OnAttack;
// etc.

while(1) {
  std::string input;
  cin >> input;
  std::istringstream tokenizer(input);
  TokenArray tokens;
  std::string token;
  while(tokenizer >> token)  // Tokenize input into whitespace-separated tokens
    tokens.push_back(token);
  CommandMap::iterator handler = handlers.find(tokens[0]);
  if(handler != handlers.end())
      (*handler)(tokens);  // call the handler
  else
      ; // handle unknown command
}
typedef std::vector TokenArray;
typedef void(*CommandHandler)(const-TokenArray&);
typedef std::map命令映射;
void OnGo(常量令牌数组和令牌)
{
//处理“go”命令
}
void OnAttack(常量令牌数组和令牌)
{
//处理“攻击”命令
}
//等等。
命令映射处理程序;
处理程序[“go”]=&OnGo;
处理程序[“攻击”]=&OnAttack;
//等等。
而(1){
std::字符串输入;
cin>>输入;
std::istringstream标记器(输入);
令牌数组令牌;
字符串标记;
while(tokenizer>>token)//将输入标记化为空格分隔的标记
代币。推回(代币);
CommandMap::iterator handler=handlers.find(标记[0]);
if(handler!=handlers.end())
(*handler)(标记);//调用处理程序
其他的
;//处理未知命令
}

你有没有想过要做一点OO。有一个抽象类,比如“Command”,并有专门的类来处理特定的命令

struct Command
{
  virtual void Execute( const string &commandline ) = 0;
};

struct GoCommand: Command
{
  void Execute( const string &commandline )
  {
    // Do whatever you need here.
  }
}
让工厂根据用户输入的命令创建命令对象

struct CommandFactory
{
  Command *GetCommand( const string &commandName )
  {
    if( commandNome == "go" )
    {
     return new GoCommand();
    }
    .........
    ........
  }
}
在客户端中,获取命令对象并调用“Execute()”方法


您可以使用自动指针进行更好的内存管理。

您需要的唯一功能参数是命令行的其余部分。每个函数都应该根据它的需要标记它

我写了几乎相同的答案,然后我接到了一个电话,所以你打败了我。但是在你生日那天你会接到很多电话,所以我不能对你这么说+1@Chris卢茨:生日快乐,如果可以的话,我会投票支持你的评论!代码的最后一部分没有将解析后的命令
cmd
传递给每个
ICommand
,因此每个命令都没有信息来确定是否要使用
istream
中的数据。需要注意的是,实际命令必须在循环中解析并传递给commmand。原因是,如果每个命令都从流中读取,它就不能(轻松地)使
istream保持与以前相同的状态。@David Rodriguez:当然,你是对的。
ICommand
有这个函数,我只是忘了调用它。这是一个自制解析器的价格…+1比我的想法更好的解决方案(现在我考虑了),但是如果你
typedef std::vector TokenArray你不应该在一致性的前提下在<循环>中声明<代码>令牌>代码> <代码>吗?@克里斯:是的,好的调用,我在编写TyPulf.+1之前写了那个部分,并且还考虑使用函数适配器(<代码> STD::函数< /COD>从C++ 0x或<代码> Boo::函数< /代码>,对于没有C++ 0x支持的编译器)这将允许您使用解决方案中的自由(静态)函数或成员函数,甚至可以调整不同的现有函数签名。谢谢,它工作得很好!