【问题标题】:validating numerical user input验证数字用户输入
【发布时间】:2010-02-06 01:47:52
【问题描述】:

我正在创建一个简单的 CLI 计算器工具作为练习。我需要确保 n1 和 n2 是数字才能使函数正常工作;因此,我想让程序在遇到预定的非数字值时退出。

谁能给我一些指导?

此外,如果有人能提供任何关于我如何做得更好的一般性提示,我将不胜感激。我只是在学习c++。

谢谢!

完整的代码如下。

#include <iostream>
#include <new>

using namespace std;

double factorial(double n) { return(n <= 1) ? 1 : n * factorial(n - 1); }

double add(double n1, double n2) { return(n1 + n2); }

double subtract(double n1, double n2) { return(n1 - n2); }

double multiply(double n1, double n2) { return(n1 * n2); }

double divide(double n1, double n2) { return(n1 / n2); }

int modulo(int n1, int n2) { return(n1 % n2); }

double power(double n1, double n2) {
    double n = n1;
    for(int i = 1 ; i < n2 ; i++) {
        n *= n1;
    }
    return(n);
}

void print_problem(double n1, double n2, char operatr) {
    cout<<n1<<flush;
    if(operatr != '!') {
        cout<<" "<<operatr<<" "<<n2<<flush;
    } else {
        cout<<operatr<<flush;
    }
    cout<<" = "<<flush;
}

int main(void) {

double* n1, * n2, * result = NULL;
char* operatr = NULL;

n1 = new (nothrow) double;
n2 = new (nothrow) double;
result = new (nothrow) double;
operatr = new (nothrow) char;

if(n1 == NULL || n2 == NULL || operatr == NULL || result == NULL) {
    cerr<<"\nMemory allocation failure.\n"<<endl;
} else {

    cout<<"\nTo use this calculator, type an expression\n\tex: 3*7 or 7! or \nThen press the return key.\nAvailable operations: (+, -, *, /, %, ^, !)\n"<<endl;

    do {    
        cout<<"calculator>> "<<flush;       
        cin>>*n1;

        cin>>*operatr;

        if(*operatr == '!') {
            print_problem(*n1, *n2, *operatr);
            cout<<factorial(*n1)<<endl;
        } else {

            cin>>*n2;

            switch(*operatr) {
                case '+':
                    print_problem(*n1, *n2, *operatr);
                    cout<<add(*n1, *n2)<<endl;
                    break;
                case '-':
                    print_problem(*n1, *n2, *operatr);
                    cout<<subtract(*n1, *n2)<<endl;
                    break;
                case '*':
                    print_problem(*n1, *n2, *operatr);
                    cout<<multiply(*n1, *n2)<<endl;
                    break;
                case '/':
                    if(*n2 > 0) {
                        print_problem(*n1, *n2, *operatr);
                        cout<<divide(*n1, *n2)<<endl;
                    } else {
                        print_problem(*n1, *n2, *operatr);
                        cout<<" cannot be computed."<<endl;
                    }
                    break;
                case '%':
                    if(*n1 >= 0 && *n2 >= 1) {
                        print_problem(*n1, *n2, *operatr);
                        cout<<modulo(*n1, *n2)<<endl;
                    } else {
                        print_problem(*n1, *n2, *operatr);
                        cout<<" cannot be computed."<<endl;
                    }
                    break;
                case '^':
                    print_problem(*n1, *n2, *operatr);
                    cout<<power(*n1, *n2)<<endl;
                    break;
                default:
                    cout<<"Invalid Operator"<<endl;
            }
        }
    } while(true);
    delete n1, n2, operatr, result;
}
return(0);
}

【问题讨论】:

  • 你是否有理由更新你的变量,而不是仅仅将它们放在堆栈上?您的代码不再安全;它屈服于异常,并且更难阅读。

标签: c++ validation


【解决方案1】:

您要做的是读取一行输入或一个字符串,然后尝试将该行转换为您的数字形式。 Boost 将其包装在 lexical_cast 中,但您根本不需要它。我已经两次回答了与您类似的问题,herehere。阅读这些帖子以了解发生了什么。

这是最终结果:

template <typename T>
T lexical_cast(const std::string& s)
{
    std::stringstream ss(s);

    T result;
    if ((ss >> result).fail() || !(ss >> std::ws).eof())
    {
        throw std::bad_cast();
    }

    return result;
}

按照我在这些帖子中概述的方式使用它:

