开发者

validating numerical user input

I am creating a simple CLI calculator tool as an exercise. I need to make sure n1 and n2 are numeric in order for the functions to work; consequently, I would like to make the program quit upon coming across a predetermined non-numeric value.

Can anyone give me some direction?

Additionally, if anyone can offer any general tips as to how I could have done this better, I would appreciate it. I'm just learning c++.

Thank you!

The complete code is included below.

#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)开发者_如何学Go 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);
}


What you want to do is read a line of input, or a string, then attempt to convert that line to your numeric form. Boost wraps this in lexical_cast, but you don't need that at all. I've answered a question similar to yours twice, here and here. Read those posts to understand what's going on.

Here's the final result:

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;
}

Use it how I outlined in those posts:

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

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

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

This uses exceptions. You can make this no-throw like outlined in the links above, by catching the bad_cast exception:

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;
    }   
}

This is good for making Boost's lexical_cast no-throw, but if you're implementing it yourself, there's no reason to waste time throwing and catching an exception. Implement them in terms of each other, where the throwing version uses the no-throw version:

// 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;
}

There is more trouble in your code: you're newing everything! Is there a reason for that? Consider if any part of your code throws an exception: now you jump out of main and leak everything. If you stack allocate your variables, they will be guaranteed to destruct.


No need for Boost or writing your own template or forcing yourself to use exceptions vs error codes. cin alone does everything you're asking for.

You can test if ( cin ) or if ( ! cin ) to determine success or failure. One failure (eg, a letter in numeric input) will stop cin from accepting any more input. Then call cin.clear() to clear the error and resume getting input, starting with whatever text caused the error. Also, you can request that a stream throw exceptions on conversion errors: cin.exceptions( ios::failbit ).

So, you can do this:

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
}

This is meant as a demonstration of error handling with iostreams. You can choose to use exceptions or manual testing or both.


You can use input stream object itself to perform simple validation:

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

Another interesting approach could be to construct a parser with Boost.Spirit library, though it is an advanced technique heavily exploiting C++ metaprogramming features. If you'd like to try it, check the quick start examples


Probably a lot of C++ guys will hate me for this, but even while C++ has all these new shiny strings and I try to stay with C++ strings as long as possible to feel clean, in this case the simplest and considerably also cleanest thing is to stick with good ol' C:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