cs1ch4sec2.htm
 

Chapter 4
 
Design with Functions


Section II: Functions Section I: Decomposition Section III: Output, Calculation, and Display Functions Section IV: Parameters
Section V: Tracing Section VI: Final Thoughts on Redesigning the Employee Salary Problem

  


Table of Contents

Learning C++:
An Index of Entry Points


2. The

of C++

A reference document on the basic elements of C++.



3. The Patterns



Index!



A. Functional Decomposition
In languages such as C++ the various tasks (modules) in a program are often called functions and this design approach is referred to as Functional Decomposition. One can view functions in C++ as miniature programs and therefore once the decomposition process has determined the functions required, each of those functions must itself be designed - in roughly the same way we design a program. To accomplish this you must AT LEAST create a description for each function that includes:

    1. The purpose of the function;

    2. What the function will 'receive' from any function that calls it;

    3.What the function will 'return' to the calling function when finished.

This is the bare minimum to be included in the description of any function. If you look back at chapter 3 you will find that this is the information we included in 'main'. What was not mentioned back then is that 'main' is a function.

In the last chapter we talked about inputs and outputs but what are these 'receives' and 'returns'? Functions, of course, do something. To accomplish that 'something' one function may need to use another function - in computer science terminology, one function may need to 'CALL' or 'SEND A MESSAGE' to another function. In the process the called function may receive data from the calling function and may return data to the calling function. This is similar to what would happen if I 'called' you to grade a test paper for me. You would receive the test paper and the correct answers from me and return the grade to me. It is also what happens when the president calls a vice-president to request that some action be performed.

As part of your program design you need to determine not only what functions are necessary, but what information must be passed back and forth between functions. To concretize this discussion, let's continue work on the 'Employee Salary' problem. At this point our design and corresponding structure chart for this problem tell us which functions are necessary. Also, the lines in the structure chart indicate function calls. Thus, 'CalculateSalary' calls 'GetRateofPay' and 'GetHoursWorked'. However, we have yet to determine the communication paths between functions. In other words, we need to determine what, if anything, each function needs to receive to accomplish its task and what, if anything, each function will return. One way to work this out for any problem is to ask the following two questions of each function:

    1. What, if any, data does the called function need from the calling function to accomplish its task?

    2. What, if any, data does the calling function expect back?

Note that there is a subtle distinction between the receives-returns pair and the input-output pair. When something is input to a function, it is "sent in" from outside the program. Something that is received by a function is sent from another part of the program - from the "calling" function. The difference between output and return is similar.

For any function that is at all complex, programmers will often include an algorithm. To help you understand the programming process and get practice with good design, you may be required at first to include an algorithm for even the simplest of functions. As your programming and design skills improve, this requirement will be relaxed - as will some of the others listed here. The idea is that at first you need practice explicitly writing down your design decisions. Later, after it becomes second nature to make them properly, it will be OK to simply do them in your head. However, you will always need to know the purpose of a function you are about to write, as well as its receives, returns and algorithm - whether you write them down or not!

B. Function Design and Basic Input Functions
Having said all this, let's design some of the functions for the Employee Salary problem. We will start with GetRateOfPay. To make our purpose statements as precise as possible, we will include 'Goal' statements. Later we will drop these.

GetRateofPay
Purpose: To get the rate of pay for an employee from a user
Goal:      <rateOfPay>, a double, contains the rate of pay for an employee

So far so good and fairly obvious. The only point that might be at all questionable is the type of the variable <rateOfPay> . (Remember that we will use the notation <...> to represent a variable. ) Here is the advantage of the analysis phase and a carefully developed set of specifications. From the analysis we did of this problem back in chapter two, we know that 'rateOfPay' involves money and therefore is of type 'double'.

Now, let's ask the first of our 'receive/return' questions:

    What, if any, data does the called function need from the calling function to accomplish its task?

