Computer Science 1511
Computer Science I

Laboratory Assignment 6
Function Returns and Reference Parameters
Due at the end of Lab

Introduction

One of the key principles of modular programming is that the function header should indicate all of the data that a function receives plus all of the data that it produces (returns). We want to avoid using global variables because when using a global variable we might inadvertently change a global variable used by another function simply because we used the same name (this is especially likely to happen in large programs). In C, there are two methods for a function to produce data: the return value of the function and reference parameters.

Return value

We use the return value of a function to return data in cases where the function (1) returns only a single data value, and (2) we expect to use that value as part of other expressions. For example, we may have a formula for calculating the tax on a sale that is somewhat complex:

  float CalculateTax(float saleamount) {
    float tax;
    
    tax = 0.05 + 0.085 * saleamount;

    return tax;
  }
The header of the function shows that the function receives 1 piece of data, the saleamount (which is a float), and returns a floating point value (the float at the start of the header). The body of the function can be anything but must include a return statement. This statement tells the computer the function is done, and what value the function calculated. This value is then returned as the value of the function call, where it can be used in an expression. For example, we might use the following call:
  thetax = CalculateTax(10.00);
In the function call, the value 10.00 is used as the value for the parameter saleamount in the function CalculateTax. The value (0.90) is then calculated. This value is returned as the value of the function call. Returning to the call we take the 0.90 calculated and store it in the variable thetax.

Reference parameters

If we want to change more than 1 value or we are changing 1 value but don't want that value to be used in an expression, then we should use reference parameters. In C, the parameters of a function are local variables, so any change we make to them does not affect a variable outside of the function. But there is a way around this limitation. What we can do is pass not the value of a variable we want to change, but the location of the variable we want to change (we pass the address of the variable in the computers memory). Then, to use that parameter we change not the value of the parameter, but the value at the address the parameter stores. These parameters are called reference parameters.

In fact, you have already used a reference parameter. The scanf function takes a string argument then 1 or more reference parameters. Each reference parameter is the name of a variable preceded by the ampersand (&) character. The ampersand is actually a C operator that looks up the address of the variable following it. Then within the function, we can change the value of the variable by indirectly addressing the variable with the * operator. A * in front of a variable says that we are interested in the memory at the location stored in the variable. Consider the following function:

void AddInterest(float *currsavings) {
  *currsavings = *currsavings + *currsavings * 0.05;
}
Now imagine we have the following code fragment:
  float savings;

  savings = 1000.00;

  AddInterest(&savings);

The fragment begins with a declaration of a variable named savings which is of type float. The computer allocates enough memory for a floating point value at some location in its memory. The computer keeps track of its memory by giving each location a number -- its address. For example, lets pretend that when it allocates space for savings that the computer allocates 4 bytes starting at the memory address 258743. So when we assign a value to the variable savings we are actually storing a representation of that value (1000.00) at the 4 bytes starting at memory address 258743. Then we have the function call.

The one argument of the function corresponds to a reference parameter (it is a reference parameter because it has a * in front of the variable name). The argument &savings indicates we should pass the address (location) of savings as the value to the parameter. So currsavings gets the value 258743. We then run function. To run the function we calculate one complex expression. First we multiply *currsavings * 0.05. currsavings is the address 258743, so using the indirection operator (the * in front of currsavings), we look up the floating point value at memory location 258743 (which is 1000.0) and perform the multiplication (to get 50.00). Then we add this value to *currsavings (1000.0) getting 1050.00, then store this value in *currsavings (changing it to 1050.00). The function then ends and the parameter currsavings goes away, but the change to the variable at the location 258743 (savings) remains. So the result of the function is that the variable savings is changed to 1050.00.

The Program

The program below shows an example of a function with reference parameters (ReadData) and a function that returns a value (CalcAverage). Compile the program and see what it produces. The program calculates the batting average of a baseball player by reading in a number of atbats and a number of hits. This data is read in using the function ReadData and the average is calculated using the function Calcaverage.


#include <stdio.h>

void Intro( void ) {
  printf("This program calculates the batting average for a baseball\n");
  printf("player after reading the #atbats and #hits for the player.\n\n");
}

void ReadData(int *atbats, int *hits) {
  printf("Note that the variable atbats is actually the address of the\n");
  printf("first argument in the call to ReadData, while *atbats is the\n");
  printf("number stored at that address.\n");
  printf("atbats = %d, while *atbats = %d\n\n",atbats,*atbats);

  printf("Please enter the number of atbats for the player: ");
  scanf("%d",atbats); /* Don't need & in front of atbats because atbats */
		      /* IS an address - the address of the argument */
  printf("*atbats is now %d\n",*atbats);

  printf("Please enter the number of hits for the player: ");
  scanf("%d",hits);
  printf("*hits is now %d\n\n",*hits);
}
  
float CalcAverage(int atbats, int hits) {
  float theaverage;

  theaverage = (float) hits / atbats;

  /* Need a return to indicate what CalcAverage calculates */
  return theaverage;
}

int main( void ) {
  int numatbats = 0;
  int numhits = 0;
  float avg;

  Intro();

  ReadData(&numatbats,&numhits);

  avg = CalcAverage(numatbats,numhits);

  printf("Batting average is %1.3f\n",avg);

  fflush(stdin);
  getchar();
  return 0;
}

What to do

Update the program so that it calculates the on-base percentage for a player as well as the average. For our purposes we will assume that the on-base percentage can be produced by the formula:

  (hits + walks) / (atbats + walks)
To do this you should make two additions:

What to turn in

Turn in a hard copy of your final program. Also, turn in sample output showing that your code calculates the correct On-Base Percentage for several sets of sample data.