在C ++中过滤来自istream的非法输入

时间:2015-02-17 04:19:21

标签: c++ c operator-overloading istream

我正在编写一个重载运算符的函数>>对于C ++中的Fraction类。标题是这样的:friend istream& operator>>(istream&, Fraction&);我一直难以满足我为检测非法输入而设定的所有要求。

这就是我想要实现的目标:

  1. 如果用户输入(int)(enter_key),该函数应将分子设置为int,分母设置为1并返回。
  2. 如果用户输入(int1)('/')(int2)(enter_key),则将分子设置为int1,将分母设置为int2,然后返回。
  3. 任何不符合前两种形式的输入都会引发异常。
  4. 该函数的调用如下:

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

    我尝试了很多东西,这是我的代码的一个版本。 FractionException已被处理:

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

    我已尝试将input.sync()替换为input.clear()input.ignore(streamsize, delim),但无效。

    我在考虑input.peek(),但是整数可以超过一位数。

    我尝试将C字符串与input.getline(char*, streamsize)一起使用并循环查找字符串以找到'/',但程序崩溃了。代码如下所示:

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

    此外,使用我现在拥有的程序,输入流有时会在缓冲区中留下字符,并导致无限循环。

    我真的很困惑我正在做的事情,因为似乎没什么用,我的代码很粗略。任何帮助或提示将不胜感激。

1 个答案:

答案 0 :(得分:1)

您对C ++的责任分工是不正常的 - 您通常希望operator>>设置失败状态(在调用设置此类状态的流操作时隐式显示,或者明确地使用.setstate),然后调用者应该控制他们是否希望让流引发异常。这是一个例子:

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

输出:

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

在ideone.com上看到它运行here

is >> x.a_ && is.get(c)中,第一个使用>>流式传输,它会跳过前导空格,但get()会被用来潜在地读取换行符:这意味着例如"10 / 2"" 39 "不被视为有效输入:如果您想支持此类内部和/或尾随空格,请考虑:

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