【问题标题】:C++ How to use less conditional statements?C++如何使用更少的条件语句?
【发布时间】:2015-01-24 04:42:48
【问题描述】:

对于我的任务,我正在存储用户登录信息。我正在接受一个作为命令的字符串。该命令可以是创建、登录、删除等。总共有 10 个选项,即可能有 10 个不同的字符串。谁能解释一种更有效的方法来编写这个而不是 10 个 if 和 else if 语句?基本上除了使用一堆if (string == "one")else if (string == "two")之外,我应该如何格式化/构造东西。谢谢

【问题讨论】:

  • 大概你的意思是==而不是=。你可以做一个查找表,但如果你有 10 段不同的代码要执行,那么真的没有办法绕过编写 10 段代码!
  • 是的,或者你必须将它们存储在一个表中
  • 如果您有许多命令选项可以在应用程序的生命周期中更改,那么一个灵活的选择是使用映射或哈希表映射命令到您要调用的函数或类似函数的对象 - 但是对于 10 个固定选项,为了简单/易读,我可能只使用 if/else
  • table / hash-switch / etc. 解决方案有很多样板可以让它们运行,所以我不会打扰,除非你至少有 20 个案例(并计划轻松添加/将来删除)
  • 毫无疑问会有人来发一个map的字符串到lambda

标签: c++ string conditional


【解决方案1】:

我希望您的讲师希望您将函数提取到另一个可重用函数:

string action;
command = CreateAction(action);
command.Do(...);

当然,在您的 CreateAction 类中,您仍然需要有条件来确定需要创建哪些命令。

AbstractCommand CreateAction(action)
{
    if (action == "login")
        return LoginCommand();
    else if (action == "remove")
        return RemoveCommand();
    ..... etc etc
}

如果你真的想摆脱所有条件,你可以创建一些自注册命令,但这涉及更多的代码和类......

您应该查找 Command PatternFactory Pattern 之类的内容

