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 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> 
You may also use a string-stream to read from strings. I won't go over this in class, but if you're interested, see the basic IO tutorial material mentioned above.

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>