Name Use Pattern - Part 2 - Functions
Pattern Name:
Name Use Pattern - Part 2 - Functions
Author:
Viera K. Proulx
Intent and Motivation:
Computer program works with different types of information - numbers, words, codes representing colors, files used by the program and many others. Each of these items needs to be uniquely identified within a program. The word identifier is used to describe any name used in most programming languages.
In this pattern we look at the names that represent functions. We can think of functions as short segments of a program that accomplish a specific task. We need to give each function a name, specify how it will be used (define the interface), and then define the program segment that will be executed each time the function name is called (used, invoked).
Problem Examples:
Problem and Context:
We are all used to working with functions in the context of mathematical computations. Probably the most familiar examples are the trigonometric functions (sin, cos, tan, and others) as well as log, sqrt (representing square root). In each of these cases, when we supply the value of the argument x, the function returns a value computed according to the appropriate formula. Other functions such as PaintCircle may not return any information back to the user, but perform a specific task, using the user supplied values to determine how the task is to be carried out.
In order to use a function the user needs to know its name, and also how many and what kind of arguments need to be supplied to the function. The user needs to know what is the purpose of the function, what will it do, but does not need to know how the function accomplishes its task. When we press the sin button on a calculator, we have no idea how the computation was performed, we just know what is the meaning of the resulting value. In computer programs, when we declare a function, we specify its signature - the interface with the user - the number and types of arguments that have to be supplied to the function.
Before the function can be used, we also need to write the program code that will specify how the function should accomplish its task. This means, we have to define the meaning or behavior of the function.
Finally, the function can be used. The user "calls" or "invokes" a function, supplying the required number and kind of arguments.
Required Elements and Structure:
Declaring a function name and signature
Function name is never defined alone. It is closely tied to the function signature, sometimes called also function header. The function signature specifies the interface between the function writer and the function user. The function signature consists of three parts:
To declare a function we select a name that will represent its purpose. We then need to consider what information needs to be supplied by the user and what information needs to be communicated back to the user. In some cases, the function may modify existing values. For example, a function called Increment(x) will read the existing value of the variable x, increment it, and store the result as a new value of the variable x.
When specifying the behavior of a function we actually are invoking the Read-Process-Write Pattern. The first step of this pattern corresponds partially to the function declaration step. The function writer needs to identify the inputs (or the information needed in this segment of code) and determine how these inputs will be supplied. Those that need to be supplied by the caller will become function arguments. If the function needs to read input from other sources as part of its task - that is of no concern here. However, the third step of the Read-Process-Write Pattern is also needed here. The function writer needs to identify the outputs of the function - specifically the information that will be made available to the caller. Again, we may generate other output, such as printed text, graphics, or sound. If this output is not returned to the caller, it does not affect the design of the function signature.
So, the function designer first has to identify the inputs (or the information needed in this segment of code) and determine how these inputs will be supplied. It then tells us to determine the nature of computation or action that the code segment will perform. Finally, the pattern tells us to identify what information will be written - returned to the user as a result of this code segment - and of course, how will this be accomplished. When designing the function signature (interface) only the nature of the inputs and outputs needs to be specified.
Example 1:
We first write a function that increments an integer. There are two ways we can design this simple function. In the first case, the user supplies the integer value (that does not need to be stored as an integer variable, but can be computed as the function call is made), by using the value argument. The function returns the incremented value as the function return value. We show the function signature, function definition, and an example of use:
// signature (declaration)
int Increment(int n);
// definition
int Increment(int n){
return (n+1);
};
// use
int x = 25;
x = Increment(x);
int y = Increment(x);
cout <<x << " --- " << y << " --- " << Increment(y) << endl;
// this will print "26 --- 27 --- 28"
In the second case the user supplies a name of the variable whose value should be incremented. The function code will be able to read the value of this variable and change it. We say that the argument is passed by reference and indicate this in the signature by following the data type with an &.
The function return value here is specified as void. This means that the function call does not result in any usable data value and so cannot be used in any computation or in the output stream.
We show the function signature, function definition, and an example of use:
// signature (declaration)
void Increment(int& n);
// definition
void Increment(int& n){
n = n+1;
};
// use
int x = 25;
cout << x << " ~~~ ";
Increment(x);
cout << x << endl;
// this will print "25 ~~~ 26"
Example 2.
Suppose, the function named F needs two values of the type int and will return one value of the type double and a boolean value. Its signature (and declaration) can be one of the following:
double F(int, int, bool&);
bool F(int, int, double&);
void F(int, int, double&, bool&);
void F(int, int, bool&, double&);
Notice carefully that the double and bool arguments are passed by reference, while the two int arguments are passed by value. In the first two cases the user receives the computed value as a function return value replaces instead of through the reference arguments.
For example, we may define a function
double Divide(int, int, bool&);
that will return as function value the quotient of the two integers. However, if we try to divide by zero, the return value will be 0 and the bool variable specified as the last argument will be set to false.
The two first arguments - both of the type int - are passed by value. That means user can specify in their place any value of the desired type - even an arithmetic expression, or even a function that returns the desired type. This is the same as in mathematics where we are allowed to write:
y = sin(log(n));
z = max(sin(x), cos(x));
We may select any one of these four choices - depending on what is the most natural way for the user to call the function.
The first two functions return the values to the caller in such way that the user can place the function anywhere where a value of the given type (double resp. bool) can be used. So, in the first case, user may write:
double y = F(a, b, choice) * F (b, c, choice);
In the second case, the user may write:
while (F(a, b, y)){
cout << y << endl;
a++; b--;
};
In the last two cases, where the writer declines to return a function value (indicated by the reserved word void) the user issues a "procedure-like" function call. That means, the name of the function together with the supplied arguments is a standalone statement:
F(a, b, y, choice);
F(a, b, choice, y);
Finally, we often write the function declarations by specifying names of the variables that will be associated with the arguments when the function is defined (implemented). This is useful for letting the caller know how the arguments will be used. For example, the function PaintCircle can have either of the two following declarations:
PaintCircle(int, int, int);
PaintCircle(int x, int y, int radius);
however, the second method is much more meaningful, and so is most often used. Of course, user does not need to use those names, the function calls can be:
PaintCircle(a, b, 10);
PaintCircle(x, x, 10);
PaintCircle(20, x+20, rad);
As an additional example, we show how our function
Divide can be used:int num = 5;
int den = 2;
bool OK;
double result = Divide(num, den, OK);
if (OK)
cout << result << endl;
else
cout << "Division by zero attempted" << endl;
Defining a function implementation
Defining a function implementation requires the same kind of analysis, design, and programming as writing any part of a program. The constraints - what information is available, what information needs to be computed and returned to the caller, what other effects (side effects) should function perform - is the core of the agreement between the function writer and function user. These constraints should be clearly specified, so the user knows exactly what effect will the function have. As mentioned before, this is really an application of the Read-Process-Write Pattern.
For example, the function DrawLine(x1, y1, x2, y2) will not change the values of the four arguments, will not return any value to the caller, but will have the following two side effects:
A line will be drawn in the graphics window and the value of hidden point representing the current location of the pen will be changed to (x2, y2).
There are two considerations here. The function can use all arguments supplied to it as variables of a specified type. The value arguments will be destroyed when the function exits. The values of reference arguments will become new values of the variables specified in the function call. In addition, the function writer can define temporary variables - these are valid only while the function is performing its task - they are invalidated as the function exits.
Using a function
Once a function has been defined, it can be called by the user, provided the right number and kind of arguments is supplied. Functions with defined return value can be used anywhere where a data value of the specified type can be used. Functions that return void are called in a standalone function call.
Implementation Examples:
The following program segments show several uses of this pattern.
// example 1
int Max(int a, int b); // declaration
int Max(int a, int b){ // definition
if (a > b)
return a;
else
return b;
}
;// use of the function:
int x = 20; int y = 15;
cout << " maximum of " << x << " and " << y << " is ";
cout << Max(x, y) << endl;
cout << " maximum of " << 20 - x << " and " << y + 10 << " is ";
cout << Max(20 - x, y + 10) << endl;
int x = 25;
// example 2
// divide numerator by denominator
// return the quotient and the remainder
int Divide(int num, int den, int& remainder){
remainder = num % den;
return(num / den);
};
// use of the function:
int x =25;
int y = 3;
int r;
int q = Divide(x, y, r);
cout << "quotient: " << q << " remainder: " << r << endl;
// example 3
// divide numerator by denominator
// return the quotient and the remainder
int Divide(int num, int den, int& remainder){
int temp = num / den; // temporary variable
remainder = num % den;
return(temp);
};
// use of the function:
int x =25;
int y = 3;
int r;
cout << (x + 8) << " / " << (y + 1) << " = ";
cout << Divide(x + 8, y + 1, r) << " with remainder " << r << endl;
// example 4
// divide numerator by denominator
// return the quotient and the remainder
void Divide(int num, int den, int& quotient, int& remainder){
remainder = num % den;
return(num / den);
};
// use of the function:
int x =25;
int y = 3;
int r;
Divide(x, y, q, r);
cout << "quotient: " << q << " remainder: " << r << endl;
Forces and Variations:
Class member functions are declared and defined in the same way, but the declaration must be inside of the class definition, and there is a specific syntax for defining class member functions outside of the class definition.
The call syntax is also a bit different - the object in the class invokes a member function, and so the call needs to identify which object requests the function to be performed. Additionally, the function then has access to all the object's member data directly, without the need of supplying these as arguments.
It is possible to specify default values for the function arguments - the discussion of this is left to the language textbooks.
Summary:
When declaring a function name, we need to also declare its signature or interface - the number and kinds of arguments the function will work with. The interface tells the user how the function can be used in the program. The definition of the function is also called the implementation. It specifies how the function is going to accomplish its task. The user does not need to know anything about the implementation - as long as the interface and the function's side effects are clearly specified.
There are three kinds of arguments in a function: two are passed arguments (by value or by reference) and the third one is a return value. If the function has a return value, the function can appear in the code anywhere where a value of that type could appear.