Computing Concepts with C++ Essentials
Laboratory Notebook
Chapter 5 - Functions

Cay S. Horstmann
Geof Pawlicki

Your name:
Your email address:
Your student ID number:

Once this form has been customized for your institution, you can use this button to send your lab work. Be sure to read the instructions before starting your work.

Lab Objectives

To gain experience in

R1. Functions as Black Boxes

Predictable input will result in predictable output. A function maps element(s) from a problem domain, called parameter(s), into an element from a range of solutions, called the return value.

To treat a function as a "Black Box", one simply uses it.

For instance, here's a function to compute the volume of a cylinder, say, a beer can, given the height and diameter in millimeters.

/* PURPOSE:   Function to compute the volume of a cylinder
   RECEIVES:  height - the height in millimeters
              diameter - the diameter in millimeters
   RETURNS:   volume - in cubic milliliters
float cylinder_volume(float height, float diameter)
{  . . . 

To use this function to measure the volume of a can of Blatz Lite, just supply parameters and get its return.

float v = cylinder_volume(2 * can_depth, 1.5 * can_diameter);

Notice that you can use the function without knowing what's inside. Actually, this function is quite simple

float cylinder_volume(float height, float diameter)
{  float volume = M_PI * pow(diameter/2, 2) * height;
   return volume;

Suppose, instead of milliliters, an answer in fluid ounces is needed, say for a recipe. Use the following functions, together with float cylinder_volume(float, float) to implement a US measurement version float cylinder_volume_oz(float, float) like this:

Cylindric volume function chart
float inch_to_mm(float inches)
/* PURPOSE:   Function to compute the volume of a cylinder
   RECEIVES:  inches - value in inches to convert to millimeters
   RETURNS:   the converted value
   REMARKS:   1 inch = 25.4 millimeters
{  const float MM_PER_INCH = 25.4;
   return inches *  MM_PER_INCH;

float mm_cubed_to_oz(float mm3)
/* PURPOSE:   Function to convert cubic millimeters to U.S. ounces
   RECEIVES:  mm3 - volume in cubic millimeters
   RETURNS:   volume in ounces
   REMARKS:   1 fluid U.S. ounce = 29.586 milliliters
{  const float MM_CUBED_PER_OZ = 29.586;
   return value_to_convert / MM_CUBED_PER_OZ;

float cylinder_volume(float height, float diameter)
/* PURPOSE:   Function to compute the volume of a cylinder
   RECEIVES:  height - the height in millimeters
              diameter - the diameter in millimeters
   RETURNS:   volume - in cubic milliliters
{  float volume = M_PI * diameter * diameter * height / 4; 
   return volume;

int cylinder_volume_oz(float height, float diameter)
/* PURPOSE:   Function to compute the volume of a cylinder
   RECEIVES:  height - the height in inches
              diameter - the diameter in inches
   RETURNS:   volume - in fl. oz.


int main()
{  float height;
   float diameter;
   float volume;

   cout << "Please enter the height (in inches)" << "\n";
   cin >>  height;
   cout << "Please enter the diameter (in inches)" << "\n";
   cin >>  diameter;
   volume = cylinder_volume_oz(height, diameter);
   cout << "The volume is " << volume << "ounces" << "\n";

   return EXIT_SUCCESS;

P1. Writing Functions

Productivity Hint 5.1 in the text suggests several enhancements to the future value function. Here is the function from the text.

float future_value(float initial_balance, float p, int nyear)
/* PURPOSE:  computes the value of an investment with compound interest
   RECEIVES: initial_balance - the initial value of the investment
             p-the interest rate as a percent
             nyear-the number of years the investment is held
   RETURNS:  the balance after nyears years
   REMARKS:  Interest in compounded monthly
{  float b = initial_balance * pow(1 + p / (12 * 100), 12 * nyear);
   return b;

int main()
{   cout << "Please enter the initial investment: ";
    float initial_balance;
    cin >> initial_balance;
    cout << "Please enter the interest rate in percent: ";
    float rate;
    cin >> rate;
    cout << "Please enter the number of years: ";
    float nyears;
    cin >> nyears;
    float balance = future_value(initial_balance, rate, nyears);
    cout << "After " << nyears << ", the initial investment of " << initial_balance <<
       " grows to " << balance << "\n";

    return EXIT_SUCCESS;

Change the future_value function to compute the value of the investment when there are npayments regular interest payments per year (instead of 12 monthly payments). Supply a main function that calls your changed function.

What is the value of a $2000 investment after 6 years at 12 percent if interest is compounded quarterly?

Change the future_value function to accept holding an investment for a fractional number of years, fyears. Supply a main function that calls your changed function.

What is the value of a $2000 investment after 6.5 years at 12 percent if interest is compounded weekly (52 weeks/year)?

R2. Function Names

The following code can be used to test a text mode function's interface. It's called a test harness, because any function can be used in it, simply by replacing foo() with the new function call and the desired parameters and returns.

float foo(int n)
/* PURPOSE:  Test harness for calls to console programs
   RECEIVES: Programmer specified parameter
   RETURNS:  Programmer specified return value 
   REMARKS:  Used to test parameter and return passing.
{  cout << "Got to " << "foo" << " with parameter " << n << "\n";
   return n * 0.1;               /* to simulate function activity */

