CS202 Lecture notes -- Review


make

Remember make from the last lecture:

We walk before we run

Let's review C++ from cs102. We'll start with our "Hello World" program from the last lecture -- src/hw1.cpp:

#include <iostream>
using namespace std;

int main()
{
  cout << "Hello world!" << endl;

  return 0;
}

Every line of this program except the cout line contains stuff that you need in every program. The "#include" tells the compiler where to get information about cout. The "using namespace std" tells the compiler to use all of the global variables and type definitions that are "standard" in C++. In this instance, we are using "cout" and "endl", which are "standard."

When your program is executed by the operating system, control starts in the main() procedure, which you must as a procedure that returns an integer. It is also a good idea to return 0 at the end of your main() routine. This is because the operating system will interpret this value when your program ends, and having it return 0 signifies to the operating system that all has gone ok.

As we did last class, we can compile this to a.out and run it, or we can use the compiler's -o flag to name the executable something else:

UNIX> g++ src/hw1.cpp               # Compile to a.out
UNIX> ./a.out                       # Run it.
Hello world!
UNIX> g++ -o bin/hw1 src/hw1.cpp    # Compile to bin/hw1
UNIX> bin/hw1                       # Run it
Hello world!
UNIX> 
We don't have to use endl to print the newline character -- using "\n" in a string accomplishes the same purpose, as shown in src/hw2.cpp:

#include <iostream>
using namespace std;

int main()
{
  cout << "Hello world!\n" ;

  return 0;
}

