More Supplementary Lecture Notes - cin.fail(), cin.clear(), cin.eof() and strchr()

James S. Plank


1. cin.fail() and cin.eof()

Part of C++ input processing is the function cin.fail(). This returns non-zero (true) if the last cin command failed, and one otherwise. When you reach the end of a file and try to read anything, this is a classic time for cin.fail(). See the program cinfail.cpp:

#include <iostream>
using namespace std;

int main()
{
  string s;
  int i;

  i = 0;
  while (1) {
    i++;
    cin >> s;
    if (cin.fail()) return 0;
    cout << "String " << i << ": " << s << endl;
  }
}

You'll note, when you run this, it ends when the end of the file has been reached, because cin.fail() returns true here:

UNIX> g++ cinfail.cpp
UNIX> cat input-1.txt
Cats foot iron claw
Neuro-surgeons scream for more
At paranoias poison door.
21st century schizoid man.
UNIX> a.out < input-1.txt
String 1: Cats
String 2: foot
String 3: iron
String 4: claw
String 5: Neuro-surgeons
String 6: scream
String 7: for
String 8: more
String 9: At
String 10: paranoias
String 11: poison
String 12: door.
String 13: 21st
String 14: century
String 15: schizoid
String 16: man.
UNIX> 
cin.fail() also returns 1 if you try to read an integer and it receives something that cannot be converted to an integer. For example, see cinfail2.cpp:

#include <iostream>
using namespace std;

int main()
{
  int j;
  int i;

  i = 0;
  while (1) {
    i++;
    cin >> j;
    if (cin.fail()) return 0;
    cout << "Integer " << i << ": " << j << endl;
  }
}

When we run it on input-2.txt, you see that it quits after reading the seventh word, because that is not an integer:

UNIX> g++ cinfail2.cpp
UNIX> cat input-2.txt
30 40 50 60 70 -100 Fred 99 88 77 66
UNIX> a.out < input-2.txt
Integer 1: 30
Integer 2: 40
Integer 3: 50
Integer 4: 60
Integer 5: 70
Integer 6: -100
UNIX> 
So, how do you know if you read an incorrect input word, or if you are at the end of the file? This is when you use cin.eof() and cin.clear(). First, cin.eof() returns 1 if you tried to read something but were at the end of file. Second, cin.clear() is used to "clear the error state" of cin. In other words, when an input failure occurs and cin.fail() returns true, the input buffer (cin) is placed in an "error state", and further input processing will not work unless you clear the state by calling cin.clear(). Used together with cin.fail(), cin.eof() and cin.clear() let you process and error check a variety of input. For example, cinfail3.cpp below reads all the integers in a file and prints them out. It uses cin.fail()/cin.clear() to check for and ignore non-integers, and uses cin.eof() to discover the end of the file:

#include <iostream>
using namespace std;

int main()
{
  int j;
  int i;
  string dummy;

  i = 0;
  while (1) {
    cin >> j;
    if (!cin.fail()) {
      i++;
      cout << "Integer " << i << ": " << j << endl;
    } else if (cin.eof()) {
      return 0;
    } else {
      cin.clear();
      cin >> dummy;
    }
  }
}

When we run it on input-3.txt, you see that it works as promised:

UNIX> g++ cinfail3.cpp
UNIX> cat input-3.txt
Black 100 as 50 a 25 dark 12 night -5 she 0 was -500
UNIX> a.out < input-3.txt
Integer 1: 100
Integer 2: 50
Integer 3: 25
Integer 4: 12
Integer 5: -5
Integer 6: 0
Integer 7: -500
UNIX> 

2. strchr() to check for one of a bunch of characters

Here's a nice trick. Suppose you want to know whether a character is one of the characters '0', 'A', 'b', '.', ':' or '/'. Well, one way to test for this is to do a bunch of if statements. The other way is to use strchr(). Remember, strchr(s, c) returns a pointer to the first instance of c in s, and it returns NULL if c is not in s. So, if you want to perform the above test, you can say:
  if (strchr("0Ab.:/", c) != NULL)
If that returns true, then c is one of the desired characters. Otherwise, it's not.

For example, suppose we want to write a program that has the user enter a card by typing the rank and the suit, like "AH" for the ace of hearts, and "2C" for the two of clubs. Here's a way to do it (in readcard.cpp):

#include <iostream>
#include <cstring>
using namespace std;

const char *ranks = "23456789TJQKA";
const char *suits = "CDHS";

const char *longranks[13] = 
   { "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine",
   "Ten", "Jack", "Queen", "King", "Ace" };

const char *longsuits[4] = 
   { "Clubs", "Diamonds", "Hearts", "Spades" };

int main()
{
  string s;
  const char *str;
  int cnum;

  while (1) {
    cout << "Enter a card: ";
    cin >> s;
    str = s.c_str();
    if (strlen(str) != 2) {
      cout << "The card must the format 'rank-suit', such as 'AH' or '2C'\n";
    } else if (strchr(ranks, str[0]) == NULL) {
      cout << "The rank must be one of " << ranks << endl;
    } else if (strchr(suits, str[1]) == NULL) {
      cout << "The suit must be one of " << suits << endl;
    } else {
      cnum = (strchr(suits, str[1]) - suits)*13 + 
             (strchr(ranks, str[0]) - ranks);
      cout << "Good card: #" << cnum << ": The " << 
          longranks[cnum%13] << " of " << longsuits[cnum/13] << ".\n";
    }
  }
}

Note the use of pointer arithmetic to get the rank and suit. Study that one carefully. It's a good trick to know.

Here's an example of it running:

UNIX> g++ readcard.cpp
UNIX> a.out
Enter a card: AH
Good card: #38: The Ace of Hearts.
Enter a card: 2C
Good card: #0: The Two of Clubs.
Enter a card: 7S
Good card: #44: The Seven of Spades.
Enter a card: TD
Good card: #21: The Ten of Diamonds.
Enter a card: YS
The rank must be one of 23456789TJQKA
Enter a card: TY
The suit must be one of CDHS
Enter a card: Fred
The card must the format 'rank-suit', such as 'AH' or '2C'
Enter a card: ^C
UNIX>