Courses/CS 2114/Lab Manual/Functions and Value Parameters

From A-State Computer Science Wiki
Jump to: navigation, search

Functions and Value Parameters

Introduction

This lab will introduce the purpose and structure of functions, a fundamental notion in programming.

Topics Covered in this Lab:
  • the structure of C++ functions
  • function prototypes
  • parameter declaration
  • default parameter values
  • return values and types
  • scoping in functions
  • function overloading
Questions Answered in this Lab:
  • What is the purpose of a function?
  • How is a function declared and called?
  • What is meant by a local variable?
  • What is a default parameter value?
  • What does it mean to return a value?
  • What is an overloaded function?
Demonstrable Skills Acquired in this Lab:
  • ability to modularize a program using functions
  • knowledge of function structures, prototypes and scoping
  • knowledge of return values
  • knowledge of default parameter values and function overloading

Divide-and-Conquer Problem Solving

In the programs of this course to date, the single function main has sufficed to express the solution to the problem at hand. When problems become more complex, such a strategy will lead to a large, complicated program that is difficult to understand. The divide-and-conquer strategy for problem solving calls for the initial problem to be broken down into a series of sub-problems, the detailed solutions of which will be taken up later. This approach is a practical way of not only programming, but problem solving in general. For instance, if a car were needed to get to work, one might list the following steps that must be performed in order to get that car and then be able to drive it.

  1. shop for the car
  2. purchase the car
  3. license the car

These steps must be followed in this order; it makes no sense to license a car that has not been purchased, for example. But these steps are not detailed enough to specify how to carry out each of them. That is the nature of the divide-and-conquer strategy; detail is delayed until a later time in order to keep the focus on the initial, larger problem. Each sub-problem of the original problem will eventually be solved in its own turn in the same fashion. When developing a programmed solution, each sub-problem becomes a function.

Functions represent one of the most important concepts in program construction. They allow higher level problem solving to be mirrored in programs; when a problem is broken down into sub-problems, corresponding functions may be written to carry out the individual tasks. These naturally smaller functions are more easily written and understood.

Library Functions

The C++ standard library contains a wide assortment of pre-written functions ready for use in other programs. Collections of these functions are stored together in specific libraries; a program which uses a particular function must specify a #include preprocessor directive for the library defining it. Functions for mathematical operations such as square root, sine, cosine, logarithm and power are stored in the cmath library; programs using these functions must include the cmath library, just as programs using cout and cin must include the iostream library.

Createfile.pngStart a program in file spFunctions1Tutorial.cpp; add the following to function main to make use of the square root function sqrt. Be sure to include the cmath library.
Verbatimcode.pngCode Illustration
   double x = 5;   
   cout << "sqrt(x): " << sqrt(x) <<endl;

The value 2.23607 should be displayed when the program is run.

Another useful function, rand, generates a pseudo-random number between 0 and RAND_MAX, a constant defined along with the rand function in the cstdlib library. The numbers are pseudo-random in that they follow a sequence from a seed planted with a call to function srand. To illustrate, add the following to function main. Be sure to include cstdlib; there is no need for a using statement.

Verbatimcode.pngCode Illustration
   cout << "RAND_MAX: " << RAND_MAX << endl;
   srand (2231);  // start pseudo-random sequence
   for (int i = 1; i <= 8; i++){
      cout << i << "\t" << setw(6) << rand() << endl;
   }
   srand (2231);  // restart sequence w/ same seed
   for (int i = 1; i <= 8; i++){
      cout << i << "\t" << setw(6) << rand() << endl;
   }

Execute the program and verify that the same sequence of eight random numbers appears twice. Change one of the seeds and verify that two different sequences result. Change both seeds to another new value and verify that new but equal sequences are produced.

Information Icon.pngAdditional information:

In real programs it is usually not desirable to generate the same random sequence each time the program is executed. For that reason, it is unwise to use the same value for the PRNG seed as is done here (it is convenient for debugging purposes, which is why it is being done here). Instead, it is common practice to use a function of the current system time to seed the PRNG. By including the ctime library, you can access the current system time (in seconds) using the time() function, so you could seed the PRNG with this value (producing a different random sequence for every second) by calling srand(time(0)). This should be done one time only at the beginning of execution of the program.