Using endl vs. "\n" is personal preference. (Actually, endl does a "flush" command, which is sometimes useful. Don't worry about it for now, but just keep that in the back of your mind).

cout is convenient because it recognizes the types of what you want to print automatically. When you give it an integer, it recognizes it as an integer and prints it accordingly. This is as opposed to printf (which we'll go over later), which requires you to specify the types of what you're printing.

So, for example, src/printemall.cpp has cout print strings, string variables and integer variables, all in one statement:

#include <iostream>
using namespace std;

/* Show how cout prints variables with different types. */

int main()
{
  int i;
  string s;

  i = 2;
  s = " times";

  cout << "Love me " << i << s << ", baby\n";

  return 0;
}

UNIX> make bin/printemall                       # You can call make to make single executables.
g++ -Wall -Wextra -std=c++98 -o bin/printemall src/printemall.cpp
UNIX> bin/printemall
Love me 2 times, baby
UNIX> 

Forgive me father, for I have cin'd

We get input from the terminal with cin, which works a lot like cout. We call input from the terminal "standard input." For example, src/readone.cpp reads one integer from standard input and prints it out:

#include <iostream>
using namespace std;

/* Read one integer from standard input and print it. */

int main()
{
  int i;

  cin >> i;
  cout << "I is " << i << endl;

  return 0;
}

Running it and typing 50 gives us the expected output:

UNIX> bin/readone              # I'm going to assume by this point, that you've called 'make' and compiled everything.
50
I is 50
UNIX> 
Cin does not care about whitespace or lines. For example, if we make two consecutive cin statements, we can put the two integers on the same line, different lines, have space in front, leading zeroes, etc. This is src/readtwo.cpp:

#include <iostream>
using namespace std;

/* Read an integer and print it.
   Read a second integer and print it. */

int main()
{
  int i, j;

  cin >> i;
  cout << "I is " << i << endl;

  cin >> j;
  cout << "J is " << j << endl;

  return 0;
}

Here are several examples of entering two integers:

UNIX> bin/readtwo
1 2
I is 1
J is 2
UNIX> bin/readtwo
1
I is 1
2
J is 2
UNIX> bin/readtwo




                  0000001 
I is 1


            -2
J is -2
UNIX> 
You can "redirect standard input from a file." This means that instead of reading from the terminal, cin will read from a file. You do this by putting "< filename" in the command that runs the executable: (cat prints the file to the terminal):
UNIX> cat data/input-1.txt
11
22
UNIX> bin/readtwo < data/input-1.txt
I is 11
J is 22
UNIX> 
You can also use the command echo to print its arguments and then a "pipe" (vertical bar) to have the output of echo be the input to the next program. We'll enter 11 and 22 into our program that way below. (The pipe and file redirection are Unix constructs that are very powerful. You will grow to love them):
UNIX> echo 11 22
11 22
UNIX> echo 11 22 | bin/readtwo
I is 11
J is 22
UNIX> 
Now, what happens when you don't enter an integer? For example, in the first call below, we call rt2 with a blank line as input, and in the second call, we input a non-number followed by a numer:
UNIX> echo "" | bin/readtwo
I is 0
J is 4196288
UNIX> echo Fred 44 | bin/readtwo
I is 0
J is 4196288
UNIX> 
In both cases, i becomes 0 and j becomes 4196288. This is because in both cases, the first cin call failed, and i and j are left uninitialized. The values are random, and vary from machine to machine. You can't count on integers being initialized to zero, so be careful with uninitialized variables!

With the first call, it's pretty clear why both cin calls failed -- there was no input. However, with the second one, you might think that since the second value is a number, the second cin call should succeed and j should become 44. This doesn't happen, because once cin fails, you have to "clear" it by calling cin.clear() to get it to work properly. But first, how do you determine that cin failed? You do that by putting the cin call into an if statement -- each cin statement is actually a boolean value that returns true if the cin statement succeeded and false if it failed.

We do that in src/rt2.cpp:

#include <iostream>
using namespace std;

/* Read in two integers and error check whether you were successful. */

int main()
{
  int i, j;

  if (cin >> i) {
    cout << "I is " << i << endl;
  } else {
    cout << "Bad cin call reading i -- calling cin.clear()\n";
    cin.clear();
  }

  if (cin >> j) {
    cout << "J is " << j << endl;
  } else {
    cout << "Bad cin call reading j -- calling cin.clear()\n";
    cin.clear();
  }

  return 0;
}

Of course, when we run it, it doesn't really work as expected:

UNIX> echo Fred 44 | bin/rt2
Bad cin call reading i -- calling cin.clear()
Bad cin call reading j -- calling cin.clear()
UNIX> 
The second cin call failed just like the first one did. This is because when you call cin.clear() it resets cin, but it is still trying to read "Fred". You have to go ahead and read the erroneous integer as a string before moving on. The better version is in src/rt3.cpp:

#include <iostream>
using namespace std;

/* Reading two integers, testing for failures, and then
   reading the offending non-integer as a string to move
   onto the next integer. */

int main()
{
  int i, j;
  string s;

  if (cin >> i) {
    cout << "I is " << i << endl;
  } else {
    cout << "Bad cin call reading i -- calling cin.clear()\n";
    cin.clear();
    cin >> s;       // Here's where we read the string
  }

  if (cin >> j) {
    cout << "J is " << j << endl;
  } else {
    cout << "Bad cin call reading j -- calling cin.clear()\n";
    cin.clear();
    cin >> s;
  }

  return 0;
}

Now, the second cin call reads j successfully:

UNIX> echo Fred 44 | bin/rt3 
Bad cin call reading i -- calling cin.clear()
J is 44
UNIX> 
Of course, when we call it on the empty file, both cin calls fail. We can detect whether the failures are due to reaching the end of the file by using cin.eof() after the failure, as in src/rt4.cpp:

#include <iostream>
using namespace std;

/* Reading two integers, testing for failures, and differentiating
   whether the failures are due to EOF (end of file), or due to an
   unsuccessful integere conversion. */

int main()
{
  int i, j;
  string s;

  if (cin >> i) {
    cout << "I is " << i << endl;
  } else {
    if (cin.eof()) return 1;      // Here we check for end-of-file, and terminate the program.
    cout << "Bad cin call reading i -- calling cin.clear()\n";
    cin.clear();
    cin >> s;
  }

  if (cin >> j) {
    cout << "J is " << j << endl;
  } else {
    if (cin.eof()) return 1;
    cout << "Bad cin call reading j -- calling cin.clear()\n";
    cin.clear();
    cin >> s;
  }

  return 0;
}

UNIX> echo "" | bin/rt4
UNIX> echo Fred | bin/rt4
Bad cin call reading i -- calling cin.clear()
UNIX> echo Fred Wilma | bin/rt4
Bad cin call reading i -- calling cin.clear()
Bad cin call reading j -- calling cin.clear()
UNIX> echo Fred 44 | bin/rt4
Bad cin call reading i -- calling cin.clear()
J is 44
UNIX> 

Reading single characters using cin

If you use cin to read variables that are of type char, it reads in single characters. For example, the program src/ncnl.cpp uses cin to read each character of standard input. It counts the total number of characters and the total number of L's.

#include <iostream>
using namespace std;

/* - Use cin to read single characters.
   - Count the total number of characters.
   - Count the number of L's. */

int main()
{
  int nc;      // Total number of characters
  int nl;      // Total number of L's
  char c;

  nc = 0;
  nl = 0;

  while (cin >> c) {
    nc++;
    if (c == 'L') nl++;
  }

  cout << "# of characters: " << nc << endl;
  cout << "# of L's: " << nl << endl;
  return 0;
}

When we run it on src/input-mixed.txt and on src/ncnl.cpp, we see that they have two and four two L's respectively.

UNIX> cat data/input-mixed.txt
Love me 2 times baby.  Love me twice 2 day.
UNIX> bin/ncnl < data/input-mixed.txt
# of characters: 33
# of L's: 2
UNIX> bin/ncnl < src/ncnl.cpp
# of characters: 308
# of L's: 4
UNIX> 
The Unix program wc counts lines, words and characters in a file. When we run it on the two input files, we see that the number of characters differs from ncnl:
UNIX> wc data/input-mixed.txt
 1 10 44 data/input-mixed.txt
UNIX> wc src/ncnl.cpp
 25  87 444 src/ncnl.cpp
UNIX> 
wc reports that data/input-mixed.txt has 44 characters, yet ncnl only read 33. Why? The reason is because when cin reads characters, it doesn't read "whitespace" -- spaces, tabs and newlines. If you count the number of non-whitespace characters in input-mixed.txt, you'll see that it has 33.

Four common cin errors: #1 -- "!cin"

The first common error is exemplified by the program in src/readten-bad.cpp:

#include <iostream>
using namespace std;

/* A program that tries to use !cin to test for an error. */

int main()
{
  int i, n1;

  for (i = 0; i < 10; i++) {
    if (!cin >> n1) {
      cout << "Done\n";
      return 0;
    }
    cout << "Number " << i << " equals " << n1 << endl;
  }

  return 0;
}

The intent of this program is to read ten integers and print them out. If one of the cin calls fails, then the program should exit prematurely, printing "Done." However, let's run it on exactly 10 integers:

UNIX> echo 10 11 12 13 14 15 16 17 18 19 | bin/readten-bad
Number 0 equals 4096
Number 1 equals 4096
Number 2 equals 4096
Number 3 equals 4096
Number 4 equals 4096
Number 5 equals 4096
Number 6 equals 4096
Number 7 equals 4096
Number 8 equals 4096
Number 9 equals 4096
UNIX> 
Hmmm. The bad line is

    if (!cin >> n1) {

The boolean "not" operator (!) is being applied only to cin, and not to the entire expression. This is an "order of operations" thing -- (!) has higher precedence than (>>). The bad part about this mistake is that the program compiles legally -- evidently you can negate cin. Perhaps I should look up what it does, but I'm not going to -- it seems like a bad idea regardless of its meaning.

To fix this, you must parenthesize as in src/readten-good.cpp:

#include <iostream>
using namespace std;

/* A program that does not negate cin, but instead negates
   the whole boolean expression.  It reads in up to ten numbers, 
   printing "Done" and exiting when it fails to read a number. */

int main()
{
  int i, n1;

  for (i = 0; i < 10; i++) {
    if (!(cin >> n1)) {            // Here is the correct line.
      cout << "Done\n";
      return 0;
    }
    cout << "Number " << i << " equals " << n1 << endl;
  }

  return 0;
}

This one works as it should:

UNIX> echo 10 11 12 13 14 15 16 17 18 19 | bin/readten-good
Number 0 equals 10
Number 1 equals 11
Number 2 equals 12
Number 3 equals 13
Number 4 equals 14
Number 5 equals 15
Number 6 equals 16
Number 7 equals 17
Number 8 equals 18
Number 9 equals 19
UNIX> echo 55 66 | bin/readten-good
Number 0 equals 55
Number 1 equals 66
Done
UNIX> echo 44 Fred | bin/readten-good
Number 0 equals 44
Done
UNIX> 

Four common cin errors: #2 -- cin.eof() is not proactive!

I've seen this one on tests so many times that I have to address it here. cin.eof() does not return TRUE when you have reached the end of the file. It returns TRUE when you have tried to read something, and that has failed because you are at the end of the file. That's a subtle distinction, but very important.

Take a look at src/badeof.cpp:

#include <iostream>
using namespace std;

/* An example of making a mistake testing for the end of file.  This is because
  cin.eof() tells you if your last reading command failed because you are at the end of file.  It
  does not tell you whether you are at the end of the file now, and your next reading command will
  fail because of that. */

int main()
{
  string s;
  int i;

  i = 0;
  while (!cin.eof()) {
    i++;
    cin >> s;
    cout << "String " << i << " is " << s << endl;
  }
  return 0;
}

This is the type of erroneous code I see on tests. The intent of this program is to number the words on standard input and print them out. However, it has a bug regarding cin.eof():

UNIX> echo Fred Wilma | bin/badeof
String 1 is Fred
String 2 is Wilma
String 3 is Wilma
UNIX> echo "" | bin/badeof
String 1 is 
UNIX> 
Both times, the program prints an extra line. In the first case, it is because after the program reads both words, the "if (!cin.eof())" statement returns true. It returns true because you haven't tried to read the word yet and failed. So, in this case, "cin >> s" fails, and s remains the same, which is why that last line says "String 3 is Wilma". Since the cin statement failed, the next cin.eof() now returns false, and the program exits.

I like to say that cin.eof() and cin.fail() are not proactive, but reactive. They are only true when a previous cin statement failed, and they are telling you why.

Below are two ways to write the program correctly. The first uses cin.eof() correctly, and the second doesn't bother using cin.eof() at all. Personally, I like the second better because it's less convoluted.

src/good-eof-1.cpp:
#include <iostream>
using namespace std;

int main()
{
  string s;
  int i;

  i = 0;
  cin >> s;
  while (!cin.eof()) {
    i++;
    cout << "String " << i << " is " << s << endl;
    cin >> s;
  }
  return 0;
}
src/good-eof-2.cpp
#include <iostream>
using namespace std;

int main()
{
  string s;
  int i;

  i = 0;
  while (cin >> s) {
    i++;
    cout << "String " << i << " is " << s << endl;
  }
  return 0;
}


Four common cin errors: #3 -- cin reads words and not lines

Mistake #3 is when you think that cin works on lines, forgetting that it works on a word-by-word basis:
UNIX> cat data/input-twenty.txt
10 110
11 109
12 108
13 107
14 106
15 105
16 104
17 103
18 102
19 101
UNIX> bin/readten-good < data/input-twenty.txt
Number 0 equals 10
Number 1 equals 110
Number 2 equals 11
Number 3 equals 109
Number 4 equals 12
Number 5 equals 108
Number 6 equals 13
Number 7 equals 107
Number 8 equals 14
Number 9 equals 106
UNIX> 
The program is working fine -- cin reads words, not lines.


Four common cin errors: #4 -- Clearing cin when it fails

The last error is forgetting to clear cin and re-read bad input. The program src/forget-clear.cpp attempts to read ten numbers and flag when a number is not read correctly:

#include <iostream>
using namespace std;

/* This program attempts to read ten numbers and flag when a number is not read correctly.
   It doesn't work, though, because it doesn't clear cin and then read the non-number 
   as a string. */

int main()
{
  int i, n1;

  for (i = 0; i < 10; i++) {
    if (!(cin >> n1)) {
      cout << "Number " << i << " entered incorrectly\n";
    } else {
      cout << "Number " << i << " equals " << n1 << endl;
    }
  }

  return 0;
}

When we run it on src/input-mixed.txt, numbers 2 and 8 should be correct, while the rest are not. However, since we don't clear cin, each cin statement returns that it read incorrectly:

UNIX> cat data/input-mixed.txt
Love me 2 times baby.  Love me twice 2 day.
UNIX> bin/forget-clear < data/input-mixed.txt
Number 0 entered incorrectly
Number 1 entered incorrectly
Number 2 entered incorrectly
Number 3 entered incorrectly
Number 4 entered incorrectly
Number 5 entered incorrectly
Number 6 entered incorrectly
Number 7 entered incorrectly
Number 8 entered incorrectly
Number 9 entered incorrectly
UNIX> 
When you try to fix this mistake, you need to remember to both clear cin, and then read the offending word. The program src/forget-read.cpp remembers to do cin.clear(), but then each successive cin statement tries to read the same word ("Love"), and returns that it read incorrectly:

#include <iostream>
using namespace std;

/* This tries to read 10 numbers, flagging a failure.  However, it doesn't
   work, because when you have a failed conversion, you need to read the
   failed word, in addition to doing clear(). */

int main()
{
  int i, n1;

  for (i = 0; i < 10; i++) {
    if (!(cin >> n1)) {
      cout << "Number " << i << " entered incorrectly\n";
      cin.clear();
    } else {
      cout << "Number " << i << " equals " << n1 << endl;
    }
  }

  return 0;
}

UNIX> bin/forget-read < data/input-mixed.txt
Number 0 entered incorrectly
Number 1 entered incorrectly
Number 2 entered incorrectly
Number 3 entered incorrectly
Number 4 entered incorrectly
Number 5 entered incorrectly
Number 6 entered incorrectly
Number 7 entered incorrectly
Number 8 entered incorrectly
Number 9 entered incorrectly
UNIX> 
Finally, the program src/forget-nothing.cpp reads the offending word after clearing cin, and also detects when input has ended, because that's when reading the string fails:

#include <iostream>
using namespace std;

/* This program reads 10 numbers, identifying when it
   reads a non-number, and also eof. */

int main()
{
  int i, n1;
  string s;

  for (i = 0; i < 10; i++) {
    if (!(cin >> n1)) {
      cin.clear();
      if (!(cin >> s)) return 0;       // This reads the number and detects EOF
      cout << "Number " << i << " entered incorrectly\n";
    } else {
      cout << "Number " << i << " equals " << n1 << endl;
    }
  }

  return 0;
}

When we run it, it correctly identifies numbers 2 and 8 as numbers. The second run identifies that there are only two lines -- one incorrect and one correct.

UNIX> bin/forget-nothing < data/input-mixed.txt
Number 0 entered incorrectly
Number 1 entered incorrectly
Number 2 equals 2
Number 3 entered incorrectly
Number 4 entered incorrectly
Number 5 entered incorrectly
Number 6 entered incorrectly
Number 7 entered incorrectly
Number 8 equals 2
Number 9 entered incorrectly
UNIX> bin/forget-nothing Fred 44
Number 0 entered incorrectly
Number 1 equals 44
UNIX>