tags:

views:

151

answers:

3

I have a C++ assignment to complete over the next couple of weeks. The task is to

  1. read a line of input containing 3 scores between 0 and 89, and read a name from the same line.
  2. Convert those scores to marks and give each student a grade depending on their marks.

My first cut has done all of this (albeit in a messy way) and now I am thinking about invalid input. The input I am really struggling with is how to deal with a user who keys in just the name: i.e. Smith, B.

Or what if one of the marks contains letters i.e. 10 c 20 Smith, B.

At the moment my program just reads it as an int I think and tries to carry on anyway - I want it to treat this as a string and output: "Bad input line: Smith, B."

Is there a way that experienced programmers deal with things like this? I don't want to create a mess of if statements everywhere in my code as that makes it hard to follow.

#include <iostream>
#include <string>

using namespace std;

//function to check input validity
bool validateInput(int mark1, int mark2, int mark3, string name);
//function to check the scores
int checkScores(int mark1, int mark2, int mark3);

int main()
{
    int mark1 = 0, mark2 = 0, mark3 = 0;
    string name;

    cin >> mark1 >> mark2 >> mark3;
    getline (cin, name);
    // continue if the input is valid
    if (validateInput(mark1, mark2, mark3, name) == true)
    {
        checkScores(mark1, mark2, mark3);
    }
    else cout << "Bad Input Line: \t" <<
    mark1 << " " << mark2 << " " << mark3
    << " " << name << endl;

    return 0;
}

bool validateInput(int mark1, int mark2, int mark3, string name)
{
    //return false if the scores are not valid
    if (mark1 < 0 || mark1 > 50 || mark2 < 0 || mark2 > 50 || mark3 < 0 || mark3 > 50)
    {
        return false;
    }
    return true;
}

int checkScores(int mark1, int mark2, int mark3)
{
    int mark1Mark = 0, mark2Mark = 0, mark3Mark = 0;
    string candidateType;
    //return the mark for the mark1 test
    if (mark1 <= 10) mark1Mark = 1;
    else if (mark1 <= 20) mark1Mark = 2;
    else if (mark1 <= 30) mark1Mark = 3;
    else if (mark1 <= 40) mark1Mark = 4;
    else if (mark1 <= 50) mark1Mark = 5;
    //return the mark for the mark2 test
    if (mark2 <= 10) mark2Mark = 1;
    else if (mark2 <= 20) mark2Mark = 2;
    else if (mark2 <= 30) mark2Mark = 3;
    else if (mark2 <= 40) mark2Mark = 4;
    else if (mark2 <= 50) mark2Mark = 5;
    //return the mark for the mark3 test
    if (mark3 <= 10) mark3Mark = 1;
    else if (mark3 <= 20) mark3Mark = 2;
    else if (mark3 <= 30) mark3Mark = 3;
    else if (mark3 <= 40) mark3Mark = 4;
    else if (mark3 <= 50) mark3Mark = 5;

    if (mark1Mark >= 5 && mark2Mark >= 5 && mark3Mark >= 5)
        candidateType = "Exceptional";
    else if (mark3Mark >= 2 && mark2Mark >= 2  && mark1Mark >= 2)
        candidateType = "Pass";
    else candidateType = "Fail";

    cout << mark1Mark << " " << mark2Mark << " " << mark3Mark << " " << candidateType;
}
+2  A: 

Failure to convert ill-formed input needs to be checked using cin.fail() as shown here.

   int x;
   cout << "enter choice:";
   cin >> x;
   while (x < 1 || x > 4)
   {
      cout << "Invalid choice, try again:";
      cin >> x;
      // not a numeric character, probably
      // clear the failure and pull off the non-numeric character
      if (cin.fail())
      {
         cin.clear();
         char c;
         cin >> c;
      }
   }

cin is helping you to validate the input upfront here, you just have to check what it tells you.

If you want to output the entire bad line on error, you could restructure the code like this to capture it and then parse, preserving the input for error display:

string rawLine;
getline(cin, rawLine);
stringstream lineStream;

int mark1, mark2, mark3;
string name;
lineStream >> mark1 >> mark2 >> mark3 >> name;

if (lineStream.fail() ||  // malformed input
        !validateInput(mark1, mark2, mark3, name))  
                              // input valid but semantically incorrect
{
    cerr << "bad line :" << rawLine << endl;
}
Steve Townsend
`istream` objects return false in a `bool` context if there has been a failure, and it's often a little more straightforward than calling `fail()` everywhere.
greyfade
@greyfade - agreed that for more specific error detection and clarity of intent this is preferable, per @codeboy2k's answer
Steve Townsend
+4  A: 

You've done pretty good so far. I'll suggest you don't use

cin >> mark1 >> mark2 >> mark3; 

cin is notorious for no error checking, leaving things in the input, not getting the newline, etc...

Instead get the entire line all at once into a string with getline and parse it out with error checking using a std::stringstream

string input = "";
getline(cin, input);
stringstream instream(input);
if (instream >> mark1) {
  // number ok do something
}
else {
  // not a number in the input
  // report it
}
codeboy2k
A: 

Thanks for all the help here. I am using stringstream now to get the entire line and checking inputs against that - much more reliable. My main() now looks like this:

int main()
{
string input;
getline(cin, input);
stringstream instream(input);

int mark1 = 0, mark2 = 0, mark3 = 0;
string name;
instream >> mark1 >> mark2 >> mark3 >> name;
// continue if the input is valid
if (validateInput(mark1, mark2, mark3, name) == true)
{
    checkScores(mark1, mark2, mark3);
}
else cout << "Bad Input Line: \t" << input << endl;

return 0;
}
B Smith
@B Smith - you can add this commentary as an edit to your original answer, it's clearer than using an Answer. Glad you are on track.
Steve Townsend