【讨论】:

    【解决方案2】:

    您可以使用函数指针和查找表。

    typedef void (*Function_Pointer)(void);
    void Create(void);
    void Login(void);
    void Remove(void);
    
    struct Function_Option_Entry
    {
      const char * option_text;
      Function_Pointer p_function;
    };
    
    Function_Option_Entry option_table[] =
    {
      {"one", Create},
      {"two", Login},
      {"three", Remove},
    };
    const unsigned int option_table_size =
      sizeof(option_table) / sizeof(option_table[0]);
    
    //...
    std::string option_text;
    //...
    for (i = 0; i < option_table_size; ++i)
    {
      if (option_text == option_table[i].option_text)
      {
        option_table[i].p_function();
        break;
      }
    }
    

    【讨论】:

      【解决方案3】:

      使用switch 和一个简单的哈希函数。
      您需要使用哈希函数,因为 C 和 C++ 只允许切换整数值。

      template<size_t N> constexpr char myhash(const char &x[N]) { return x[0] ^ (x[1]+63); }
      char myhash(const string& x) { return x.size() ? x[0] ^ (x[1]+63) : 0; }
      
      switch(myhash(s)) {
      case myhash("one"):
          if(s != "one") goto nomatch;
          // do things
          break;
      case myhash("two"):
          if(s != "two") goto nomatch;
          // do things
          break;
      default:
      nomatch:
          // No match
      }
      

      如果您不使用std::string,则需要稍作调整。

      【讨论】:

      • 不错的哈希想法,+1,虽然 goto 应该被禁止使用 C/C++ :) 在当前代码中还可以,但是用户的滥用会造成噩梦。
      • @vsoftco:谢谢。我坚决支持使用正确的工具来完成这项工作,即使那应该是可怕的goto。 (是的,给定的 一个合适的用途。)永远记得 Djikstra 在结构化编程(使用任何 goto)刚刚开始时写了那篇著名的文章(是的,这意味着他有点夸张,虽然不多)。
      • 好主意,但是您的哈希函数是否适用于仅包含一个符号的字符串?
      • @Deduplicator:我的意思是您只需检查长度是否不为零,然后直接选择 x[1]。如果字符串的大小等于 1,则可能会导致问题。还是不行?
      • @Anton: std::strings 有一个 0 终止符,它不是长度的一部分。
      【解决方案4】:

      我建议您为每个特定字符串创建一个函数。例如,如果你收到一个字符串“create”,你会调用函数doCreate(),如果你收到一个字符串“login”,那么你会调用函数doLogin()

      对这些函数的唯一限制是它们都必须具有相同的签名。在上面的示例中,它是这样的:

      typedef void (*func_t) ();
      

      这个想法是创建一个从字符串到这些函数的 std::map。因此,您不必写 10 个 if 左右,因为您将能够通过特定字符串名称的名称从映射中简单地选择正确的函数。让我通过一个小例子来解释一下:

      typedef void (*func_t) ();
      void doCreate()
      {
           std::cout << "Create function called!\n";
      }
      
      void doLogin()
      {
           std::cout << "Login function called!\n";
      }
      
      std::map<std::string, func_t> functionMap;
      
      void initMap()
      {
          functionMap["create"] = doCreate;
          functionMap["login"] = doLogin;
      }
      
      int main()
      {
           initMap();
           std::string str = "login";
           functionMap[str](); // will call doLogin()
      
           str = "create";
           functionMap[str](); // will call doCreate()
      
           std::string userStr;
           // let's now assume that we also can receive a string not from our set of functions
           std::cin >> userStr;
           if (functionMap.count(userStr))
           {
                functionMap[str](); // now we call doCreate() or doLogin()
           }
           else
           {
                std::cout << "Unknown command\n";
           }
      
           return 0;
       }
      

      希望对你有所帮助=)

      【讨论】:

      • @LightnessRacesinOrbit:麻烦的是,我们在默认情况下发生了崩溃。这可能是不想要的,因为数据是用户提供的,没有经过预先审查。我也怀疑它是否更有效(OP 希望)。
      • @Deduplicator:我认为默认情况并不是一个真正的大问题,可以通过添加一个小检查来解决。关于效率......实际上我可能不正确理解 OP,但在我看来,这更多是关于可伸缩性和可读性的问题,而不是关于效率的问题。反正真的要这么贵吗?
      • 如果效率是一个主要问题,您可以使用 unordered_map
      • @SteveLorimer:它真的比 std::map 快吗?我的意思是理论上——当然是的。但是如果字典的大小小于一百……实际上我同意 Deduplicator 并且相信他的回答提供了最有效的解决方案。
      • @Deduplicator 可能是最有效的,严格来说,但可读性和维护成本很高。
      【解决方案5】:

      您可以使用地图为您进行比较。

      类似这样的:

      初始化地图:

      std::map<std::string, std::function<void(std::string&)>> map;
      map["login"]  = std::bind(&Class::DoLogin,  this, std::placeholders::_1);
      map["create"] = std::bind(&Class::DoCreate, this, std::placeholders::_1);
      

      接收消息:

      map.at(rx.msg_type)(rx.msg_data);
      

      处理程序:

      void Class::DoLogin(const std::string& data)
      {
         // do login
      }
      

      【讨论】:

      • ITT std::placesholders
      • 哈哈,是的,他们占据了位置!呃,错字:)
      【解决方案6】:

      也许您可以创建一个std::map&lt;std::string, int&gt; 并使用地图查找来获取传递的命令的代码 - 您可以稍后打开该号码。或者创建一个enum Command 并拥有一个std::map&lt;std::string, Command&gt; 并使用开关。

      例子:

      enum Command
      {
           CREATE,
           LOGIN,
           ...
      };
      
      std::map<std::string, Command> commandNameToCode;
      // fill the map with appropriate values
      commandNameToCode["create"] = Command::CREATE;
      
      // somehow get command name from user and store in the below variable (not shown)    
      std::string input;
      
      // check if the command is in the map and if so, act accordingly
      if(commandNameToCode.find(input) != commandNameToCode.end())
      {
          switch(commandNameToCode[input])
          {
          case CREATE:
               // handle create
               break;
          ...
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2016-02-28
        • 2016-12-08
        • 2021-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-03
        相关资源
        最近更新 更多