【问题标题】:what is the correct way to handle multiple input command differently in c++?在 C++ 中以不同方式处理多个输入命令的正确方法是什么?
【发布时间】:2016-10-28 03:32:58
【问题描述】:

我有一个程序从用户那里获取命令,它会以不同的方式处理不同的命令。 例如:

ADD_STUDENT ALEX 5.11 175
ADD_TEACHER MERY 5.4  120 70000
PRINT MERY 
REMOVE ALEX
PRINT TEACHER SALARY
PRINTALL 

因此,我需要检查每一行并查看输入的内容。

这是我的代码,但我想我误解了 iss

string line;
while(getline(cin, line))
{
  //some initialization of string, float variable
  std::istringstream iss(line);
  if(iss >> command >> name >> height >> weight)
   ..examine the command is correct(ADD_STUDENT) and then do something..
  else if(iss >> command >> name >> height >> weight >> salary)
   ..examine the command is correct(ADD_TEACHER) and then do something...
  else if(iss >> command >> name)
   ..examine the command is correct(REMOVE) and then do somethin...
}

我的想法是,如果所有参数都已填充,则 iss>> first >>second >>third 将返回 true 如果没有足够的论据,则为假。但显然我错了。

【问题讨论】:

    标签: c++


    【解决方案1】:

    你的问题被很好地低估了。这总是提示我使用 Boost Spirit 提供一个过分夸大的示例实现。

    注意:请不要将此作为你的家庭作业。

    使用以下示例输入查看它Live on Coliru

    ADD_STUDENT ALEX 5.11 175
    ADD_STUDENT PUFF 6 7
    ADD_STUDENT MAGIC 7 8
    ADD_STUDENT DRAGON 8 9
    ADD_TEACHER MERY 5.4  120 70000
    PRINT MERY 
    ADD_TEACHER DUPLO 5.4  120 140000
    PRINTALL  10
    REMOVE ALEX
    PRINT  TEACHER SALARY
    PRINT  MERY PUFF MAGIC DRAGON
    REMOVE MERY PUFF MAGIC DRAGON
    PRINT  TEACHER SALARY
    

    完整代码:


    更新当包含make_visitor.hpp,如图所示here,您可以更优雅地编写访问者代码:

    auto print_salary = [&] () 
    { 
        for(auto& p : names) 
            boost::apply_visitor(make_visitor(
                        [](Teacher const& v) { std::cout << "Teacher salary: " << v.salary << "\n"; },
                        [](Student const& v) {}), 
                    p.second);
    };
    

    查看改编示例Live on Coliru


    #define BOOST_SPIRIT_USE_PHOENIX_V3
    #include <boost/spirit/include/qi.hpp>
    #include <boost/spirit/include/phoenix.hpp>
    
    namespace qi = boost::spirit::qi;
    namespace phx= boost::phoenix;
    
    struct Person
    {
        std::string name;
        double height, weight;
        friend std::ostream& operator<<(std::ostream& os, Person const& s) {
            return os << "Person { name:" << s.name << ", height:" << s.height << ", weight:" << s.weight << " }";
        }
    };
    
    struct Student : Person
    {
        Student() = default;
        Student(std::string n, double h, double w) : Person {n,h,w} {}
    };
    
    struct Teacher : Person
    {
        Teacher() = default;
        Teacher(std::string n, double h, double w, double s) : Person {n,h,w}, salary(s) {}
        double salary;
    };
    
    int main()
    {
        std::stringstream ss;
        ss << std::cin.rdbuf();
    
        std::map<std::string, boost::variant<Student, Teacher> > names;
    
        using namespace qi;
        auto add_student  = phx::ref(names)[_1] = phx::construct<Student>(_1, _2, _3);
        auto add_teacher  = phx::ref(names)[_1] = phx::construct<Teacher>(_1, _2, _3, _4);
        auto remove       = phx::erase(phx::ref(names), _1);
        auto print_all    = [&] (int i) { for(auto& p : names) { std::cout << p.second << "\n"; if (--i==0) break; } };
        auto print_salary = [&] () 
        { 
            struct _ : boost::static_visitor<> {
                void operator()(Teacher const& v) const { std::cout << "Teacher salary: " << v.salary << "\n"; }
                void operator()(Student const& v) const { }
            } v_;
            for(auto& p : names) boost::apply_visitor(v_, p.second);
        };
    
        auto name_ = as_string[lexeme[+graph]];
    
        if (phrase_parse(begin(ss.str()), end(ss.str()), 
                    (
                         ("ADD_STUDENT" >> name_ >> double_ >> double_)            [ add_student ]
                       | ("ADD_TEACHER" >> name_ >> double_ >> double_ >> double_) [ add_teacher ]
                       | (eps >> "PRINT" >> "TEACHER" >> "SALARY")                 [ print_salary ]
                       | ("PRINTALL" >> int_)      [ phx::bind(print_all, _1) ]
                       | ("PRINT"  >> +name_       [ std::cout << phx::ref(names)[_1] << std::endl ])
                       | ("REMOVE" >> +name_       [ remove ])
                    ) % +eol,
                    qi::blank))
        {
            std::cout << "Success";
        }
        else
        {
            std::cout << "Parse failure";
        }
    }
    

    输出:

    Person { name:MERY, height:5.4, weight:120 }
    Person { name:ALEX, height:5.11, weight:175 }
    Person { name:DRAGON, height:8, weight:9 }
    Person { name:DUPLO, height:5.4, weight:120 }
    Person { name:MAGIC, height:7, weight:8 }
    Person { name:MERY, height:5.4, weight:120 }
    Person { name:PUFF, height:6, weight:7 }
    Teacher salary: 140000
    Teacher salary: 70000
    Person { name:MERY, height:5.4, weight:120 }
    Person { name:PUFF, height:6, weight:7 }
    Person { name:MAGIC, height:7, weight:8 }
    Person { name:DRAGON, height:8, weight:9 }
    Teacher salary: 140000
    Success
    

    【讨论】:

    • @kbok 谢谢。尽管我开玩笑地打算使用此代码。这当然是最完整的答案。一英里。或七。 :)
    【解决方案2】:

    这样做:

    iss >> command;
    if (!iss)
        cout << "error: can not read command\n";
    else if (command == "ADD_STUDENT")  
        iss >> name >> height >> weight;
    else if (command == "ADD_TEACHER")  
        iss >> name >> height >> weight >> salary;
    else if ...
    

    【讨论】:

    • == 运算符是非法的
    • std::string 不违法。第二个参数(c-string)被转换为std::string
    • @user1701840 如果commandstd::string,那么它是合法的。
    • @LeonidVolnitsky 我不认为 c-string 被转换了,操作符只是有一个重载bool operator== (std::string lhs, const char* rhs)
    • 通常,当!iss 触发时,它是文件结尾。确保它不会陷入无限循环。
    【解决方案3】:

    您的问题是使用 &gt;&gt; 运算符从流中读取并清除令牌。

    if(iss >> command >> name >> height >> weight)
    

    这(上)尝试从流中读取 4 个令牌,并且对于每次成功读取,它都会从流中清除读取的数据。

    else if(iss >> command >> name >> height >> weight >> salary)
    

    当您看到这个(上图)时,这意味着无法读取某些令牌并将其转换为适当的类型,但很可能至少命令令牌已从流中剥离。

    【讨论】:

      【解决方案4】:

      好吧,现在没有太多投票的机会已经太晚了,但是你们让我想到了这个……

      为了稳健性,您可以将解析分为两个阶段:第一阶段获取行,第二阶段获取行并对其进行处理。

      对于第一阶段,您可以使用getline

      #include <string>
      #include <sstream>
      
      void ParseLines(std::istream& source)
      {
          while(source)
          {
              // Get a line from the source.
              std::string inputLine;
              std::getline(source, inputLine);
      
              // Make a stream out of it.
              std::istringstream inputStream(inputLine);
              std::string command;
              inputStream >> command;
              if(inputStream) // Empty or bad line: skip
                  HandleCommand(command, inputStream);
          }
      }
      

      第二阶段处理命令。可能是这样直接的:

      void HandleCommand(const std::string& command, std::istringstream& params)
      {
          if(command == "ADD_STUDENT")
          {
              float someFloat;
              int someInt;
              params >> someFloat >> someInt;
              // add the student.
          }
          // etc.
      }
      

      但我并不羞耻,我会实现一个Factory范式:

      #include <map>
      
      typedef void (*CommandHandler)(const std::string&, std::istringstream&);
      typedef std::map<std::string, CommandHandler> CommandTable;
      
      static CommandTable gCommands; // Yep. A global. Refactor however you see fit.
      
      void HandleCommand(const std::string& command, std::istringstream& params)
      {
          CommandTable::iterator handler = gCommands.find(command);
          if(handler == gCommands.end())
          {
              // Handle "command not found" error.
              return;
          }
      
          (*(handler->second))(command, params);
      }
      
      void AddStudent(const std::string& command, std::istringstream& params)
      {
          float someFloat;
          int someInt;
          params >> someFloat >> someInt;
          // add the student.
      }
      
      // Other command handling functions here...
      
      void RegisterCommands()
      // Call this once prior to parsing anything,
      // usually one of the first things in main().
      {
          gCommands["ADD_STUDENT"] = &AddStudent;
          // ... other commands follow...
      )
      

      还没有测试过这些,但它应该大部分都在那里。注意 cmets 中的任何错误。

      附:这是非常低效,并且会比设计合理的命令解析器运行得更慢,但是对于大多数工作来说应该足够了。

      【讨论】:

      • 看起来比把所有东西都扔进主目录是个好主意!谢谢!
      • 嗡嗡声。正如您的回答所示,我的回答很重 almost exactly the same number of LoC。只是,我的不做捷径:它实际上实现了命令和类型、存储和查找:/ 哦,我更晚才发布它。 (我很喜欢像你这样的任务,显然:))
      • 只是没有取悦一些人。我使用函数指针来保持答案简单,并且我故意限制在 C++03 左右,因为我不知道这家伙的编译器有多老。就像我说的,以任何你喜欢的方式重构,因为正确的重构取决于其余代码的组织方式,而这些都没有发布。 C++11 提供了很多更好的选择。另外,我是故意避免 Boost Spirit,以免混淆可怜的OP的思想;他们只是想使用istreamoperator &gt;&gt;。顺便说一句,尽管如此,还是感谢您的反对。
      • 什么恶意?你的回答很糟糕。 OP 没有指定 C++03,即使他指定了,也有 tr1::function 和 boost::function。至于全局变量,使用全局变量发布答案是不好的。您至少可以带一个参数或其他东西。
      【解决方案5】:

      您可以在技术上标记整个输入行,但这似乎离您的水平有点太远了。如果您确实想进入它,有一个不错的页面和教程here 将帮助您使用 strtok()。

      如果您不想采用该方法,您可以单独解析您的命令列表。假设您已读入一个名为“command”的字符串。

      if (command == "ADD_STUDENT")
      {
          int weight, height, otherfield;
          cout << ">" << flush;
          cin >> weight >> height >> otherfield;
          //do something, like add them to the database
      }
      

      这似乎是您最好的选择,虽然它需要大量编码,但您可能更容易完成。你可以真正进入它并使用这样的格式字符串:

      scanf("%s, %s %d, %f", lastname, firstname, age, height);
      

      这样,输入将如下所示:

      ADD_STUDENT Doe, John 30, 5.6
      

      【讨论】:

      • 您无需求助于scanfstrtok 来标记行。见stackoverflow.com/questions/53849/…
      • @Mike 你使用什么类型的字符串真的很重要。我从不使用 std::string,所以 .split() 对我不起作用。
      猜你喜欢
      • 2016-09-13
      • 2016-06-03
      • 1970-01-01
      • 2011-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-19
      • 1970-01-01
      相关资源
      最近更新 更多