This is not some obscure, technical question and it can usually be answered in a very practical way. For example, if your boss requests you to go ask the first person you see on the street how much he or she is paid by the hour (not a very wise question to ask in this day and age but let's just pretend), do you need any further information from your boss? No, you just go out and ask that perfect stranger how much he or she is paid!

Similarly, the 'GetRateOfPay' function does not need any information from the 'calling' function. Therefore, we can say that it 'receives' nothing. Now for the second question:

    What, if any, data does the calling function expect back?

Again, the analysis is based on common sense. Does your boss expect any information back from you after you ask that perfect stranger what he or she is paid? I don't know about your boss but mine would expect to get back the rate of pay of the person. So, one piece of data is returned - the rate of pay. Similarly with the 'GetRateOfPay' function. Any function that calls 'GetRateOfPay' expects to get back the rate of pay the user enters so the function returns one item. Here is how the design looks so far.

    GetRateofPay
    Purpose:	To get the rate of pay for an employee from a user
    Goal:		 <rateOfPay>, a double,  contains the rate of pay for an employee
    
    Receives:	Nothing
    Returns	        <rateOfPay >
    

Now for the algorithm - and it couldn't be simpler:

Get the rate of pay from the user

What this will mean in code is that we will write a prompt with a 'cout' statement and then get the input with a 'cin' statement. We actually have a very simple but important pattern here. We actually have a very simple but important pattern here. Whenever the purpose of a function is to get a value from a user, we always have the same basic lines of code - one or more 'cout's' to present a prompt and a 'cin' to input the value requested.

We also have a new kind of pattern here - a design pattern. All functions where the purpose is to "get a value from a user" follow the same basic design:

Pattern A: Basic Input

	Purpose: 	To input or get some value(s) from a user
	Goal: 		The appropriate variable(s) contain the value(s)
Receives: Nothing Returns: The variable(s)
Algorithm: Get the value(s) for the variable(s) from the user
Note how this general pattern is realized in the design for 'GetRateOfPay'.

We will return to the 'Employee Salary' problem in a bit but first let's look at a second example of this new pattern. In chapter 3 we had a number of variations of a program where the user was to enter the price of items to be discounted. A decomposition of this problem would lead us to the conclusion that one of the tasks was to get the price. Since this function has as its purpose, "to get a value from a user", it follows the "Basic Input" pattern used in the 'GetRateOfPay' function. Here is the design:

    GetPrice
    Purpose:	To get the price of an item to be discounted from a user
    Goal:		<price>, a double,  contains the price
    
    Receives:	Nothing
    Returns	       <price>
    
    Algorithm:
    	Get the price of an item from the user
    
Study this carefully to understand how it makes use of the "Basic Input" pattern and how similar it is to the design of 'GetRateOfPay'. Note how the algorithm for 'GetPrice' is essentially the same one-liner found in 'GetRateOfPay'. This means that the implementation of this algorithm will be very similar to that of the algorithm in 'GetRateOfPay' - one or more 'cout's' to present a prompt and a 'cin' to input the value requested.

In many of the programs you will write, there will be one or more functions whose design will be based on the "Basic Input Pattern". In fact, there is a second function in the Employee Salary Problem that should use this pattern - the 'GetHoursWorked' function - see fig 4.1 and the box "Get Employee Hours Worked". Since its design will be so similar to what we have just seen, we will not discuss it further. However, you might want to try it on your own.

C. Function Declarations
Functions, just like variables and constants, need to be declared. They also need to be defined - as do variables and constants - and it is time to state more clearly the difference between a declaration and a definition . When an identifier (the name for some C++ element such as a variable, constant, or function) is declared, we are announcing the existence of this element to the program, but we are not requesting that the system set aside memory. The act of defining the element sets aside memory. With variables and constants, as they are used in our programs, this declaring and defining takes place at the same time. Functions can also be declared and defined at the same time, but it is usually better not to do so for a number of reasons that we will skip for now.

The difference between a function declaration and definition is essentially this: in the function declaration we state the name of the function and its receives and returns. We do not, however, include the code for the actions of the function. They are part of the definition because it is they that take up memory.

A function can be declared any number of times - this just means that we are announcing the existence of the function and if that announcement takes place more than once, no big deal. However, if we define a function twice, we are asking the system to set aside memory twice for the instructions (code) in the function - with both memory locations referred to with the same name, the name of the function. The problem is that when the function is called, the system will not know which set of instructions to execute and computers HATE ambiguity.

Imagine an office complex with a big book containing procedures to be followed for all the situations employees can encounter. Now suppose that an employee encounters situation 'X', but when she looks up what to do, she finds a procedure 'X' on page 25 and a different procedure 'X' on page 101. Which one should she follow? In the case of a program, we may know that both sets of instructions are the same, but the computer will not know this and you will get a compile error.

Enough talk, let's declare a function. We will start with 'GetRateOfPay' since we have carefully worked on its design..

double GetRateOfPay();

The name of the function is 'GetRateOfPay'. Note that the first letter of the name is capitalized. C++ itself does not care whether we use upper or lower case letters but this distinguishes for the programmer a function name from a variable name - see style sheet . In front of the name is the type identifier 'double'. This indicates that the function returns a double. In the design we said that GetRateOfPay returns the rate of pay which we decided would be of type 'double'. The type of the value returned by a function always appears in front of the function name. We will see that some functions return nothing. In that case the word void appears in front of the function.

After the function name there is a set of parentheses with nothing inside of them. This indicates that this function receives nothing. - which is correct since our analysis of 'GetRateOfPay' conclude that it received nothing. Later we will see the code for functions that do receive data of some type.

D. Using Functions in Programs
Now we need to explore how to use a function such as 'GetRateOfPay' in a program. Take a look at the code below:

    // A program to calculate the salaries for 10 employees using functions and the 'for' loop
    // File: ch4prg1.cpp
    
    // The Specifications go here
    
    #include <iostream.h>
    
    const  int REGULAR_HOURS = 40; // Number of hours before overtime kicks in
    const  double OVERTIME_ADJUSTMENT = 1.5;  // Overtime pay adjustment
    const  int NUM_TIMES = 10;	// Number of employees to work with
    
    
    double GetRateOfPay();
    /*
    Purpose:	To get the rate of pay for an employee from a user
    Goal:		 <rateOfPay>, a double,  contains the rate of pay for an employee
    
    Receives:	Nothing
    Returns		<rateOfPay>
    */
    
    void main()
    // Purpose: 	to calculate salaries for 10 employees
    // Receives:	NONE
    // Returns:	NONE
    	
    {
    	double rateOfPay;
    	double overtimeRateOfPay;
    	
    	double regularSalary;
    	double overtimeSalary;
    	double fullSalary;	  
    	double hoursWorked;  // can be a fraction of an hour
    
    
    	
    
    	for (int count = 0; count < NUM_TIMES; count++)
    
    	{	rateOfPay= GetRateOfPay();
    		cout << "Please enter the hours worked this week for this employee " ;
    		cin >> hoursWorked;
    		if (hoursWorked > REGULAR_HOURS)
    		{	regularSalary = REGULAR_HOURS * rateOfPay;
    			overtimeRateOfPay = rateOfPay * OVERTIME_ADJUSTMENT;
    			overtimeSalary = (hoursWorked - REGULAR_HOURS) * 
    						  overtimeRateOfPay; 
    			fullSalary = regularSalary + overtimeSalary;
    		}
    		else
    		{ 	fullSalary	 = hoursWorked * rateOfPay;
    		}
    
    		cout << "The employee's salary is: " << fullSalary << endl;
    
    	} 
    
    }
    
At the top of the code you see the function declaration, "double GetRateOfPay();". A few lines below that you see the line: "void main()". We have not said much about this but 'main' is simply another function! The function 'main', however, is special in that it begins all C++ programs. In other words, C++ programs begin by executing the first instruction inside the function 'main' and you cannot have a C++ program without a 'main'. It is also unlike other functions in that we both declare and define it at the same time.

Knowing that 'main' is a function helps explain the lines:

    // Purpose: to calculate salaries for 10 employees
    // Receives: NONE
    // Returns: NONE

inside the function. This function receives nothing and returns nothing. In fact the receive/return part of the design for 'main' in our programs will always appear as:

    // Receives: NONE
    // Returns: NONE

Unlike the function 'GetRateOfPay', notice that right below the declaration of 'main' is its definition - its implementation code. This is the only function we will write in this way.

Inside the function 'main' there is a place where the code:

cout << "Please enter your rate of pay";

cin >> rateOfPay;

used to appear. Find this location in the original code. Note that this code has been replaced with the single line:

rateOfPay = GetRateOfPay();

This is the function call. When this line is executed, the computer will execute the code in the memory locations where the code for the 'GetRateOfPay' function is stored. Since the parentheses are empty, nothing will be sent to (passed) to the function from 'main' while the value that 'GetRateOfPay' returns will be stored in the memory location symbolized by the variable 'rateOfPay'.

E. Function Definitions
As just mentioned, when the next instruction in a program is a function call, the computer will attempt to execute the code for the function being called. In our example then, the computer will attempt to execute the code for 'GetRateOfPay'. But where is the code for this function? It is not here! And, if we tried to translate this code as it stands, we would get an error message something like:

Linker Error: Undefined symbol GetRateOfPay....

Two points here. First, note that the error says that the symbol is 'undefined', not undeclared. The system can find the symbol but it cannot find the definition for it - there is no address containing the code that corresponds to the function name 'GetRateOfPay'. Second, this is not a compiler error but a linker error. The linker is a second piece of software that comes along after the compiler to 'link' or connect function names with the code that is supposed to be associated with the function name. In chapter 2 we used the phrase "the memory location associated with...." The linker makes the connection between the name (of a function, variable or constant) and the memory that name symbolizes or is associated with.

    double GetRateOfPay()
    {	double payRate;
    	cout << "Please enter an employee's rate of pay ";
    	cin >> payRate;
    
    	  return payRate;
    }
    
We declare a variable 'payRate'; tell the user we want an employee's rate of pay; place what the user enters into 'payRate'; and return the contents of 'payRate'. Notice how the code, with the exception of the last line, is the same as what we have been seeing all along. Functions are simply a way of organizing or structuring code.

The instruction return is new but it does exactly what it says - it is the function's mechanism for returning a value. A function can only return one value using the return instruction. If our design indicates that a function returns nothing, the return instruction is skipped. We will see later how to return more than one value. (The fact that functions in C++ can return no more than one value should tip you off that these are not exactly mathematical functions.)

A word about the variable 'payRate' and the use of variables inside functions: Variables like 'payRate' that are declared inside functions are said to be local variables. What this means is first, that they are 'known' only inside the function. You can visualize a function as a private room inside which some operation is performed. If that operation requires that the operator take notes or write down some temporary value, the outside world need not have any knowledge about what was written down or what the operator called the temporary value. All the outside world cares about is the value returned.

In the case of 'GetRateOfPay' the variable 'payRate' is local - the rest of the program knows nothing about it and, indeed, a different function in some other part of the program could also have the same variable name. Using the same 'private room' analogy, there could be one room in which the daily sales of vegetables are summed up and a clerk calls the result 'theSum' and there could be another room in which the daily sales of meat are summed up and another clerk again calls the result, 'theSum'. As long as both clerks stick to themselves, there is no problem with them using the same name for two different calculations.

To emphasize this, it should be pointed out that we could change the name of 'payRate' to 'rateOfPay - the variable name found in 'main'. This was not done originally to avoid confusion on your part but the machine does not care. It easily keeps track of the fact that one 'rateOfPay' variable belongs to the function 'main' and one to the function 'GetRateOfPay'.

The second important fact about local variables is that they exist in memory only as long as the code in the function is executing. Thus, only when 'GetRateOfPay' is called is memory set aside for the local variable 'payRate'. Likewise, this memory for 'payRate' is returned to the system when 'GetRateOfPay' executes the 'return' instruction and stops.

One little, ugly point that gets programmers all the time: A declaration ends with a semicolon BUT, neither the first line of a function definition nor the whole definition, ends with a semicolon. You can think of this as something designed just to keep you on your toes.

It is now time to put the whole program together:

    
    
    // A program to calculate the salaries for 10 employees using functions and the 'for' loop
    // File: ch4prg1.cpp
    
    // The specifications go here
    
    #include <iostream.h>
    
    const  int REGULAR_HOURS = 40; // Number of hours before overtime kicks in
    const  double OVERTIME_ADJUSTMENT = 1.5;  // Overtime pay adjustment
    const  int NUM_TIMES = 10;	// Number of employees to work with
    
    
    double GetRateOfPay();
    /*
    Purpose:	To get the rate of pay for an employee from a user
    Goal:		 <rateOfPay>, a double,  contains the rate of pay for an employee
    
    Receives:	Nothing
    Returns		<rateOfPay>
    */
    
    void main()
    // Purpose: 	to calculate salaries for 10 employees
    // Receives:	NONE
    // Returns:	NONE
    {
    	double rateOfPay;
    	double overtimeRateOfPay;
    	
    	double regularSalary;
    	double overtimeSalary;
    	double fullSalary;		  
    	double hoursWorked;  // can be a fraction of an hour
    
    	for (int count = 0; count < NUM_TIMES; count++)
    
    	{	rateOfPay = GetRateOfPay();
    		cout << "Please enter the hours worked this week for this employee ";
    		cin >> hoursWorked;
    		if (hoursWorked > REGULAR_HOURS)
    		{	regularSalary = REGULAR_HOURS * rateOfPay;
    			overtimeRateOfPay = rateOfPay * OVERTIME_ADJUSTMENT;
    			overtimeSalary = (hoursWorked - REGULAR_HOURS) * 
    						  overtimeRateOfPay; 
    			fullSalary = regularSalary + overtimeSalary;
    		}
    		else
    		{ 	fullSalary	 = hoursWorked * rateOfPay;
    		}
    
    		cout << "The employee's salary is: " << fullSalary << endl;
    
    	} 
    
    }
    
    double GetRateOfPay()
    {	double payRate;
    	cout << "Please enter an employee's rate of pay ";
                cin >> payRate;
    	return payRate;
    }
    
The declarations for all functions but 'main' go at the top of the file. Then the function 'main is declared and defined, and finally, the definitions for all other declared functions come after 'main'. When the function 'main' is executed, the function 'GetRateOfPay' is called - over and over again, as many times as the loop is executed. Each time 'GetRateOfPay' is called, the local variable 'payRate' comes into existence (has memory set aside for it) and is given a value by the user. That value is then returned to 'main' and stored in the variable 'rateOfPay' to complete the work performed by 'GetRateOfPay'. At the same time, the memory for the local variable is returned to the system. The value in 'rateOfPay' is then used in the calculations inside the loop. Notice that each time the function 'GetRateOfPay' finishes execution it returns a value to 'rateOfPay' and then the system executes the first line in the calling function ('main') after the function call. In other words, the code to get the hours worked is executed next.

Note also that the name 'GetRateOfPay' is used three times in this program - to declare the function, to define it, and to call it. When you write code be sure that you spell your function (and variable as well as constant) names exactly the same each time. Remember that C++ is case sensitive meaning that 'GetRateOfPay' is not the same as 'getRateOfPay'.

F. Why Use Functional Decomposition
Most students at this point object - why should we go through all the hassle of writing functions like this when the original code worked so well and this function does so little. Remember, however, our original goals for using modules (functions). We wanted to break the code up into tasks:

  1. that could be re-used elsewhere with little or no change,

  2. that were so simple that they could be easily coded,

  3. that were independent and therefore easily changeable without worrying about the effects those changes might have on other parts of the code.

The fact that we have discovered a "user input" pattern for functions like this means we have accomplished the first goal; the code is certainly simple enough thus accomplishing the second goal; and we now explore the third goal.

Suppose that tomorrow the boss comes in and says:

    "That code you wrote to calculate salaries - well, it seems that the data entry clerks are making mistakes, entering negative numbers and huge rate of pay values. I know you can't catch all possible errors but could you fix the code to catch the worst of them? "

You don't have to go through all the code looking for all the places where the salary is asked for (imagine that this is a larger, more complex program) before you can make any changes. All you need to do is find the 'GetRateOfPay' function and modify it. Here is an example of the modifications you might make.

    double GetRateOfPay()
    {	double payRate;
    	cout << "Please enter an employee's rate of pay ";
                cin >> payRate;
    	while ( (payRate < 0) || (payRate > 50.00) )
    	{	cout << "An invalid pay rate has been entered.";
    		cout << "Please enter a valid rate of pay "
    		cin >> payRate;
    	}
    	return payRate;
    }
    
Make sure you understand this code as it represents yet another pattern - we'll call it the "Clean Handling of Invalid Data " pattern. The user enters a value. If the value is valid (however that is defined in the problem at hand) everything is OK and the value is returned. If not, a loop is entered and not exited until a valid value is input. Note that we can make this data validation code as complex as we want but anyone looking at the rest of the code won't care a bit - the call to this function remains the same. Further, it's complexity does not add anything to the complexity of 'main'.

Top of Section Main Menu Next Section