Homework

In these notes, we make explicit some things that have probably come up already anyway.

break and continue

Here are two statements that can sometimes be used to make loops simpler. Don't overdo using them though!

The statement break;, when executed inside a loop, "breaks" out of (i.e. exits) the loop. So, for example, if you had an array A of n ints and you wanted to print them out comma-separated, you might do something like this.

int i = 0;
while(true) {
  cout << A[i];
  ++i;
  if (i == n)
    break;
  cout << ","; 
}

It's kind of overkill here, but it hopefully makes the purpose of break clear. Note: in the case of nested loops, break only "breaks out of" the innermost of the loops in which it appers.

The statement continue;, when executed inside a loop, skips whatever remains of the loop body, and continues on with the loop. What exactly this means depends on the type of the loop. With a while loop, this mean skipping whatever remains of the loop body and jumping back up to the continuation condition. With a for loop, this mean skipping whatever remains of the loop body and jumping back up to the update statement, i.e. the third part of the for-line. Suppose I wanted to compute the average of only the non-negative elements of an array A of n ints. I might do something like this:

int s = 0, count = 0;
for(int i = 0; i < n; i++) {
  if (A[i] < 0)
    continue;
  s = s + A[i];
  count++;
}
cout << double(s)/count << endl;

Once again, this is overkill in this simple example. A regular if-else would be better. But it's a simple example that explains what continue statements are about.

+=,-=,*=,...

You often find yourself writing something like runningSum = runningSum + nexti. You can't use ++, because the increment is nexti not 1. But you don't like repeating that long variable name runningSum. C/C++ has a nice shorthand for this: +=. We can write the above expression equivalently with += as:

runningSum += next

All the regular arithmetic, logical, and bitwize operators allow for this construction. So we can do "i *= 2" instead of "i = i*2", and we can do "flag ||= check" instead of "flag = flag || check". So ... now you know!

exit(1)

We mentioned this in an earlier class, but now it's a bigger deal. If you want to exit the program, and you're not in main(), you can't just do "return 0;", because that would only return from the current function. So there is a function "exit" that you can call to exit from anywhere. You must #include <cstdlib> to use it. It takes an int argument which is the value the program will return to the operating system. So, for example,

exit(0);

will exit the program, returning zero, no matter where you are.

flush and i/o buffering

Here are two examples of strange behavior. First:

#include <iostream>
using namespace std;

int main() {
  cout << "Got here ...";
  int* A = NULL;
  A[0] = 25;
  cout << " and now I'm here!" << endl;
  return 0;
}

When I run this, I expect to see Got here ..., and then see a Seg Fault message. Here's what I get instead:

~/$ ./prog
Segmentation fault (core dumped)

What happened to the "Got here" message?

Here's the second strange example:

#include <iostream>
#include <unistd.h>
using namespace std;

int main() {
  for(int i = 0; i < 10; i++) {
    cout << '*';
    sleep(1);
  }
  cout << endl;
  return 0;
}

When I run this, I expect to see a *, then one second later see another *, then one second later see another *, and so on until 10 *'s are printed. Instead, I see nothing for 10 whole seconds, then all at once I see 10 *'s. What's happening?

The answer in both cases is I/O bufferring is happening. When you write things with cout, the characters you write are "bufferred", that means stored temporarily until either enough have been collected to make writing to the screen worthwhile, or some event has occurred to cause the less-than-full buffer to be written to the screen despite having unused capacity. Such events include writing a "endl" or asking for input with cin, both of which we've seen. Another such event, which we haven't seen, is writing a "flush" to cout. This "flushes the buffer", i.e. forces the contents of the buffer to be written to the screen, without writing a newline. Using this, we can get the second example working as we'd like:

#include <iostream>
#include <unistd.h>
using namespace std;

int main() {
  for(int i = 0; i < 10; i++) {
    cout << '*' << flush;
    sleep(1);
  }
  cout << endl;
  return 0;
}

Problems

  1. Here's a good problem to work on, as it takes into account a number of the things we've talked about: The file scores.txt contains the scores of students on various problems on an exam. Each row corresponds to a student, and the scores along that row are that student's scores on problems 1, 2, 3 etc.
    Your job: figure out which problem was the hardest! You may assume that for every problem, at least one student got full credit for that problem. If the average score for problem X as a percentage of the full credit score for X is less than the average score for problem Y as a percentage of the full credit score for Y, then problem X is "harder" than problem Y.

    ~/$ ./prob1
    Problem p4 is hardest (ave = 48.5294%)

    Check out this solution.