int main(void)
{
    std::string s;
    std::cin >> s;

    try
    {
        int i = lexical_cast<int>(s);

        /* ... */
    }
    catch(...)
    {
        /* ... */
        // conversion failed
    }
}

这使用了异常。您可以通过捕获bad_cast 异常来实现上述链接中所述的无抛出:

template <typename T>
bool lexical_cast(const std::string& s, T& t)
{
    try
    {
        t = lexical_cast<T>(s);

        return true;
    }
    catch (const std::bad_cast& e)
    {
        return false;
    }
}

int main(void)
{
    std::string s;
    std::cin >> s;

    int i;
    if (!lexical_cast(s, i))
    {
        std::cout << "Bad cast." << std::endl;
    }   
}

这对于使 Boost 的 lexical_cast 不抛出异常非常有用,但如果您自己实现它,则没有理由浪费时间抛出和捕获异常。将它们相互实现,其中投掷版本使用不投掷版本:

// doesn't throw, only returns true or false indicating success
template <typename T>
const bool lexical_cast(const std::string& s, T& result)
{
    std::stringstream ss(s);

    return (ss >> result).fail() || !(ss >> std::ws).eof();
}

// throws 
template <typename T>
T lexical_cast(const std::string& s)
{
    T result;
    if (!lexical_cast(s, result))
    {
        throw std::bad_cast("bad lexical cast");
    }

    return result;
}

你的代码有更多的麻烦:你newing 一切!这有什么原因吗?考虑一下代码的任何部分是否抛出异常:现在您跳出 main 并泄漏所有内容。如果你堆栈分配你的变量,他们将被保证破坏。

【讨论】:

  • 很难一刀切。对于计算器程序,您希望接受像1+1 这样的输入。 cin &gt;&gt; temp_string 让这变得困难。像123nan这样的输入不应该一遇到字母就被拒绝,不要贪早?
【解决方案2】:

无需 Boost 或编写自己的模板或强迫自己使用异常与错误代码。 cin 独自完成您要求的一切。

您可以测试if ( cin )if ( ! cin ) 以确定成功或失败。一次失败(例如,数字输入中的字母)将阻止cin 接受更多输入。然后调用cin.clear() 清除错误并继续获取输入,从导致错误的任何文本开始。此外,您可以请求流在转换错误时引发异常:cin.exceptions( ios::failbit )

所以,你可以这样做:

for (;;) try {
    double lhs, rhs;
    char oper;
    cin.exceptions( 0 ); // handle errors with "if ( ! cin )"
    cin >> lhs >> oper; // attempt to do "the normal thing"
    if ( ! cin ) { // something went wrong, cin is in error mode
        string command; // did user enter command instead of problem?
        cin.clear(); // tell cin it's again OK to return data,
        cin >> command; // get the command,
        if ( command == "quit" ) break; // handle it.
        else cin.setstate( ios::failbit ); // if command was invalid, 
                                           // tell cin to return to error mode
    }
    cin.exceptions( ios::failbit ); // now errors jump directly to "catch"
        // note that enabling exceptions works retroactively
        // if cin was in error mode, the above line jumps immediately to catch
    if ( oper != '!' ) cin >> rhs;
    // do stuff
} catch ( ios::failure & ) {
    cin.clear();
    cin.ignore( INT_MAX, '\n' ); // skip the rest of the line and continue
}

这是作为 iostream 错误处理的演示。您可以选择使用异常或手动测试或两者兼而有之。

【讨论】:

    【解决方案3】:

    您可以使用输入流对象本身来执行简单的验证:

    What is the best way to do input validation in C++ with cin?

    另一种有趣的方法是使用Boost.Spirit 库构建解析器,尽管它是一种高级技术,大​​量利用了 C++ 元编程特性。如果您想尝试一下,请查看quick start 示例

    【讨论】:

      【解决方案4】:

      可能很多 C++ 人会因此而讨厌我,但即使 C++ 有所有这些新的闪亮字符串,我也会尽可能长时间地使用 C++ 字符串以保持干净,在这种情况下是最简单且相当干净的事情就是坚持好的 ol' C:

      if (sscanf(input, "%d", &integer) != 1) {
       // failure to read number
      }
       // happily continue and process
      

      【讨论】:

      • 哈哈,好像有人私信了。对不起,GMan。思想在程序员的网站上是有效的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-11
      相关资源
      最近更新 更多