CS302 Lecture Notes - Remembering C++ I/O

For more reference material on I/O in C++, see:

Today's lecture concerns remembering C++ input/output. Cin and cout are the ways to read from standard input and write to standard output respectively. They are C++ objects which have "methods" (procedures) associated with them. Some of the pertinent ones are cin.eof(), cin.fail() and cin.clear(). For a nice description of these, take a look at my lecture notes from CS102 on the topic.

As a basic program, take a look at rdoubles.cpp:

#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;

int main()
{
  double d;
  int dnum;
  string s;
  
  dnum = 1;
  while(1) {
    cout << "Enter a double: ";
    cout.flush();

    cin >> d;
    if (cin.eof()) exit(0);
    if (cin.fail()) {
      cout << "Not a double." << endl;
      cin.clear();
      cin >> s;
    } else {
      cout << "Double number " << dnum << " is " << d << endl;
      dnum++;
    }
  }
}

This program reads words on standard input, and if they are doubles, prints them out. When it sees a non-double, it marks that it is not a double. It exits on EOF:

UNIX> g++ rdoubles.cpp -o rdoubles
UNIX> rdoubles
Enter a double: 1
Double number 1 is 1
Enter a double: 2
Double number 2 is 2
Enter a double: 3
Double number 3 is 3
Enter a double: 4
Double number 4 is 4
Enter a double: 5 6 7 8
Double number 5 is 5
Enter a double: Double number 6 is 6
Enter a double: Double number 7 is 7
Enter a double: Double number 8 is 8
Enter a double: Fred
Not a double.
Enter a double: 9.0
Double number 9 is 9
Enter a double: 10.33333
Double number 10 is 10.3333
Enter a double:  <CNTL-D>
UNIX> 
You'll note that it reads words, and not lines, so that when 5 6 7 8 is entered, the next three cin >> d commands return instantly.

Contrast that with the program below (rdub2.cpp), which uses getline() to read a line, and then sscanf() to convert that line into a double. Thus, if there are multiple words on a line, it checks the first but not the rest.

#include <stdio.h>
#include <iostream>
#include <string>
using namespace std;

int main()
{
  double d;
  int line;
  string s;
  
  line = 0;
  while(1) {
    cout << "Enter a double: ";
    cout.flush();

    getline(cin, s);
    line++;
    if (cin.eof()) exit(0);
    if (sscanf(s.c_str(), "%lf", &d) != 1) {
      cout << "Line " << line << " Not a double." << endl;
    } else {
      cout << "Double on line " << line << " is " << d << endl;
    }
  }
}

UNIX> g++ rdub2.cpp -o rdub2
UNIX> rdub2
Enter a double: 44.44
Double on line 1 is 44.44
Enter a double: 1 2 3 4 5
Double on line 2 is 1
Enter a double: Fred
Line 3 Not a double.
Enter a double: Fred 0 2 3 4
Line 4 Not a double.
Enter a double: 
Line 5 Not a double.
Enter a double: -55.55
Double on line 6 is -55.55
Enter a double:  <CNTL-D>
UNIX> 

Compilation of Multiple Files

Often it makes sense to break up programs into multiple files, connected by a header file. A simple example is where we define a procedure read_double(), which reads a double from standard input, exiting on EOF and skipping non-doubles. This is in rd.cpp:

#include <stdio.h>
#include <iostream>
#include <string>
#include "rd.h"
using namespace std;

double read_double()
{
  double d;
  string s;

  while (1) {
    cin >> d;
    if (cin.eof()) exit(0);
    if (cin.fail()) {
      cin.clear();
      cin >> s;
    } else {
      return d;
    }
  }
}

So that another program may use read_double(), we put a "prototype" or definition of the function into rd.h:

double read_double();

Finally, the program userd.cpp uses read_double() to read doubles:

#include <stdio.h>
#include <iostream>
#include <string>
#include "rd.h"
using namespace std;

main()
{
  while (1) {
    cout << read_double() << endl;
  }
}

When you compile this, you can either specify everything on the command line:

UNIX> g++ -o userd userd.cpp rd.cpp
UNIX> userd
4 5
4
5
6 Fred 7
6
7
<CNTL-D>
UNIX> 
Or compile each cpp file separately into an object file, and then link each object file to make the executable:
UNIX> g++ -c userd.cpp
UNIX> g++ -c rd.cpp
UNIX> g++ -o userd userd.o rd.o
UNIX> userd
1 2 3 4
1
2
3
4
<CNTL-D>
UNIX> 
Linking from object files is faster than compiling the source files.

Make

Make is a program that automates the task of compiling. It relies on a file called makefile in the current directory to describe how to compile. The format is a littly byzantine, but you can figure it out. I will provide makefiles with all labs and lecture notes so that you may make use of it. Here, I'll type make clean, which will remove all executables and object files:
UNIX> make clean
rm -f *.o rdoubles rdub2 userd
UNIX> 
Now, if I type make, it will compile all the programs by first creating object files and then linking them:
UNIX> make
g++    -c -o rdoubles.o rdoubles.cpp
g++ -o rdoubles rdoubles.o
g++    -c -o rdub2.o rdub2.cpp
g++ -o rdub2 rdub2.o
g++    -c -o userd.o userd.cpp
g++    -c -o rd.o rd.cpp
g++ -o userd userd.o rd.o
UNIX> 
Now, if I touch userd.cpp and type make again, it knows to re-make userd.o and userd, but that it doesn't have to do anything to rd.o since rd.cpp was not modified:
UNIX> touch userd.cpp
UNIX> make
g++    -c -o userd.o userd.cpp
g++ -o userd userd.o rd.o
UNIX>