在C ++中基于文本命令实现用户界面

时间:2013-12-04 22:07:17

标签: c++ string scanf

很长一段时间以来,我一直在努力为我的程序创建输入法。

我希望程序接受以下形式的输入:

function entity(number,text,text)
function entity(number)
function entity(int, text, int)

我希望程序根据用户以上面显示的形式输入的命令进行操作。他们选择了这个功能。添加实体例如学生并在括号中填写有关这些实体的数据。现在我要实现的是将此字符串拆分为add / entity的块和括号中的所有值,以便我可以根据它们进行操作。

到目前为止我设法实现的是完全错误的,因为我对整个c ++事情都相当陌生,所以它确实很难在半个时间内完全解决这个问题

string function, entity;
    char values[1024];
    char command[1024];
    cin.getline(command, 1024);


    stringstream t;
    t << command;
    int  number[5];
    char parameter1[20];
    char parameter2[20];
    t >> funtion >> entity >> values;
    sscanf_s(values, "%s %s %s ", number, _countof(number), parameter1, _countof(parameter1), parameter2, _countof(parameter2));

简单来说,我想从函数实体(参数,参数,参数)剪切字符串/ char(我尝试了两种不同的东西)到我可以在程序中的其他地方使用的小块。需要摆脱括号,逗号并分别取出每个单词。

3 个答案:

答案 0 :(得分:0)

您选择的方法非常适合该问题,特别是如果您以后想要向程序添加更多命令表单。

处理这种输入的正确方法是使用诸如GNU Bison之类的解析器生成器编写解析器,但在您的情况下可能过度,需要太多空间来解释,并且需要一些关于语言的知识和语法(在计算机科学意义上)。如果您想了解更多相关信息,请在Google上搜索“GNU Bison”或“解析器生成器”。

更有限但更容易理解的解决方案是使用C ++ 11正则表达式。首先匹配整行以查找函数,实体和参数列表,然后根据实体或函数类型匹配参数列表。代码看起来像这样:

// This regular expression matches one line of input (one command).
// A line has some text starting with a letter (the function name),
// then one or more spaces, another piece of text,
// and then a string of arbitrary characters inside parentheses.
// Parentheses not preceded by \\ denote capture groups.
// Any characters matched by them are later accessible through
// the std::smatch object.
std::regex rx_line("([A-Za-z][A-Za-z0-9]*)\\s+"
                   "([A-Za-z][A-Za-z0-9]*)\\s*"
                   "\\((.*)\\)\\s*");

// Retrieve one non-empty line of input, skipping whitespace.
std::string line;
std::getline(std::cin >> std::ws, line);

// Match the line regex to the line, capturing the parts of text which we want.
std::smatch line_match;
if (!std::regex_match(line, line_match, rx_line)) {
    // Report error: the command has incorrect format.
}

// Retrieve parts of the input matched by capture groups.
std::string function = line_match[1];
std::string entity = line_match[2];
std::string params = line_match[3];

// Depending on which entity was mentioned in the command,
// match the parameters to a suitable regex.
if (entity == "student") {
    // number, then comma, then text in quotes, then comma,
    // and finally a number.
    std::regex rx_student("([1-9][0-9]*)\\s*,"
                          "\\s*\"([^\"]*)\"\\s*,"
                          "\\s*([1-9][0-9]*)");
    std::smatch student_match;
    if (!std::regex_match(params, student_match, rx_student)) {
        // report error
    }

    // Retrieve parameters from capture groups.
    int int1 = std::atoi(student_match[1].str().c_str());
    std::string text = student_match[2];
    int int2 = std::atoi(student_match[3].str().c_str());

    // ...
    // do something with the data
    // ...
} else if (entity == "account") {
    // and so on
} else {
    // error - unknown entity
}

参见例如http://www.cplusplus.com/reference/regex/ECMAScript/有关正则表达式语法的说明。

答案 1 :(得分:0)

我认为值得从一开始就开始:虽然它有一个固定大小的缓冲区可以读入,但我认为使用std::string来捕获每一行更容易。此外,如果读取操作成功,您应该在读取后始终测试。也就是说,我会编写一个外部循环来处理各个行:

