Debugging

And now for the exciting conclusion to the e.cpp debugging problem! There were two suggestions in class: First, alter the loop variable to start at 1 rather than at zero. This would eliminate the multiply by zero that makes the denominator zero, which would then prevent the division by zero that caused the INF answer. This was run, and the answer for e^1 turned out to be 3.71828, which is close, but not really close enough. The fix for that was to change "denom *= i" to "denom *= (i+1)". The program then gives the correct answer. However, this is not a satisfactory solution. We basically found the answer by making random changes. Get a roomful of monkeys writing C++ for you, and your code would work just as well. A better idea is to step back and rethink the problem, then rewrite it correctly from the beginning. There are three steps to doing this:

  1. Know that there is a bug.
  2. Know where the bug is.
  3. Know how to fix the bug.

Loop Invariant

One concept that can help you to write bug-free programs in the first place is to think about defining a loop invariant for each loop in your program. This is simply a statement about a loop that's always true at a given point in the loop's execution. They also make excellent comments for the loop. Some examples from e.cpp:

For e.cpp we must make sure that numerator/denominator == x^i/i! before we add it in. Check e-corrected.cpp for the correctly written loop. Note how the order of staements in the loop body differs from that of e.cpp.

Also note how the variables are set up so that the loop invariant is true before the first iteration of the loop.

Functions

A function can be thought of as a black box which takes inputs, does something to them, then spits out an output. It is not necessary for you to know how the function works to use it, just what it takes in, and what it puts out. Functions are used to break big problems into bite sized pieces. Think of a car: the parts all work independently. The guy designing the alternator doesn't need to know how the fuel pump works, only that it does what it's supposed to do. In addition, although there are four wheels on a car, there is only one design. Functions can be used repeatedly ("called" or "invoked") within a program to reduce the amount of typing that you must do. Functions do one thing, and do it well.

There are several predefined functions. Page 105 lists all the functions included in math.h. To call one of these functions, #include <math.h> then type its name and put the arguments in parentheses. For example, to call the square root function, sqrt(), on a variable x, you type sqrt(x) within an expression. Multiple arguments, or parameters, are separated by commas, as for the power function which returns x^y, pow(x, y). sqrt.cpp contains examples of invoking two functions, sqrt and fabs.

Type Conversion Functions

Say you have two ints. Now say you want to divide them, and get a fractional answer. "You can't do that!" you exclaim. Ah, but you can. There are built in functions that need no #include to work. Say you have int x and int y, and want to find out exactly what x/y is, but without defining new doubles to hold the values of the ints. There is a function double() that temporarily converts one type to another. You can get the answer you desire by using double(x)/double(y)Note that double(x/y) will not have the same result. It will first divide x by y as ints and then convert the answer to a double.

Defining Your Own Functions

There are two parts to a function you define:

  1. Prototype (Declaration)
  2. definition

Header

double geometricSeries(double x, int n)

The first word in the header is the result type. This is the type of output the function produces. the second thing is the function's name. you need to know this to call it. The name is followed by parentheses filled with the function's parameters. When you call the function, you must follow it with parentheses filled with the same number of parameters, of the same types, in the same order.

If this header were the prototype, it would be followed by a semicolon, and would be placed before the definition of main. If it were the header to the definition, then it would be followed by a statement enclosed in {}, just like main. As a matter of fact, main is just a function, so you have actually already been writing function definitions!

Check out the example geometricSeries.cpp to see how user defined functions are written. When you do, you will notice one thing that you may not have seen yet. There is a line that starts with the keyword return. Once the program gets to this line, it will return the value or variable that follows the word return. Once it has done that, the function will terminate. Here, the return statement is at the end of the function, but they can appear anywhere in the function, and can appear in if-else clauses. No matter where they appear, though, once they have been reached, the function ends.

To Index Previous Next