Reading

Sections 4.4-4.5 of "Problem Solving with C++".

Vocabulary

Before starting new material, it is worth taking a moment to stop and make sure that we are clear on some of the basic vocabulary used to talk about functions in programs. This was covered in the previous lesson. Hopefully the following annotated program will clarify things a bit.

Functions and Top-Down design

The essence of programming is breaking down a single, large, complex problem into many simple pieces that can be attacked independently. Functions help us to do this, and that is one of their most important features. They help us write programs in a top down manner, essentially be letting us write our program using a "wish-list" of functions that we can actually go back and implement later. An example should make this clearer.

Problem: You really enjoy food, but try to maintain a healthy lifestyle. However, given how much you eat each day, your body weight is slowly increasing. In fact, each year you increase your weight by an annual rate of 3% if you weigh only 130 pounds. However, this rate grows larger for every additional 20 pounds you put on because as you gain more weight, you tend to exercise less. This 3% rate thus gets bigger based on your current weight, but it won't go above 8% no matter what you weigh. So, for example, if you weigh 150 pounds, your weight gain rate is 3.5%. Weight gain is compounded monthly at the annual rate (i.e. the monthly compounding rate is the annual rate divided by 12). Each year you also make a large one-time weight gain or reduction. You either decide to double down on the Thanksgiving chow, or you skip and exercise all winter. Write a program that simulates 5 years under this system, interactively querying the user for weight gain/loss at the beginning of each year, and returning the balance at the end of the 5th year.

There's a lot to this problem. However, imagine how much easier it would be to solve if the following functions were available:

With this "wish list" of functions, we could write the program quite easily:
double B = 0.0;
for(int i = 0; i < 5; i++)
{
  B = gainloss(B);
  double r = rate(B);
  B = B*compound(r);
}
cout << B << endl;
This is a pretty easy program! Of course, the 3 functions in our wish list do not exist, so we'll have to implement them for ourselves.

double gainloss(double B)
{
  // Get type of gain/loss transaction
  char act;
  cout << "Enter l:loss g:gain ";
  cin >> act;

  // Get amount of gain/loss
  double A;
  cout << "Enter amount: ";
  cin >> A;

  // Get new weight amount
  if (act == 'l')
    B = B - A;
  else
    B = B + A;

  return B;
}
double rate(double B)
{
  // Get # of 20 lbs above 130.
  int T = (B-130)/20;

  // Calc rate
  double r = 3 + T*0.5;
  if (r > 8)
    r = 8;

  return r;
}
double compound(double r)
{
  // Sim. year with monthly compounding
  double R = r/100, total = 1.0;
  for(int i = 0; i < 12; i++)
    total = total*(1 + R/12);

  return total;
}

The key here is that each of these functions can be implemented independently. When I implement transaction, I don't need to worry about any other aspect of the program - it's like transaction is its own little (easy!) program to write. Take a look at the complete program.

Multi-Parameter Functions

Functions get infinitely more interesting when they have more than one argument or parameter. We've already seen at least one example of a multi-parameter function from the cmath library, the pow function. Specifying multiple parameters for a function is just like spcifying several single parameters in a comma-separated list. For example, suppose you wanted to define a function max that looked at two ints and returned the larger of the two. It's prototype would be
int max(int,int);
The definition would look like this:
int max(int a, int b)
{
  if (a < b)
    return b;
  else
    return a;
}
Note that this example shows you that you can return from anywhere within a function, just like you can return from anywhere within main.

It's important to note that the order of arguments is matters. For example, suppose you had a function

void rep(int,char);
that printed the char argument to the screen the number of times given by the int. If I want to print a # symbol 42 times, I need to be sure to say rep(42,'#'), because the function expects the int object first. If I said rep('#',42) instead, do you know what'd happen? I'd print 35 *s! Why? Because the same kind of implicit type conversions that go on inside expressions go on with function arguments! The rep function expects an int as its first argument, and when it gets '#' instead, it simply converts it to an int ... and the ASCII value of '#' is 35, so that's the int you get. Likewise, a char is expected as the second argument, and when rep gets the number 42 instead, it converts it to a character, and ASCII value 42 gives you the char '*'.

When implicit conversions aren't possible, the compiler gives you an error message. For example, if you tried to call rep("#",42) the compiler would give you an error saying that the first argument of rep was supposed to be an int, but you gave it a string, and there's no way to convert the string to an int. You'll likely see lots of these messages in your life!

Predicates

Functions which return the type bool are traditionally referred to as predicates. Imagine that you had to compute more and more terms of a series approximation for cos(x) until your series approximation and the cmath library approximation agreed to a particular precision. In other words, you had two floating-point values and a number n and you had to test to see whether they agreed to within 10-n. We could define a function approxequal to do this.
bool approxequal(double, double, int);
... where the first two arguments are the quantities to compare, and the int argument is the precision.
bool approxequal(double x, double y, int n)
{
  if (fabs(x - y) > pow(10,-n))
    return false;
  else
    return true;
}
With the definition of this predicate, the main while-loop of you program would look like
while(!approxequal(series,libvalue,n))
{ 
  ...

Really there's no need to bring predicates up as a special subject, since functions that return bools are not any more special than functions that return, say, strings. However, you might not have thought much about the use of having such functions.

Problems

  1. Surveying Problem This gives you another simple example of top-down design.
  2. Drawing a triangle with *'s This is a simple multi-parameter function problem.
  3. Distance between points This is another simple multi-parameter function problem.
  4. What percentage of numbers are prime? This is a simple example using a predicate.