for (std::string line; std::getline(std::cin >> std::ws, line); ) {
    // process each individual line
}

使用操纵器std::ws会使循环内部的东西更容易:它会跳过所有空格,例如,一行上的前导空格和空行。也就是说,如果std::getline()设法读取一行,则已知该行不为空。

根据您撰写的有关您的功能的内容,它们似乎有不同的参数。所以我会创建一个字符串流来处理单个行,将第一个单词作为命令读取,并根据该分割读取更多参数:

std::istringstream sin(line);
std::string        command;
if (sin >> command) {
    if (command == "add") {
        int         number;
        std::string name;
        if (sin >> number >> name) {
            entity(number, name);
        }
        else {
            std::cout << "ERROR: failed to read add command from '" << line << "'\n";
        }
    else if (command == "???") {
        // ...
    }
    else {
        std::cout << "ERROR: unrecognized command on line '" << line << "'\n";
    } 
}

有可能编写一个函数来确定传递给它的函数指针的参数,读取它们并调用函数,但这需要一些高级技术。基于您的问题,提供答案将为您提供一个可能有点过于复杂的解决方案。

答案 2 :(得分:0)

您尝试实现的是解析,而不仅仅是原始拆分。 以下是使用boost::spirit将对您有用的示例。我强烈建议你使用这些工具,因为它可以让你有一点灵活性,因为scanf的原始文件解析不能提供给你。这不是唯一可用的工具,但值得花时间学习它。

所以这里是我的小评论示例,我希望它对您有所帮助:

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/qi_as.hpp>
#include <boost/variant.hpp>
#include <iostream>
#include <string>
#include <vector>
#include <iterator>

// Parameters can be either string or int
typedef boost::variant<std::string, double> Parameter;

// Here is the function where we read the command and retrieve entity/function
// and parameters
bool read_command(const std::string& str)
{
    // Just to ease the parser writing
    namespace qi = boost::spirit::qi;
    namespace ascii = boost::spirit::ascii;
    namespace phoenix = boost::phoenix;

    typedef qi::rule<std::string::const_iterator, std::string()> rule;

    // Variables where we will store the results
    std::string entity;
    std::string function;    
    std::vector<Parameter> arguments;

    // An identifier 
    rule identifier = qi::alpha >> *(qi::alnum | qi::char_("_"));

    // A parameter is either a number or an identifier, there is a issue if we
    // use "rule" type here, double are not read correctly anymore...
    auto parameter = qi::double_ | identifier;

    // The whole command  
    bool r = qi::phrase_parse(str.begin(), str.end(),
        ( identifier >> identifier >> '(' >> (parameter % ',' ) >> ')' ), 
        qi::space, entity, function, arguments); // Use qi "magic" to store result

    // Print everything
    std::cout 
    << "Parsing result:" << std::endl
    << " - Entity: " << entity << std::endl
    << " - Function: " << function << std::endl
    << " - " << arguments.size() << " parameter(s): ";

    // Use copy to print all the arguments
    std::copy(arguments.begin(), arguments.end(),
              std::ostream_iterator<Parameter>(std::cout, ", "));
    std::cout << std::endl;
    return r;
}

int main()
{
    std::string str;

    // Read the line   
    while (getline(std::cin, str))
    {
        // Check that this is a line we do want to parse
        if (str.empty() || str[0] == 'q' || str[0] == 'Q')
        {
            break;
        }

        // Read the command and output the result
        if (read_command(str))
        {
            std::cout << "Parsing succeeded! " << std::endl;
        }
        else
        {
            std::cout << "Parsing failed." << std::endl;
        }
    }

    return 0;
}

输出示例:

$ ./a.exe
test f(1)
Parsing result:
 - Entity: test
 - Function: f
 - 1 parameter(s): 1,
Parsing succeeded!
entity function(1, a, b)
Parsing result:
 - Entity: entity
 - Function: function
 - 3 parameter(s): 1, a, b,
Parsing succeeded!
er1_ ft_(1, r_)
Parsing result:
 - Entity: er1_
 - Function: ft_
 - 2 parameter(s): 1, r_,
Parsing succeeded!