【问题标题】:How to Parse Input Commands Having a Simple Syntax?如何解析具有简单语法的输入命令?
【发布时间】:2018-02-09 02:43:43
【问题描述】:

我正在开发一个程序,该程序应该接受用户的指令并用它做一些事情。它使用类和对象来知道要做什么。但是,有些命令只要求用户输入 2 个东西,而其他命令则需要输入 3、4 个等。例如:

request> balance 2

这个调用是检索客户 2 的余额。但是这个调用:

request> deposit 2 30

这应该是向客户 2 的账户存入 30 美元。

如何让程序了解用户可以输入 2 个由空格分隔的变量,或者更多?

我曾想过也许将整个用户输入作为一个字符串,然后解析可能会更容易......

char temp[257];
scanf("%s", temp);

但是,如果您调试并输出这个所谓的字符串,它只会输出用户输入的最后一项。所以如果用户输入了

request> balance 3

它只抓取 3 并将其存储到 temp...

我想你们明白了,我需要能够为终端提供多个字符串、整数等,并将每个单独的字符串存储到自己的变量中。任何帮助将不胜感激!

一些伪代码供参考:

#include <iostream>
#include <stdio.h>
#include <cstdlib.h>

including namespace std;

int main()
{
    char temp[257];
    int x, y, z;

    printf("request: ");
    scanf("%s", temp);     // I need this to be able to scan more than one 
                           // thing, and know where to store them
    scanf("%s %d %d", temp, x, y);
    // because if I do this ^^, then it will just hang if I only need x and 
    // not y.

    // Do some random stuff w/ variables now

    return 0;

}

【问题讨论】:

  • 现在读完了,也许吧,但我的眼睛看起来更安抚了:D
  • 您的第一个问题是您使用了错误的语言。这不是 C++,而是 C。在 C 中,如果您想读取整行文本作为输入,则该函数称为 fgets()。您将把整行文本读入一个大小合适的char 数组(当然是检查溢出),一旦整行文本被读取,解析读取的行以获取有效命令。尝试使用scanf() 吞下输入,一次一个令牌,这只是一种挫败感。正如你已经发现的那样。它可以工作,但非常困难并且充满了雷区。只需使用fgets
  • 我正在读取 fgets() 语法作为想要从流中读取,我将如何将用户输入转换为流或将其用作流?非常感谢。
  • 一般需要先读行,tokenize,然后对第一个token进行分类。从那里您应该知道有效命令需要多少其他令牌以及它们各自代表什么,假设它们是位置的。

标签: c++ parsing


【解决方案1】:

虽然您有一个可靠的答案,但您是否想要一个 C++ 或 C 解决方案开始仍然有点模棱两可。为了完整起见,您可以使用fgets(您的流是stdin)然后调用sscanf 来解析用户输入的不同数量的项目,在返回时使用switch 来处理不同的案例。

一个允许 1-input 显示余额和 2-input 更新余额的简短示例可以如下完成。程序在第一次 匹配失败 时退出(因此您只需键入 'q' 即可退出)。 输入失败会被忽略。

#include <stdio.h>

enum { NMMAX = 16, BUFSZ = 256 };  /* if you need constants, define them */

typedef struct {    /* simple struct for user name and balance (integer) */
    char name[NMMAX];
    long balance;
} account_t;

int main (void) {

    account_t accounts[] = {{"John", 12305},
                            {"Paul", 388541},
                            {"George", -38112},
                            {"Ringo", 1}};
    int naccts = sizeof accounts / sizeof *accounts;

    for (;;) {     /* loop continually until matching failure or user cancels */
        char buf[BUFSZ] = "";
        int acct = 0,
            rtn = 0;
        double bal = 0;
        fputs ("request> ", stdout);        /* display prompt */
        if (!fgets (buf, BUFSZ, stdin)) {   /* read input, check EOF */
            fprintf (stderr, "error: user canceled input (ctrl+d)\n");
            return 1;
        }
        /* separate inputs into acct and bal, rtn holds number of inputs */
        rtn = sscanf (buf, " %d %lf", &acct, &bal);
        if (rtn && acct > naccts) {  /* check if requested account valid */
            fprintf (stderr, "error: invalid account.\n");
            continue;
        }
        switch (rtn) {  /* switch on sscanf return */
            case 0  :   /* use matching falure to exit */
                goto finished;
                break;
            case 2  :   /* 2 inputs - update balance */
                accounts[acct].balance += (long)(bal * 100);
            case 1  :   /* 1 input - show balance */
                printf ("%-15s : $%.2f\n", accounts[acct].name, 
                        accounts[acct].balance / 100.0);
                break;
        }
    }
    finished:;

    return 0;
}

注意: case 2 : 掉线是故意的。)

使用/输出示例

$ ./bin/acctbal
request> 0
John            : $123.05
request> 0 -1.85
John            : $121.20
request> 1
Paul            : $3885.41
request> 1 .59
Paul            : $3886.00
request> 5
error: invalid account.
request>
request> q

祝你最终用哪种语言写的好运。

【讨论】:

  • 永远不要把旧的卑微goto排除在外。非常适合近距离跳跃,也是一次打破 2 循环(或范围)的唯一方法(而不是退出)。这个最终解决了这个问题,比我没有想到的更聪明。巧合的是,我们会说...
  • 这只是一个有趣的元素......是的,你处理得很好。我不计较。 :)
【解决方案2】:

您的示例代码看起来很像 C,除了 using namespace std; 您也不应该这样做,但这是另一个主题。这是使用 C++ 结构处理用户输入(一次全部输入)的快速而肮脏的方法。

#include <iostream>
#include <sstream>

int main()
{
    std::string line;              ///< hold user input as string
    std::getline(std::cin, line);  ///< get the whole line at once
    std::stringstream ss(line);    ///< stream for processing line arguments

    /*std::string args;
    while (ss >> args)
    {
        // decide what to do with each input argument here
        std::cout << "args= "<< args << std::endl;
    }*/
    // if you "know" your input arguments you don't need a loop
    std::string command;
    int val1(-1);
    int val2(-1);
    ss >> command >> val1 >> val2;

    std::cout << "command = " << command << " val1="
    << val1 << " val2=" << val2 << std::endl;

    return 0;
}

【讨论】:

  • 感谢您的反馈。我将修复 C 的语法。我最终可能会使用它,但是有没有办法在不包含新库的情况下做到这一点?这是一个课堂作业,我不确定我们是否可以使用我们没有学习过的库。
  • @lookingforlemons 如果你在谈论 std::stringstream 是的,你可以不用它,但是 std::string 是 C++ 对 char* 的回答,它是整个 OOP 事物的基础。
  • 我实际上指的是 sstream... 因为我同意它的基本原理,我们的教授坚持认为我们现在了解如何使用 *char 来处理字符串
  • 所以如果在while循环中,字符串读取'deposit 3 40',我怎么能说string1 = 'deposit',int1 = 3,和int2 = 40?
  • 如果你使用 C++11 或更高版本,则字符串为整数 int foo = std::stoi(args) 否则你可以使用 std::stringstream 但由于你还没有知道你可以使用 C 的老朋友 @ 987654328@
猜你喜欢
  • 1970-01-01
  • 2017-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-26
  • 2015-04-22
相关资源
最近更新 更多