I need to check each score value, they need to be between 0 and 100 and if not the user needs to be prompted to reenter a valid value.
My code:
#include <iostream>
using namespace std;
void sort(int*, int);
void displaySort(int*, int);
int main()
{
int lInput;
cout << "Enter the size of your list: ";
cin >> lInput;
int* lPtr = new int[lInput];
for (int i = 0; i < lInput; i )
{
cout << "Enter a score: ";
cin >> *(lPtr i);
if (*(lPtr i) < 0 || *(lPtr i) > 100)
{
cout << "Invalid input, enter again: ";
}
}
cout << endl;
sort(lPtr, lInput);
displaySort(lPtr, lInput);
cout << endl;
delete[] lPtr;
system("PAUSE");
return 0;
}
void sort(int* array, int size)
{
int scan, minIndex, minValue;
for (int scan = 0; scan < (size - 1); scan )
{
minIndex = scan;
minValue = *(array scan);
for (int i = scan 1; i < size; i )
{
if (*(array i) < minValue)
{
minValue = *(array i);
minIndex = i;
}
}
*(array minIndex) = *(array scan);
*(array scan) = minValue;
}
}
void displaySort(int* array, int size)
{
cout << "List of scores in ascending order:" << endl;
for (int i = 0; i < size; i )
{
cout << *(array i) << " ";
}
}
The way I have it now, it still takes the invalid number. I want it so if an invalid number is entered it does not take the number and asks for a valid one.
CodePudding user response:
In the following code
for (int i = 0; i < lInput; i )
{
cout << "Enter a score: ";
cin >> *(lPtr i);
if (*(lPtr i) < 0 || *(lPtr i) > 100)
{
cout << "Invalid input, enter again: ";
}
}
the problem is that on invalid input, you output text which contains an error message and prompts the user to enter a new number, but you don't actually read any new input. Instead, you simply jump to the next loop iteration, which effectively means that you are accepting the bad input.
One way to solve the problem would be to make an infinite loop which will continue running until the user enters valid input. When that happens, you can break out of that loop with the break
statement.
for (int i = 0; i < lInput; i )
{
for (;;) //infinite loop, equivalent to while(1)
{
cout << "Enter a score: ";
cin >> *(lPtr i);
if (*(lPtr i) < 0 || *(lPtr i) > 100)
{
cout << "Input must be between 0 and 100, try again!\n";
continue;
}
break;
}
}
However, one problem with this code is that it only performs a range check, but does not check whether the input was valid at all. In particular, it does not check whether the stream extraction operator >>
successfully converted the user's input into a number. This can be checked by calling cin.fail()
.
It would be best to perform this additional check before the range check, like this:
for (int i = 0; i < lInput; i )
{
//this loop will continue until the input is valid
for (;;) //infinite loop, equivalent to while(1)
{
cout << "Enter a score: ";
cin >> *(lPtr i);
//check if stream error occurred
if ( cin.fail() )
{
//check if error is recoverable
if ( cin.bad() )
{
throw std::runtime_error( "unrecoverable I/O error" );
}
//print error message
cout << "Input must be a number, try again!\n";
//discard bad input (remainder of line)
cin.ignore( std::numeric_limits<std::streamsize>::max(), '\n' );
//clear stream status flags
cin.clear();
continue;
}
if (*(lPtr i) < 0 || *(lPtr i) > 100)
{
cout << "Input must be between 0 and 100, try again!\n";
continue;
}
//input is valid, so break out of the infinite loop
break;
}
}
Note that the code above requires you to additionally #include <limits>
.
However, this code is still not quite perfect. If you enter input such as 12sdlhfh
, then it will accept 12
as valid input, but the next stream extraction will fail, because sdlhfh
is not a valid number, and it will print an error message. This error message could be prevented by discarding the remainder of the line after every stream extraction, but in this case, this is probably not be the ideal solution, because you would probably want to reject input such as 12sdlhfh
.
To be able to reject such input, you should not use the stream extraction operator >>
, because it will stop reading as soon as it encounters a non-digit. Instead, you should always read one line at a time, using std::getline
, and validate the entire line using std::stoi
and some extra code to verify that no non-whitespace characters appear after the number.
for (int i = 0; i < lInput; i )
{
//this loop will continue until the input is valid
for (;;) //infinite loop, equivalent to while(1)
{
std::string line;
std::size_t pos;
cout << "Enter a score: ";
getline( cin, line );
//check if stream error occurred
if ( cin.fail() )
{
//check if error is recoverable
if ( cin.bad() )
{
throw std::runtime_error( "unrecoverable I/O error" );
}
//print error message
cout << "Input error, try again!\n";
//clear stream status flags
cin.clear();
continue;
}
//attempt to perform the actual conversion
try
{
*(lPtr i) = std::stoi( line, &pos );
}
catch ( std::invalid_argument )
{
cout << "Unable to convert input to number, try again!\n";
continue;
}
catch ( std::out_of_range )
{
cout << "Range error, try again!\n";
continue;
}
//verify that rest of line does not contain any non-whitespace characters
for ( ; pos < line.length(); pos )
{
if ( !std::isspace( static_cast<unsigned char>(line[pos]) ) )
{
cout << "Invalid character found, try again!\n";
//we cannot use "continue" here, because that would
//continue to the next iteration of the innermost
//loop, but we want to continue to the next iteration
//of the outer loop
goto continue_outer_loop;
}
}
if (*(lPtr i) < 0 || *(lPtr i) > 100)
{
cout << "Input must be between 0 and 100, try again!\n";
continue;
}
//input is valid, so break out of the infinite loop
break;
continue_outer_loop:
continue;
}
}
Note that the code above additionally requires: #include <string>
and #include <cctype>
.
The code above uses one goto
statement. Normally, you should not use goto
if possible, but for exiting nested loops, it is acceptable.
Also note that if you use the above code, then it won't be compatible with using cin >> lInput;
outside the loop. Mixing std::getline
and std::istream::operator>>
will generally not work, because std::istream::operator>>
will leave the newline in the buffer, so the next call to the std::getline
will probably only retrieve an empty line.
CodePudding user response:
Try this -
int main()
{
int lInput;
cout << "Enter the size of your list: ";
cin >> lInput;
int* lPtr = new int[lInput];
for (int i = 0; i < lInput; i )
{
cout << "Enter a score: ";
cin >> *(lPtr i);
while (*(lPtr i) < 0 || *(lPtr i) > 100)
{
cout << "Invalid input, enter again: ";
cin >> *(lPtr i);
}
}
cout << endl;
sort(lPtr, lInput);
displaySort(lPtr, lInput);
cout << endl;
delete[] lPtr;
system("PAUSE");
return 0;
}