int main()
{  int give = 3;                /* any test value can be entered here */
   float get;
   get = foo(give);
   cout << "Returned from " << "foo" << " with " << get << "\n";

   return EXIT_SUCCESS;

Why are the following functions badly named? Try using them in the test harness. What happened? What names would be better?

R3. Function Comments

Sooner or later, you'll encounter cryptically named and uncommented work like the following.

bool cn(Employee e1, Employee e2)
{  return e1.get_name() == e2.get_name();

bool cs(Employee e1, Employee f)
{  return e1.get_salary() == f.get_salary();

bool c4dup(Employee joe, Employee mary)
{  return  cn(joe, mary) and cs(joe, mary);

int main()
{  Employee john;   
   Employee jane;

   if(c4dup(john, jane))
      cout << "Same" << "\n";
      cout << "Different" << "\n";

   return EXIT_SUCCESS;

What do these functions do ?

Rewrite them with comments and more descriptive function and parameter names. Use:


P2. Return Values

In functions with more complicated branching of control, one way to insure a reasonable return value is to gather together all the possibilities and issue only one return statement from the very end of the block statement. Rewrite the points_of_compass function as follows:

  1. Introduce an additional variable string direction_string
  2. Be more clever about the logic--first compute the major direction (north, east, south, west), then append an east or west if necessary
  3. Return the direction_string from the end of the function only
string points_of_compass(int degrees)
/* PURPOSE:   Convert a numeric compass position to it's verbal equivalent
   RECEIVES:  degrees - the compass needle angle in degrees
   RETURNS:   the value as a compass direction ("N", "NE", ...) 
{  degrees = degrees % 360;
   float octant = degrees / 45.0 - 0.5;
   if (octant >= 7)
      return "North West";
   else if (octant >= 6)
      return "West";
   else if (octant >= 5)
      return "South West";
   else if (octant >= 4)
      return "South";
   else if (octant >= 3)
      return "South East";
   else if (octant >= 2)
      return "East";
   else if (octant >= 1)
      return "North East";
   else if (octant >= 0)
      return "North";
      return "North West";

int main()
{  int degrees;
   cout << "Please enter the compass heading (in degrees): ";
   cin >> degrees;

   string direction = points_of_compass(degrees);
   cout << "You are heading " << direction << "\n";
   return EXIT_SUCCESS;

R4. Parameters

Consider the following functions:

   string lowercase(string s);
   void swap (float& a, float& b);
   float can_volume(float h, float r);

and these variables:

   string greeting = Hello!"
   int a;
   int b;
   float x;

What is wrong with each of the following function calls ?

Side Effects, Procedures and Reference Parameters

Formally, procedures differ from functions in that they do not, ideally, have a return value, or if they do, it's production is not the principal purpose of the procedure. The lack of a return value might result from any of several conditions:

R5. Tracing Reference Parameters

The street gambler's game of 3-Card Monte is deceptively simple. Three cards are placed face down on a table. One of the three is the Queen of Spades, and you are shown where it is. The dealer then re-arranges the cards, and asks "Where is the Queen?"

void swap(string& a, string& b)
{  string temp;
   temp = a;
   a = b;
   b = temp;

int three_card_monte(string card1, string card2, string card3)
{  swap(card1, card2);
   swap(card2, card3);
   swap(card1, card3);
   if (card1 == "queen")
      return 1;
   else if (card2 == "queen")
      return 2;
   else /* (card3 == "queen") */
      return 3;

int main()
{  string first = "queen";
   string second = "king";
   string third = "ace";
   int guess;
   int location;
   location = three_card_monte(first, second, third);
   cout << "Where's the Queen ( 1, 2 or 3 ) ? ";
   cin >> guess;

   if (guess == location)
      cout << "Congratulations!" << "\n";
      cout << "Better luck next time, it was number " << location << "\n";
   return EXIT_SUCCESS;

What are the values of the parameters to three_card_monte(card1, card2, card3)

To what variable do a and b refer in the first, second and third calls to swap(a,b)

call a b

P3. Programming with Reference Parameters

Write a function sort3d() that sorts three integers in decreasing order. You may use:

void sort2d(int& a, int&b)
{   int temp;

    if (a < b)
    {  temp = b;
       a = b;
       b = temp;

R6. Variable scoping

Generally, we want to encourage you to define a variable when you first need it, but you have to pay attention to the scope. Find what's wrong with this function's variable scoping, then fix it.

/* PURPOSE:   Select the maximum of three integer values
   RECEIVES:  ints i, j, k

int maximum(int i, int j, int k)
{  if (i > j)
   {  int a;
      a = i;
   {   a = j;
   if (k > a)
   {   return k;
   {   return a;

P4. Eliminating Global Variables

Global variables may "work", but the advantages they offer are outweighed by the confusion they can cause. Since all functions can set a global variable, it is often difficult to find the guilty party if the global variable is set to the wrong value.

int maximum;

void set_max(int a)
/* PURPOSE:   Updates maximum if parameter is larger 
   RECEIVES:  a - the value to compare against maximum
   REMARKS:   Uses global int maximum  
{  if (maximum < a) 
   {   maximum = a;
void max3(int i, int j, int k)
{  maximum = i;
   return maximum;

int main()
{   cout << "Please enter the first integer: ";
    cin >> i;
    cout << "Please enter the second integer: ";
    cin >> j;
    cout << "Please enter the third integer: ";
    cin >> k;
    maximum = max3(i, j, k);

    cout << "The maximum is " << maximum << "\n"
    return EXIT_SUCCESS;

Re-write max3() to avoid the use of global variables, and to preserve the logic of the function.

P5. Stepwise refinement

A call-tree diagram can help organize a large number of related functions. Consider drawing a house like this one using the code library's graphics functions.

Cylindric volume function chart

It can be done by organizing calls like these:


into a call tree like this

    +---------- draw_front
    |               |
    |               +----------- draw_window (3 times)
    |               |
    |               +----------- draw_door
    +---------- draw_roof

Write a program that implements these functions to draw a house. (Hint: draw_window needs a parameter to specify the location of the window.)

R7. Walkthroughs

Check the arguments accepted by three_card_monte(). What happens if one of them is empty, e.g. three_card_monte("queen","king","") ?

What changes to the function would you recommend ?

P6. Recursion

Consider a function int digits(int) which finds the number of digits needed to represent an integer. For example, digits(125) is 3 because 125 has three digits (1, 2, and 5). The algorithm is defined as:

if n < 10, then digits(n) equals 1. Else, digits(n) equals digits(n / 10) + 1.

(Why? If n is less than 10, one digit is required. Otherwise, n requires one more digit than n/10.)

For example, if called as int num_digits = digits(1457), the following trace results:

= digits(145) + 1
= digits(14) + 1 + 1
= digits(1) + 1 + 1 + 1
= 1 + 1 + 1 + 1

Do a trace of digits(32767)

Write int digits(int) to be called by the following main():

int main()
{  int test_value;

   cout << "Please enter a number " << "\n";
   cin >>  test_value;
   int ndigits = digits(test_value);   
   cout << "You need " << ndigits << " bits to represent " << test_value << " in decimal\n";
   return EXIT_SUCCESS;

Don't forget to send your answers when you're finished.