How to avoid an error when a number is expected as an input and the user types a letter? C ++

5
void getPiezas(int* piezas,int* f2){
    cout << "Digite cantidad de piezas a procesar\n";
    cin >> *piezas;
    ...
}

if the user types a letter instead of a whole number or if you enter a double instead of a int . How should I approach the code?

    
asked by R.Ramirez 07.05.2017 в 22:23
source

3 answers

5

When an error occurs in the reading of a data, for example that you expect to receive an integer and the user enters a letter, the error flags of std::cin are activated, and which is equally important, the following readings will not be made. You can check with the following example:

int main()
{
  int var;
  std::string texto;

  std::cin >> var >> texto;
  std::cout << '-' << texto << '-';
}

If only one text is entered, for example asdf , the program will display -- . This occurs because when the integer reading fails, std::cin activates the error bit and is blocked. To be able to continue using the stream it is necessary to reset the error bits:

int main()
{
  std::string texto;
  int var;
  std::cin >> var;

  if( !std::cin.good() )
    std::cin.clear();

  std::cin >> texto;
  std::cout << '-' << texto << '-';
}

If the entry asdf is repeated now, the program will print -asdf- which indicates that before a wrong reading the stream does not discard any data ... as it is normal that if a data it is not valid the program remains waiting for you to enter a valid one, it is necessary to discard the erroneous data of the buffer input and this is achieved with the method ignore() :

#include <iostream>
#include <limits>
#include <string>

int main()
{
  int var;

  while( true )
  {
    std::cin >> var;
    if( !std::cin.good() )
    {
      std::cout << "ERROR\n";
      std::cin.clear();
      std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
    }
    else
      break;
  }

  std::cout << var;
}

numeric_limits is a template that allows you to obtain the range of values supported by a given data type ( int , short , long long , ...). In the case of aliases ( std::size , std::streamsize , ...) it is the compiler that makes the conversion to the corresponding native type.

This could easily be embedded in a function for it could be done in many ways possible ... pulling the simple function could meet the following premise: "returns a boolean that indicates whether the reading has been made correctly or not You also receive an integer by reference that is only updated when the reading is correct ":

bool ReadInt(int& valor)
{
  std::cin >> valor;

  bool ok = std::cin.good();

  if( !ok )
  {
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
  }

  return ok;
}

Let's try the function:

int main()
{
  int var;

  while( true )
  {
    if( !ReadInt(var) )
      std::cout << "ERROR\n";
    else
      break;
  }

  std::cout << var;
}

Edit

To deal with the issue of decimals you can do the following: Once the data is read, it is verified that the error flags have not been activated. Then it will be understood that the data read is correct if any of the following circumstances occurs:

  • In the input buffer we find a line break
  • In the input buffer we find a space

Applying this to the previous function would be like this:

bool ReadInt(int& valor)
{
  std::cin >> valor;

  bool ok = std::cin.good();

  if( ok )
  {
    char c = static_cast<char>(std::cin.peek());
    ok = (c == '\n' || c == ' ' );
  }

  if( !ok )
  {
    std::cin.clear();
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(),'\n');
  }

  return ok;
}
    
answered by 08.05.2017 в 10:00
3

It depends on what you understand by mistake, you are already avoiding it!

Where is the error?

By default, when an object in the family std::basic_istream (as std::cin ) you give it to read something different from the input you give it, it does not throw an error. Depending on the version of the C ++ standard that your compiler follows, suppose this code:

int a = 11111;
int b = 22222;

std::cout << "Pon un valor numérico: ";
std::cin >> a;

std::cout << "Pon un valor numérico: ";
std::cin >> b;

std::cout << "El valor a es: " << a << '\n'
          << "El valor b es: " << b << '\n';

For this input data:

Pon un valor numérico: 1
Pon un valor numérico: patata

It will behave as follows:

  • In pre-C ++ 11 standards, when a formatted reading fails, the input data remains unchanged.
  • In standards after C ++ 11, when a formatted reading fails, the input data gets the value 0 .

So, according to the standard your compiler follows, you'll get these outputs:

Anterior a C++11
 El valor a es: 1
 El valor b es: 22222
C++11 o superior
 El valor a es: 1
 El valor b es: 0

There is no error in sight, but we can look for it.

