简体   繁体   中英

Filtering Illegal Inputs from istream in C++

I'm writing a function that overloads the operator >> for a Fraction class in C++. The heading is like this: friend istream& operator>>(istream&, Fraction&); I've been having difficulties to fulfill all the requirement I set for detecting illegal inputs.

Here's what I want to achieve:

  1. If the user enters (int)(enter_key), the function should set the numerator to int and denominator to 1 and return.
  2. If the user enters(int1)('/')(int2)(enter_key), set the numerator to be int1 and denominator to be int2, and then return.
  3. Any forms of input that doesn't fit the first two will throw an exception.

The function is called like this:

Fraction fin;
do {
    cout << "Enter a fraction here: ";
    try {
        cin >> fin;
        sum += fin;
    } catch (FractionException &err) {
        cout << err.what() << endl;
    }
} while (fin != 0);

I have tried many things, and here's one version of my code. The FractionException has been taken care of:

istream& operator>>(istream& input, Fraction& frac){
    int num, den;
    char slash = '\0';
    //_________________________________
    if(!(input >> num)){
        input.sync();
        throw FractionException("Please enter numbers only.");
    }
    frac.numer = num;
    //_________________________________
    if(!(input >> slash)){
        input.sync();
        throw FractionException("Please enter slash.");
    } else if(slash == '\0'){ //useless
        frac.denom = 1;
        return input;
    } else if(slash != '/')
        throw FractionException("Illegal character.");

    //_________________________________
    if(!(input >> den)){
        input.sync();
        throw FractionException("Please enter numbers only.");
    } else if(den == 0) {        
            throw FractionException("The denominator is 0; illegal entry.");
    } else
        frac.denom = den;

    return input;
}

I've tried to replace input.sync() with input.clear() and input.ignore(streamsize, delim) , but didn't work.

I am thinking of input.peek(), but and integer can be more than one digit long.

I tried to use C string with input.getline(char*, streamsize) and loop through the string to find '/', but the program crashes. The code looks like this:

int inputSize, slashIndex;
int num, den;
char* line;
char* numStr;
char* denStr;
bool foundSlash(false);

input.getline(line, 1000);
inputSize = strlen(line);
for(int i = 0; i < inputSize; i++) {
    if(!isdigit(line[i])) {
        if(line[i] == '/'){
            slashIndex = i;
            foundSlash = true;
            goto checkDen;
        } else throw FractionException("Non-slash character is entered");
    }
}

checkDen:
if(foundSlash){
    for(int i = slashIndex + 1; i < inputSize; i++)
        if(!isdigit(line[i]))
            throw FractionException("Denominator contains non-numbers");
    strncpy(numStr, line, slashIndex - 1);
    frac.numer = atoi(numStr);
    denStr = /*substring from slashIndex + 1 to inputSize*/;
            //The strncpy function only copies from index 0
    frac.denom = atoi(denStr);
} else {
    frac.numer = atoi(line);
    frac.denom = 1;
}

Also, with the program I have right now, the input stream sometimes have left over characters in the buffer, and it causes an infinite loop.

I'm just really confused with what I'm doing because nothing seems to work, and my code is sketchy. Any help or hints will be appreciated.

Your division of responsibility is not normal for C++ - you generally want operator>> to set a failure state (either implicitly when calling streaming operations that set such state, or explicitly with .setstate ), then the caller should control whether they prefer to have the stream raise exceptions. Here's an example:

#include <iostream>
#include <sstream>
#include <limits>
#include <cassert>

struct X { int a_, b_; };

std::istream& operator>>(std::istream& is, X& x)
{
    char c;
    if (is >> x.a_ && is.get(c))
        if (c == '\n')
            x.b_ = 1;
        else if (!(c == '/' && is >> x.b_))
            is.setstate(std::istream::failbit);
    return is;
}

int main()
{
    std::istringstream iss("5\n10/2\n3xyz\n8/17\n9\nNOWAY\n42/4\n");
    X x;
    while (iss >> x)
        std::cout << "parsed " << x.a_ << '/' << x.b_ << '\n';
    iss.clear();

    std::string remnants;
    assert(getline(iss, remnants));
    std::cout << "parsing failed, line remnants for '" << remnants << "'\n";
    // normally would prefer following to getline above...
    // iss.ignore(std::numeric_limits<std::streamsize>::max(), '\n');

    iss.exceptions(std::istream::failbit);
    while (iss)
        try
        {
            while (iss >> x)
                std::cout << "also parsed " << x.a_ << '/' << x.b_ << '\n';
        }
        catch (const std::exception& e)
        {
            std::cout << "caught exception " << e.what() << '\n';
            if (!iss.eof())
            {
                iss.clear();
                iss.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            }
        }
    std::cout << "eof " << iss.eof() << ", bad " << iss.bad()
         << ", fail " << iss.fail() << '\n';
}

Output:

parsed 5/1
parsed 10/2
parsing failed, line remnants for 'yz'
also parsed 8/17
also parsed 9/1
caught exception basic_ios::clear
also parsed 42/4
caught exception basic_ios::clear
eof 1, bad 0, fail 1

See it run here on ideone.com.

In is >> x.a_ && is.get(c) , the first uses >> streaming which will skip leading whitespace, but get() has so be used to potentially read a newline: that means eg "10 / 2" , " 39 " aren't considered valid input: if you want to support such internal and/or trailing whitespace, consider:

std::istream& operator>>(std::istream& is, X& x)
{
    std::string line;
    if (getline(is, line))
    {
        std::istringstream iss(line);
        char c;
        x.b_ = 1; // default
        if (!(iss >> x.a_) ||
            (iss >> c) && (!(c == '/' && iss >> x.b_)))
            is.setstate(std::istream::failbit);
    }
    return is;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM