Functions Functions without Arguments One way a programmer will use top-down design is by creating functions. The programmer will often write on function for each of the subproblems in the structure chart. In this section will focus on simple function design (without arguments and return values). A structure chart for drawing a house might look like this: Draw House Draw Triangle Draw Intersecting Lines Draw Parallel Lines Draw Base Line Draw Base Line In this example, we could design four functions to handle the subproblems or parts of the structure chart. We could use the main function to actually draw the house by calling each of the functions at the appropriate time. Functions // Draws a house (main function only) #include <iostream> using namespace std; // Functions used . . . void drawTriangle(); // Draws a triangle void drawIntersect(); // Draws intersecting lines void drawBase(); // Draws a horizontal line void drawParallel(); // Draws parallel lines int main() { // Draw a triangle drawTriangle(); // Draw parallel lines drawParallel(); // Draw a horizontal line drawBase(); return (0); } Inside main() there are three function calls, that pass no arguments and return no values. Each call activates the function named, causing it to be executed. When execution completes, control is passed back to main() and the next line in main() is executed. Functions Function Prototypes Just like any other identifier in C++, a function must be declared before it can be used. Inserting a function prototype before the main() function is one way to declare a function. The prototype gives the compiler information about the function: the data type of the function the function name the data types and number of arguments. The data type of the function is determined by the data type of the value that will be returned by the function. When a function returns no value, we use the reserved word, void, as its data type: void drawTriangle(); The empty parentheses after the function name mean that the function accepts no arguments. Note: Function prototypes must always end in a semicolon. They are just declarations and do not define the actual function. Functions Function Definitions The prototype is used to provide the compiler with some basic information about the function so that the programmer’s use of the function call can be verified for accuracy. It does NOT provide the operation detail of the function. You need to define a function body for each of the subproblems in the same way that you define the main() function. To define a function, you need a function header. The header is similar to the prototype, except that there is no semicolon. Instead of the semicolon, you provide a function body enclosed in a set of braces {}. We could define the function to draw parallel lines, as follows: // Draws parallel lines void drawParallel() { cout << “ | | ” << endl; cout << “ | | ” << endl; cout << “ | | ” << endl; return; } Since this function is of type void, the return statement is not necessary. However, it is a good programming practice to include one anyway. Functions Each function body could be considered a self-contained miniprogram. It can contain its own variable declarations. Those variables declared inside a function body are considered to be local to that function and can be accessed only from within that function. (More later) In-Class Exercise: 1. Write a function that draws three blank lines. Answer: [ void drawBlanks ( ) { cout << endl << endl << endl; return; } ] Placement of Functions and Prototypes The function prototypes for the functions to be used in a program are normally placed above the main function after any #include directives. The order of the prototypes and the function definitions does not affect their order of execution. The order of execution is determined by the order of the function call statements. Functions Program Example: // File: house.cpp // Draws a house #include <iostream> using namespace std; // Functions used . . . void drawTriangle(); void drawIntersect(); void drawBase(); void drawParallel(); // Draws a triangle // Draws intersecting lines // Draws a horizontal line // Draws parallel lines int main() { // Draw a triangle drawTriangle(); // Draw parallel lines drawParallel(); // Draw a horizontal line drawBase(); return (0); } // Draws parallel lines void drawParallel() { cout << “ | | ” << endl; cout << “ | | ” << endl; cout << “ | | ” << endl; Functions return; } // end drawParallel // Draws a triangle void drawTriangle() { drawIntersect(); drawBase(); return; } // end drawTriangle // Draws intersecting lines void drawIntersect() { cout << “ /\\ “ << endl; cout << “ / \\ “ << endl; cout << “ / \\ “ << endl; return; } // end drawIntersect // Draws a horizontal line void drawBase() { cout << “ ------- “ << endl; return; } // end drawBase NOTE: Notice the use of comments. Documenting your code thoroughly is a good programming practice. Functions Order of Execution of Functions Since the function prototypes appear before the main function, the compiler sees and processes them before it sees the main function. The information supplied by the prototype enables the compiler to correctly translate a call to that function, as a transfer of control to that function. It also enables the compiler to verify the syntax of that call to be sure it matches the parameters set by the prototype. After the main function is compiled, each of the function definitions are then compiled. As part of the translation process, the compiler inserts a machine language statement at the end of the function body, that causes a transfer of control back from the function to the calling statement. Each function executes in a separate area of the memory allocated to run the program. When a function is called an area of memory is opened in which the function will execute. When the function completes, control is passed back to the calling statement and the memory is released. Advantages of Using Functions There are many advantages to using functions: Aid in program organization (modularity) Easier for programming teams Simplify programming tasks since existing functions can be reused Allows the removal of the details from main() o Procedural Abstraction Can be executed more than once in the same program Functions Functions of type void which accept no arguments have a very limited capability. Without incoming or outgoing data, these functions can only be used to display information. They are useful for displaying instructions, error messages and the like. In-Class Exercise: 1. Write a program that prompts the user for his first name, last name, student ID, and email address. 2. Using the function you wrote earlier, print three blank lines, followed by the user’s information displayed in the following format: Name: Mary Smith Student ID: 123-45-6789 Email Address: mxk10@psu.edu Functions Function with Input Arguments (Section 3.5) So far the only functions we have had to call have been the mathematical functions. In this chapter we will start to write our own functions. In creating C++ functions we must be concerned with both the function itself and how it interfaces with other functions, such as main( ). This includes correctly passing data into a function when it is called and returning a value back from a function. Arguments that pass information into the function are called input arguments. Arguments that return results are called output arguments. We can also return a single result from a function by executing a return statement. The use of arguments make functions much more versatile because it enables the function to manipulate different data each time it is called. As with the mathematical functions, a function is called by giving the function's name and passing any data to it in the parentheses following the function name: function_name (data passed to function); For example, the following program will call the function drawCircleChar( ), passing the value stored in input to it. Functions #include <iostream> using namespace std; int main( ) { char input; void drawCircleChar (char); // the function prototype cout << "\nEnter a single character: "; cin >> input; drawCircleChar (input); return (0); // the function is called here } The function drawCircleChar( ) is referred to as the called function and the function main( ) is referred to as the calling function. Within main the function drawCircleChar( ) is declared to receive one character value and returns no value back to main( ). This declaration is formally referred to as a function prototype. The function is then called somewhere in the program. Functions Function Prototypes Before a function can be called, it must be declared to the function that will do the calling. The declaration statement for a function is referred to as a function prototype. The function prototype will tell the calling function the type of value that will be formally returned, if any, and the data type of the values that the calling function should transmit to the called function. For example, the function prototype previously used: void drawCircleChar (char); declares that the function drawCircleChar( ) expects one character value to be sent to it, and that this particular function formally returns no value (void). The function prototype may be placed with the variable declaration statements of the calling function, or above the calling function name, or after the statement #include <iostream>. The general form of a function prototype is: return_data_type function_name (list of argument data types); For example: int fmax (int, int); float swap (int, char, char, double); void display (double, double); Functions Calling a Function To call a function, all that is required is that the name of the function be used and that any data passed to the function be enclosed within the parentheses following the function name. For example: drawCircleChar (input); Note: The function will only receive the value of input and it must determine where to store that value before it does anything else. Defining a Function A function is defined when it is written. Each function is defined once in a program and can then be used by any other function in the program that suitably declares it. Like the main( ) function, every C++ function definition consists of two parts, a function header and a function body: function header line { C++ statements; } Functions For example: void drawCircleChar (char symbol) represents a function header. The argument symbol will be used to store value passed to the function. void drawCircleChar(char symbol) { // start of function body cout << “ “ << symbol << endl; cout << “ “ << symbol << “ “ << symbol << endl; cout << “ “ << symbol << “ “ << symbol << endl; return; } // end of function body and end of function Putting it all together -- Program 3-7: #include <iostream> using namespace std; int main( ) { char input; void drawCircleChar (char); // the function prototype cout << "\nEnter a single character: "; cin >> input; drawCircleChar (input); called here return (0); // the function is Functions } // following is the function drawCircleChar() void drawCircleChar(char symbol) { // start of function body cout << “ “ << symbol << endl; cout << “ “ << symbol << “ “ << symbol << endl; cout << “ “ << symbol << “ “ << symbol << endl; return; } // end of function body and end of function In-Class Exercise: I. Write a function named check( ) that has three parameters. The first parameter should accept an integer number, the second parameter a floating-point number, and the third parameter a double-precision number. The body of the function should display the values of the data passed to the function when it is called. II. Include the function written above in a working program. Make sure your function is called from main( ). Test the function by passing various data to it. Functions Argument/Parameter List Correspondence When you are using multiple-argument functions, you must be careful to include the correct number and types of arguments in the function call. The following summarizes the constraints on the number, order and type (not) of input arguments (as quoted from your book): I. The number of actual arguments used in a call to function must be the same as the number of formal parameters listed in the function prototype. II. The order of arguments in the lists determines the correspondence. The first must correspond to the first, the second to the second, and so on. III. Each actual argument must be of a data type that can be assigned to the corresponding formal parameter without loss of information. Functions Default Arguments A convenient feature of C++ is the flexibility of providing default arguments in a function call. The default argument values are listed in the function prototype and are automatically transmitted to the called function when the corresponding arguments are omitted from the function call. For example: void example (int, int =5, float = 6.78) provides default values for the last two arguments. If any of these arguments are omitted when the function is actually called, the C++ compiler will supply these default values. Thus all of the following function calls are the same: example (7, 2, 9.3) example (7, 2) example (7) Functions Three rules must be followed when using default parameters: I. The first is that, if any parameter is given a default value in the function prototype, all parameters following it must also be supplied with default values. II. The second rule is that if one argument is omitted in the actual function call, then all arguments to its right must also be omitted. III. The third rule specifies that the default value used in the function prototype may be an expression consisting of both constants and previously declared variables. The true benefit of default arguments is realized when you want to extend an existing function to include more features that require additional arguments. By adding the new arguments to the right of the previously existing arguments and providing each with a default value eliminates the need to change all existing calls to that function. These existing function calls are conveniently unaffected by your changes. Functions Reusing Function Names (Function Overloading) In most high-level languages, including C, each function must have its own unique name. This can lead to the necessity to create multiple functions that perform the same functionality but accept arguments of a different data type. For example, let’s look at the following function prototypes: void abs (int); void labs (long); void fabs (double); Each of these three functions will perform essentially the same operation but on a different argument data type. C++ provides the capability of using the same function name for more than on function, which is referred to as function overloading. Each function that uses the name must still be written and exist as a separate entity. The use of the same function name does not require that the code within the functions be similar, although good programming practice dictates that functions with the same name should perform essentially the same operations. All that is formally required in using the same function name is that the compiler can distinguish which function to select based on the data types of the arguments when the function is called. Functions Using function overloading, the prototypes for functions to compute and display the absolute value of a specific data type might look as follows: void cdabs (int); void cdabs (long); void cdabs (double); Returning Values When a function is called by value it may process the data sent to it in any fashion desired and directly return at most one, and only one, "legitimate" value to the calling function. As with the calling of a function, directly returning a value requires that the interface between the called and calling functions be handled correctly. From its side of the return transaction, the called function must provide the following items: 1. the data type of the returned value 2. the actual value being returned A function returning a value must specify the data type of the value that will be returned in the header line. For example, the header for a function that calculates and returns the circumference of a circle, given the value of the radius, might look as follows: float findCircum (float r) Functions This means that the findCircum() function accepts a single float value, which is the circle’s radius. It will then calculate the circumference and return that value as a float. Float is the data type of the findCircum() function. Moreover, for a function to return a value it must use a return statement, which will be of the form: return expression; or return (expression); When the return statement is encountered, the expression is evaluated first. The value of the expression is then automatically converted to the data type declared in the header before being sent back to the calling function. The findCircum() definition could read as follows: float findCircum (float r) { // start of function body return (2.0 * PI * r); // PI is a constant } // end of function body and end of function Now, we must prepare the calling function to receive the value sent by the called function. On the calling side, the function must: 1. be alerted to the type of value to expect 2. properly use the returned value Functions To use a returned value we must either provide a variable to store the value or use the value directly in an expression. Storing the returned value in a variable is accomplished using a standard assignment statement. For example: circumference = findCircum (radius); To use the returned value in an expression we can do something like this: cout << “The circumference is: “ << findCircum(radius) << endl; This will display the value returned by the function. In-Class Exercise: I. Write a function called findArea() whose prototype looks like: float findArea(float); II. Write a main() function that calls and uses the findCircum() and your findArea() function after prompting the user for the circle’s radius. Be sure to define the PI constant. Functions ANOTHER EXAMPLE – Program 3-8: #include <iostream> using namespace std; int main () { double fahren; // start of declarations double tempConvert(double); // function prototype cout << "\nEnter a Fahrenheit temperature: "; cin >> fahren; cout << "The Celsius equivalent is " << tempConvert(fahren) << endl; return 0; } // convert Fahrenheit to Celsius double tempConvert (double in_temp) { return( (5.0 / 9.0) * (in_temp - 32.0) ); } Functions In-Class Exercise: A second-degree polynomial in x is given by the expression ax2 + bx + c where a, b, and c are known numbers and a is not equal to zero. Write a C++ function named poly_two (a, b, c ,x) that computes and returns the value of a second-degree polynomial for any passed values of a, b, c, and x. Problem Inputs vs. Input Parameters It is a common programming error for the beginner programmer to confuse the concepts of problem inputs and input parameters. Problem inputs are variables that are given their data from the user through the execution of a cin or other input statement. Input parameters receive their data through the execution of a function call statement. Therefore, the data being passed to an input parameter must already be defined before the function is called. The programmer should not prompt and read the input parameters inside the body of the function definition. Functions The Function Data Area Each time a function is called, a separate area of memory is allocated to store that function’s data. The function’s formal parameters and any local (or auto) variables that are declared in that function are stored in the function’s data area. When the function terminates all data in the function’s data area is lost. The data area is recreated each time the function is called. Testing Functions Using Drivers A function is an independent module of programming code. It can be created by an individual programmer and be tested independently from the program that uses it. To perform such a test, a programmer will usually design a short driver function that defines the function arguments, calls the function and displays the value returned in order to test the functionality or operation of the function. Since main( ) is necessary to run any program, a main( ) function is written as a driver to test any function. Functions Program Example: // File testScale.cpp // Tests function scale. #include <iostream> #include <cmath> #include <conio.h> using namespace std; float scale(float, int); // Needed for getch(); // Function prototype int main() { float num1; int num2; // Get values for num1 and num2 cout << "Enter a real number: "; cin >> num1; cout << "Enter an integer: "; cin >> num2; // Call scale and display result. cout << "Result of call to function scale is " << scale(num1, num2) // actual arguments << endl; getch(); return 0; } // reads any single character // information flow Functions float scale(float x, int n) // formal parameters { float scaleFactor; // local variable scaleFactor = pow(10, n); return (x * scaleFactor); } Function main Data Area Function scale Data Area num1 x 2.5 2.5 num2 n 3 2.5 scaleFactor ? Data areas after call: scale(num1, num2); Functions In-Class Exercise: Write a function that calculates the elapsed time in minutes between a start time and an end time, expressed as integers on a 24-hour clock (8:30 PM = 2030). You need to deal only with end times occurring later on the same day as the start time. Also write a driver program to test your function. Variable Scope (Section 3.6) By their very nature, C++ functions are constructed to be independent modules. We can think of functions as a closed box, with slots at the top to receive values and a single slot at the bottom of the box to return a value. The variables created inside a function are conventionally available only in the function itself. They are said to be local to the function, local variables. This term refers to the scope of a variable. The scope of a variable is defined as the section of the program where the variable is valid or "known". A variable can have either a local scope or a global scope. Local variables are meaningful only when used in expressions or statements inside the function that declared them. This means that the same variable name can be used in more than one function. For each function that declares the variable, a separate and distinct variable is created. Functions Most of the variables we have used until now have been local variables. This is a direct result of placing our declaration statements inside functions. A variable with global scope, more commonly termed a global variable, is one whose storage has been created for it by a declaration statement located outside any function. These variables can be used by all functions that are physically placed after the global variable declaration. Program 3-9: #include <iostream> using namespace std; int firstnum; // create a global variable named firstnum int main ( ) { int secnum; // create a local variable named secnum void valfun (void); // function prototype (declaration) firstnum = 10; // store a value into the global variable secnum = 20; // store a value into the local variable cout << "\nFrom main(): firstnum = " << firstnum; cout << "\nFrom main(): secnum = " << secnum; valfun ( ); // call the function valfun cout << "\n\nFrom main() again: firstnum = " << firstnum; Functions cout << "\nFrom main() again: secnum = " << secnum; return 0; } void valfun(void) // no values are passed to this function { int secnum; // create a second local variable named // secnum secnum = 30; // this only affects this local variable's value cout << "\n\nFrom valfun(): firstnum = " << firstnum; cout << "\nFrom valfun(): secnum = " << secnum << endl; firstnum = 40; // this changes firstnum for both // functions } OUTPUT: From main(): firstnum = 10 From main(): secnum = 20 From valfun(): firstnum = 10 From valfun(): secnum = 30 From main() again: firstnum = 40 From main() again: secnum = 20 Functions Global Scope Resolution Operator When a local variable has the same name as a global variable, all references to the variable name made within the scope of the local variable refer to the local variable. This means that the local variable name takes precedence over the global variable. For example -- Program 3-10: #include <iostream> using namespace std; float number = 42.8f; // a global variable named number int main ( ) { float number = 26.4f; // a local variable named number cout << "The value of number is " << number << '\n'; return 0; } In such cases, we can still access the global variable by using C++ global resolution operator, which has the symbol ::. It must be placed immediately before the variable name, as in ::number When used in this manner :: tells the compiler to use the global variable. Functions For example -- Program 3-11: #include <iostream> using namespace std; float number = 42.8f; int main ( ) { float number = 26.4; // a global variable named number // a local variable named number cout << "The value of number is " << ::number << '\n'; return 0; } Misuse of Globals Global variables allow the programmer to "jump around" the normal safeguard provided by functions. It is possible to make all variables global ones. DO NOT DO THIS. Using only global variables can be especially disastrous in larger programs that have many user-created functions. Since a global variable can be accessed and changed by any function following the global declaration, it is time consuming and frustrating task to locate the origin of an erroneous value. Global variables, are sometimes useful in creating variables that must be shared between many functions. Rather than passing the same variable to each function, it is easier to define the variable once as a global. Functions Variable Storage Class The scope of a variable defines the location within a program where that variable can be used. Given a program, you could take a pencil and draw a box around the section of the program where each variable is valid. The space inside the box would represent the scope of a variable. In addition to the space dimension variables also have a time dimension. The time dimension refers to the length of time that storage locations are reserved for a variable. This time dimension is referred to as the variable's "lifetime." Where and how long a variable's storage locations are kept before they are released can be determined by the storage class of the variable. Besides having a data type and scope, every variable also has a storage class. The four available storage classes are called auto, static, extern, and register. The storage class is a modifier that precedes the data type when you declare a variable. Examples: auto int num; static int miles; extern float num; auto char in_key; Functions I. auto (keyword is optional): This is the default storage class for internal or local variables. Variables of this type come into existence when a function is entered. Their storage space is released when the function exits. This class of variable can be initialized when declared, but, if it is not initialized, it will contain whatever value is in the memory location assigned to the variable. II. static: Storage space for static variables is allocated when the program is loaded into memory and stay in memory for the life of the program. Static variables may be initialized at the time of declaration; otherwise, they are automatically initialized to zero. III. extern: The extern storage class tells the compiler that a variable of a certain type has already been defined elsewhere in the program and that the compiler should not allocate memory space for the variable at this time. The compiler will not issue warning messages unless it cannot locate the definition of the variable when all modules are linked. This is used for global variables. Functions IV. register: The register variables have the same time duration and scope as auto variables. The only difference is where the storage is located. Most computers have a few additional high-speed storage areas located directly in the computer’s processing unit (CPU ), that can also be used for variable storage. These areas are called registers. They can be accessed faster than normal memory and computer instructions that reference registers typically require less space. Use of registers can increase the execution speed of a C++ program, if their use is supported on your computer. Variables declared with the register storage class are automatically switched to auto if your computer does not support register variables or if the declared variables exceed the computer’s register capacity.