Modern C++ (beginning with C++11) also provides a better library specifically designed for generating random numbers. The <random> library provides more options and much higher quality pseudo- and true-random generation than what is provided by rand() alone. For more information see: http://www.cplusplus.com/reference/random/ and http://en.cppreference.com/w/cpp/numeric/random as well as the talk available at https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful.

User-Defined Functions

Each program to this point in the course has utilized one user-defined function, main; consider its basic format.

Pseudocode.pngPseudocode
int main (void)
{
   // body
   return 0;
}  // end function main

Function main follows the format used by most functions.

Information Icon.pngSyntax
returnType functionIdentifier (parameter list)
{
   // body
   return returnValue; // returnValue must be of returnType
}  // end function functionIdentifier

The return type of function main is int; correspondingly, the return value of main is zero. The return value is communicated back to the caller of a function. In the case of main, the caller is outside the scope of the program itself and so need not be discussed here. Not all functions will return a value, more on this later. The parameter list is another means of communication, but it can flow to, as well as from, the function. Function main utilizes no parameter list communication; keyword void explicitly indicates this.

A program may consist of an arbitrarily large number of user-defined functions. A console application must have one function named main as this is where the linker sets up execution to start. (Console applications utilize "console" input and output with cin and cout, respectively.) Add the following to the program before function main.

Verbatimcode.pngCode Illustration
void writeRandoms8 (void)
{
   cout << "function writeRandoms8\n";
   for (int i = 1; i <= 8; i++)
      cout << i << "\t" << setw(6) << rand() << endl;
   return;
}  // end function writeRandoms8

Compare this function with the outline of function main above. Note that the keyword void is used here in two contexts, first to indicate nothing will be returned and second to explicitly indicate an empty parameter list. Corresponding to the void return type, the return statement includes no value. (The return statement is not strictly required in situations like this, but it does serve to document the end of the function.) Extend function main with a call to this function.

Verbatimcode.pngCode Illustration
   writeRandoms8 ();

Execute the program and verify that it works as expected.

Function writeRandoms8 illustrates the mechanics of functions, but it is not really practical in and of itself. In fact, function rand, whose results lie between 0 and RAND_MAX, is of less use than it might be; problems of a statistical nature are typically concerned with probabilities on the range from zero to one. If the results of rand are divided by RAND_MAX, probabilities are produced. Consider a function which will do precisely that.

Verbatimcode.pngCode Illustration
double rand01 (void)
{
   return static_cast <double> (rand()) / RAND_MAX;
}

Compare the return type and return value of this function to those of writeRandoms8. Add the function definition for rand01 (shown above) to your program just below the definition of writeRandoms8. Then, extend function main so that it invokes this function eight times and displays the results on the screen.

Function Prototypes

Each identifier in a C++ program appears in one of two contexts, definition or usage. It stands to reason that the definition of an identifier must precede its usage; otherwise, the compiler will not know what to do with it. Functions writeRandoms8 and rand01 were defined before function main was defined; thus the definitions of the identifiers writeRandoms8 and rand01 precede their usage. Since functions can call one another, the definition-usage ordering becomes more complicated as the number of functions increases. Function prototypes may be used as pre-definitions of functions to allow for any ordering of the function definitions themselves.

A function prototype is essentially the function header followed by a semicolon (rather than a left curly brace, as in a function definition). Add prototypes for functions writeRandoms8 and rand01 to the program immediately following the programs's #include preprocessor directives.

Verbatimcode.pngCode Illustration
void writeRandoms8 (void);
double rand01 (void);

Move the full definitions of these functions so that they follow function main. Execute the program to verify that everything is in place. Without the prototypes, the usages of the function identifiers would precede their respective definitions.

Note that the identifers for functions writeRandoms8 and rand01 each now appear in the program three times; each occurrence has a different purpose. The first occurrence is in the function prototype, telling the compiler what this function will look like but not how it will do its work. The second occurence is within function main in a function call; when the program's execution reaches this point it will jump from there to begin execution of the function. The third and final occurrence is after function main in the function header of the function definition; this is followed immediately by the function body.

Value Parameters

As stated earlier, a function's parameter list can be used to pass information to the function. The previous examples had empty (void) parameter lists as they could perform their tasks without additional information. Consider again function writeRandoms8; it will now be modified to write as many random numbers as requested. Begin by making copies of the function prototype and definition. Change the copies of the prototype and function header as follows.

Verbatimcode.pngCode Illustration
void writeRandoms (int n)  // was writeRandoms8 (void)

Next, change the function body's cout statement to reflect the new name of the function and modify the body's for loop to count from 1 to n, rather than 1 to 8. Finally, extend function main to include a call to the new function as follows.

Verbatimcode.pngCode Illustration
   srand (2231);
   int k = 8;
   writeRandoms (k);

Execute the program and verify that the same eight pseudo-random numbers are generated (following the same seed) by the new function.

The parameter list with which the function is called (within function main) is referred to as the actual parameter list; the list with which the function is defined is the formal parameter list. When a function is called, memory is allocated for each variable in the formal parameter list, and the value of the corresponding actual parameter is then copied into it. For example, calling function writeRandoms creates a new variable n into which the value of k, 8, is copied. Variable n, like every other variable encountered so far, is of the automatic storage class; as such, it "lives and dies" within the curly braces in which it is defined. In this case, n is associated with the curly braces of function writeRandoms; when execution reaches the end of the function, the memory of variable n is released and is no longer accessible. Variable k, having been defined within the curly braces of function main, still exists and will do so until function main reaches its conclusion.

Incidentally, the compiler reads only the types specified in a prototype parameter list; any identifiers present are ignored. Change function writeRandom's prototype list (int n) to (int) and recompile the program to verify that this is so. Typically, the presence of identifiers is helpful in describing the nature of the function's parameter list.

Default Parameter Values

As it stands now, each call to function writeRandoms must provide a parameter indicating how many random numbers are to be displayed. It is possible to specify a default value which will be used should no parameter be given. Change the prototype of writeRandom so that it reads as follows.

Verbatimcode.pngCode Illustration
void writeRandoms (int n = 5);  // default parameter value

(Again, the identifier n in the parameter list may be omitted.) Next, extend function main with the following.

Verbatimcode.pngCode Illustration
   writeRandoms ();  // force use of default parameter value

Finally, modify the function header of writeRandoms definition as follows.

Verbatimcode.pngCode Illustration
void writeRandoms (int n /* =5 */)

Note that this last modification is not strictly required, as it inserts a comment only; however, it serves as a useful reminder in the function definition.

Execute the program and verify that the call to writeRandoms with an empty parameter list does indeed display five random numbers.

Using a Formal Parameter to Obtain a Return Value

A function will now be developed which computes a return value based on its formal parameter. Recall from algebra that the expressions \sqrt{5} and 5^\frac{1}{2} are equivalent. Recall also that logarithms are exponents; specifically, \log_a b is the exponent to which a is raised to get b.


   \log_a b = c \Longleftrightarrow a^c = b

From this, the following can be seen to hold true.


   a^{\log_a b} = b

One of the fundamental properties of logarithms states that the logarithm of a number raised to a power is equivalent to that power multiplied by the logarithm of the number.


   \log_a b^n = n \log_a b

By combining these facets of logarithms, square roots can also be determined using logarithms.


\sqrt{x}  =  10^{\log_{10} \sqrt{x}}


\sqrt{x} =  10^{\log_{10} x^\frac{1}{2}}


\sqrt{x} =  10^{\frac{1}{2} \log_{10} x}

Use the cmath functions pow and log10 to write a function sqrtViaLogs which implements this formula; add it and its prototype to the program. Extend function main with a for loop to use this function to display the square roots of the integers 1 through 9, one to a line; be sure that the index of the for loop, which is being passed to the square root function, is an integer.

Testing

In order to see that the results of function sqrtViaLogs are correct, it might be a good idea to display the results of parallel calls to standard function sqrt. Copy the for loop from the previous section and adjust it to display two square roots per line, one using sqrtViaLogs and the other using sqrt.

Execute the program and verify that the results of both square roots are identical.