However, in both cases, given that an incorrect data reading will have occurred, the data flow will establish the flag of failures to true value, you can check this flag with the function std::basic_ios::fail :

int leer_valor()
{
    int resultado;

    while (true)
    {
        std::cout << "Pon un valor numérico: ";
        std::cin >> resultado;

        if (!std::cin.fail())
            return resultado;

        std::cout << "Algo ha fallado al leer\n";
        std::cin.clear();
    }
}

int a = leer_valor();
int b = leer_valor();

std::cout << "El valor a es: " << a << '\n'
          << "El valor b es: " << b << '\n';

In the example, the leer_valor function would infinitely ask the user for values until the std::cin reading is correct. It is important to erase the flag of failure with std::basic_ios::clear because if not, subsequent readings would fail even being correct.

Do you want mistakes? You will have mistakes!

By default, when an object in the family std::basic_istream (as std::cin ) you give it to read something different from the input you provide it, it does not throw an error but we can configure it to throw exceptions in case of erroneous reading using the function std::basic_ios::exceptions :

int a = 11111;
int b = 22222;
std::cin.exceptions(std::ios_base::failbit);

try
{
    std::cout << "Pon un valor numérico: ";
    std::cin >> a;

    std::cout << "Pon un valor numérico: ";
    std::cin >> b;
}
catch (std::ios_base::failure &error)
{
    std::cout << "Algo ha pasado al leer!";
}

std::cout << "El valor a es: " << a << '\n'
          << "El valor b es: " << b << '\n';

Although apparently this option is not very popular.

What if you type a double ?.

Reading a double on an integer using the standard input will not produce any errors, nor will it set the fault flags nor throw exceptions if you set it to do so. Simply will assign the entire part of the entry number to the given int variable. Which does not seem very problematic. But if you really need it, it would be more appropriate to read the entry as text and check that all the characters are numeric:

int leer_valor()
{
    std::string lectura;
    std::locale l("");

    while (true)
    {
        std::cout << "Pon un valor numérico: ";
        std::cin >> lectura;

        if (std::all_of(lectura.cbegin(), lectura.cend(), [&l](auto v) { return std::isdigit(v, l); }))
            return std::stoi(resultado);

        std::cout << "Algo ha fallado al leer\n";
    }
}

int a = leer_valor();
int b = leer_valor();

std::cout << "El valor a es: " << a << '\n'
          << "El valor b es: " << b << '\n';

The previous example uses the algorithm that verifies that all the data of a collection fulfills a function, in this case all the data of the read chain must comply with std::isdigit so alphabetical values or decimal values will not pass the filter. Keep in mind that unless something strange happens, reading from console to a text variable will never fail.

Conclusion.

If the user types a letter instead of a whole number you can control it by reading the error flags but if you type a double instead of a int it is necessary to save in a text variable and analyze it.

    
answered by 08.05.2017 в 10:12
1

First of all, I think that what you want is not that pieces is an array. I rely on that in the console output you put "quantity of pieces", and if it's just a the amount you need, the declaration of the function would have to be modified.

void getPiezas(int piezas,int* f2){...}

If what you wanted was to pass piezas by reference to be able to modify its value would be:

void getPiezas(int& piezas,int* f2){...}

As for reading data, you can include it in a loop until a series of conditions are met. For example, read the data in the form of string and analyze each of the characters of the string later in an auxiliary function.

void getPiezas(int& piezas, int* f2){
    std::string aux ;

    std::cout << "Digite cantidad de piezas a procesar\n";
    std::cin >> aux;

    while(!esEntero(aux)){
        std::cout << "El número de piezas debe ser un entero\n";
        std::cout << "Digite cantidad de piezas a procesar\n";
        std::cin >> aux;
    }

    piezas = stoi(aux) ;
}

These conditions can be performed in a Boolean function that tells you if it is an integer or not. In this case, each character of the chain has been examined to know if each of them is a decimal. At the moment that there is one that is not, it leaves the loop and returns false .

bool esEntero(const std::string& piezas){
    bool resultado = true ;
    for(unsigned i = 0 ; i < piezas.size() && resultado ; i++)
        if(std::isdigit(piezas[i]) == 0)
            resultado = false ;

    return resultado ;
}
    
answered by 07.05.2017 в 23:41