Chapter 5 Functions 5.1 - Dividing a program into functions 1) makes it easier to read -- one of the principles of structured programming 2) makes it smaller - Functions in C++/C are like subroutines or procedures in other languages Simple Functions (an example:) #include void starline() ; // declaration (prototype) main () { starline() ; // first call cout << "Type Range\n" ; starline() ; // second call cout << "char -128 to 127\n" ; cout << "short -32,768 to 32,767\n" ; cout << "int System dependent\n" ; cout << "char -2,147,483,648 to " << "2,147,483,647\n" ; starline() ; // third call } void starline() // definition { for ( int i = 0 ; i < 45 ; i++ ) cout << '*' ; cout << endl ; } - Two functions: main(), starline() 5.2 - Three components to a function: declaration, definition, and call(s) - void starline() is a simple function: no arguments (the parentheses are empty) and no return type (the "void" in front) The Function Declaration - The function declaration or prototype is: void starline() ; (before main() ) - See Figure 5.2 for syntax Calling the Function - It is called (or invoked, or executed) three times in main() by: starline() ; - See Figure 5.1 for flow of control - See Figure 5.2 for syntax The Function Definition - The function definition consists of a declarator (the first line) followed by the function body within braces: void starline() // declarator { // beginning of body for ( int i = 0 ; i < 45 ; i++ ) cout << '*' ; cout << endl ; } // end of body - The declarator is the same as the 5.3 declaration except no ; at the end - See Figure 5.2 for syntax - Table 5.1 summarizes the components Comparison with Library Functions - Library functions are pre-declared in header files (which must be included: e.g.: #include ), and pre- defined in system files, so you only need to call them Eliminating the Declaration - A function need not be declared if it is defined above the place it is called: #include void starline() //definition AND declaration { for ( int i = 0 ; i < 45 ; i++ ) { cout << '*' ; } cout << endl ; } main () { // Same main program as before } - Ok for small programs, but better style to separate declarations and definitions Passing Arguments to Functions 5.4 - An argument is a data item sent to a function that allows the function to do or compute different things depending on the value of the argument Passing Constants #include void repchar ( char, int ) ; // declaration main () { repchar ( '-', 43 ) ; // first call cout << "Type Range\n" ; repchar ( '=', 23 ) ; // second call cout << "char -128 to 127\n" ; cout << "short -32,768 to 32,767\n" ; cout << "int System dependent\n" ; cout << "char -2,147,483,648 to " << "2,147,483,647\n" ; repchar ( '-', 43 ) ; // third call } void repchar ( char ch, int n ) // declarator { for ( int i = 0 ; i < n ; i++ ) // cout << ch ; // body cout << endl ; // } 5.5 - In the declaration: void repchar ( char, int ) ; char and int are the types of the arguments that will be sent to repchar - In the function calls, constant values for the arguments are specified within the parentheses - e.g.: repchar ( '-', 43 ) ; tells repchar to print 43 dashes - The types (and the number) of the argument values in the call, char for '-' and int for 43 in this case, must agree with those in the declaration, which must in turn agree with the types specified in the declarator of the function definition - In the function declarator: void repchar ( char ch, int n ) ch and n are the parameters that get the values of the arguments in each call; they act like variables within the function body - Each parameter is initialized to the value of its corresponding argument Passing Variables 5.6 - Values of variables (and expressions) can also be passed as arguments to functions as long as their types match those in the function definition: #include void repchar ( char, int ) ; main () { char chin ; int nin ; cout << "Enter a char " ; cin >> chin ; cout << "Enter # repeats " ; cin >> nin ; repchar ( chin, nin ) ; } void repchar ( char ch, int n ) { for ( int i = 0 ; i < n ; i++ ) cout << ch ; cout << endl ; } Passing by Value - The values contained in chin and nin are copied into ch and n respectively at the call; this is "passing by value" - Figure 5.3 explains this method - We'll see "passing by reference" later Structures as Arguments (can do it) 5.7 Passing a Distance Structure #include struct Distance { int feet ; float inches ; } ; void engldisp ( Distance ) ; main () { Distance d1 ; cout << "Enter feet: "; cin >> d1.feet ; cout << "And inches: "; cin >> d1.inches ; cout << "\nd1 = " ; engldisp ( d1 ) ; cout << endl ; } void engldisp ( Distance dd ) { cout<< dd.feet << "\'-" << dd.inches << "\""; } - The values in d1 get copied into dd at the function call just as with simple argument types (See Figure 5.4) - Changing the values in dd in the function would not change those in d1 Passing a circle Structure 5.8 - This example won't be discussed since (1) there is no Unix version of the Console Graphics Lite package (2) there is nothing new to learn from it with respect to passing structs Names in the Declaration - Help explain the meaning to a reader: void repchar ( char the_char, int n_reps ) ; - Can be different than the definition (but it's better to make them the same): void repchar ( char ch, int n ) { for ( int i = 0 ; i < n ; i++ ) { cout << ch ; } cout << endl ; } - Have no connection with names of variables in a call: repchar ( chin, nin ) ; Returning Values from Functions 5.9 - Many functions return a value: #include float lbstokg( float pounds ); // declaration void main () { float lbs, kgs ; cout << "Enter wt. (#s) " ; cin >> lbs ; kgs = lbstokg ( lbs ) ; cout << "Wt. in Kgs = " << kgs << endl ; } float lbstokg ( float pounds ) // definition { float kilograms = 0.453592 * pounds ; return kilograms ; } - The type of the returned value must be specified before the function name in both the declaration and definition (or void if no value is returned); if no type is specified, the type defaults to int -- BUT it's bad style to use this default - The returned value is specified in a "return" statement - The call, lbstokg ( lbs ), can be used like any other value in an expression The return Statement 5.10 - When the return statement in lbstokg() is executed, the value in the variable kilograms is transferred to the calling program (main) where it's copied into the kgs variable by the assignment -- see Figure 5.6 - To return > 1 value, use other methods Eliminating Unnecessary Variables: #include float lbstokg( float pounds ); // declaration void main () { float lbs ; cout << "Enter wt. (#s) " ; cin >> lbs ; cout << "Wt. in Kgs = " << lbstokg ( lbs ) << endl ; } float lbstokg ( float pounds ) // definition { return 0.453592 * pounds ; } - No need for kgs variable in main() - No need for kilograms in lbstokg() - Often use parentheses around return value for clarity: return ( 0.453592 * pounds ) ; Returning Structure Variables 5.11 - This can be done as is shown below for the function addengl() which returns a Distance structure value #include struct Distance { int feet ; float inches ; } ; Distance addengl(Distance dd1, Distance dd2); void engldisp ( Distance dd ) ; void main () { Distance d1, d2, d3 ; cout << "Enter feet: "; cin >> d1.feet ; cout << "And inches: "; cin >> d1.inches ; cout << "Enter feet: "; cin >> d2.feet ; cout << "And inches: "; cin >> d2.inches ; d3 = addengl ( d1, d2 ) ; engldisp ( d1 ) ; cout << " + " ; engldisp ( d2 ) ; cout << " = " ; engldisp ( d3 ) ; cout << endl ; } 5.12 Distance addengl(Distance dd1, Distance dd2) { Distance dd3 ; dd3.inches = dd1.inches + dd2.inches ; dd3.feet = 0 ; if ( dd3.inches >= 12.0 ) { dd3.inches -= 12.0 ; dd3.feet++ ; } dd3.feet += dd1.feet + dd2.feet ; return dd3 ; } void engldisp ( Distance dd ) { cout<< dd.feet << "\'-" << dd.inches << "\""; } - Note that addengl() could not be simplified to just: return dd1 + dd2 ; since + hasn't been defined for Distances - But we could eliminate d3 in the main() function by replacing the last line by: engldisp( addengl(d1, d2) ) ; cout << endl ; Reference Arguments 5.13 - A reference (in the function definition) provides an alias -- a different name -- for a variable used as an argument in a function call ==> the function can change the value of the argument variable in the calling program (unlike pass by value) - A reference is actually a memory address - A benefit of pass by reference is that more than one value may be returned by a function, as in the example below. Passing Simple Data Types by Reference #include void intfrac ( float num, long & intp, float & fracp ) ; void main () { float number, fracpart ; long intpart ; cout << "Enter a real: "; cin >> number ; intfrac ( number, intpart, fracpart ) ; cout << "Integer part = " << intpart << ", fraction = "<< fracpart <(num) ; fracp = num - static_cast(intp) ; } 5.14 - The & (ampersand) indicates that intp and fracp are aliases for the second and third arguments in a call to intfrac() (i.e. aliases for intpart and fracpart respectively, in this case); number is passed by value (i.e. copied into num) - This is how the two values (of intp and fracp) are passed back to main() -- see Figure 5.7 for how this is done - Note that the &'s are needed in the function declaration and definition, but must be omitted in a function call A More Complex Pass by Reference: #include void main () { void order ( int & num1, int & num2 ) ; int n1 = 99, n2 = 11, n3 = 22, n4 = 88 ; order ( n1, n2 ) ; order ( n3, n4 ) ; cout << "n1: " << n1 << ", n2: " << n2 << ", n3: " << n3 << ", n4: " << n4 << endl; } void order ( int & num1, int & num2 ) { if ( num1 > num2 ) { int temp = num1; num1 = num2; num2 = temp; } } Passing Structures by Reference: 5.15 #include struct Distance { int feet ; float inches ; } ; void scale( Distance & dd, float factor ) ; void engldisp ( Distance dd ) ; void main () { Distance d1 = {12, 6.5}, d2 = {10, 5.5} ; scale ( d1, 0.5 ) ; scale ( d2, 0.25 ) ; cout << "\nd1 = " ; engldisp ( d1 ) ; cout << "\nd2 = " ; engldisp ( d2 ) ; cout << endl ; } void scale( Distance & dd, float factor ) { float inches = (dd.feet*12 + dd.inches)*factor; dd.feet = static_cast(inches / 12 ) ; dd.inches = inches - dd.feet*12 ; } void engldisp ( Distance dd ) { cout<< dd.feet << "\'-" << dd.inches << "\""; } Notes on Passing by Reference 5.16 - It is possible in other languages - The effect is achieved by using pointers in C, and is a third way to pass arguments in C++ - The use of & to indicate reference arguments in C++ is different than the use of & as the address operator Overloaded Functions - An overloaded function can perform different operations depending on the the number and kind of arguments sent to it Different Numbers of Arguments - Can use the same function name with different numbers of arguments - C++ makes the proper connection as shown in Figure 5.8 #include void repchar () ; void repchar ( char the_char ) ; void repchar ( char the_char, int n_reps ) ; main () { 5.17 repchar () ; repchar ( '=' ) ; repchar ( '+', 30 ) ; } void repchar () { for ( int i = 0 ; i < 45 ; i++ ) { cout << '*' ; } cout << endl ; } void repchar ( char ch ) { for ( int i = 0 ; i < 45 ; i++ ) { cout << ch ; } cout << endl ; } void repchar ( char ch, int n ) { for ( int i = 0 ; i < n ; i++ ) { cout << ch ; } cout << endl ; } Different Kinds of Arguments - Can even overload a function name with the same numbers of arguments as long as the types of the arguments are different #include 5.18 struct Distance { int feet ; float inches ; } ; void engldisp ( Distance dd ) ; void engldisp ( float tot_inches ) ; main () { Distance d1 ; float d2 ; cout << "Enter feet: "; cin >> d1.feet ; cout << "And inches: "; cin >> d1.inches ; cout << "Total inches: "; cin >> d2 ; cout << "\nd1 = " ; engldisp ( d1 ) ; cout << "\nd2 = " ; engldisp ( d2 ) ; cout << endl ; } void engldisp ( Distance dd ) { cout<< dd.feet << "\'-" << dd.inches << "\""; } void engldisp ( float tot_inches ) { int feet = static_cast(tot_inches/12); float inches = tot_inches - feet*12 ; cout<< feet << "\'-" << inches << "\"" ; } 5.19 - Overloading saves the programmer from having to remember different names - In C we need abs(int), cabs(complex), fabs(double), labs(long) ==> same in C++ - Example of different types of arguments Inline Functions - Using functions saves space - BUT there is a problem: it takes extra time to make each function call - Cure(?): just put copies of the code in the program where needed ==> - Problem: not structured programming - Cure: use inline functions which does it #include inline float lbstokg ( float pounds ) { return 0.453592 * pounds ; } void main () { float lbs ; cout << "Enter wt. (#s) " ; cin >> lbs ; cout << "Wt. in Kgs = " << lbstokg ( lbs ) << endl ; } 5.20 - Works best for very short functions - Inlining is just a request to compiler - Inlining takes the place of C #define macros, which don't do type checking and need care with parentheses Default Arguments - A function can be called with fewer than its number of arguments if default values are supplied for them - The default/missing arguments must be the last ones (the first ones must be given): #include void repchar ( char ch, int n_reps = 45 ) ; main () { // repchar () ; /* no default: won't work */ repchar ( '=' ) ; repchar ( '+', 30 ) ; } void repchar ( char ch, int n_reps = 45 ) { for ( int i = 0 ; i < n_reps ; i++ ) { cout << ch ; } cout << endl ; } Variables and Storage Class 5.21 - Two features of variables in functions that are determined by its storage class: (1) its visibility: which parts of the program can access it (2) its lifetime: how long it stays in existence - Will discuss three storage classes: automatic, external, and static Automatic Variables - All variables defined in a function body are automatic, which is the default - We could use the auto keyword to make this explicit, but it is rarely done: void afunction() { auto int num_items ; } Lifetime - Automatic variables are automatically created when the function is called and are automatically destroyed when the function returns (hence the name) - Their lifetime coincides with the time of the execution of the function - They only exist when needed ==> conserves memory Visibility 5.22 - A variable's visibility (or scope) describes the locations in a program from which it can be accessed - Part of structured programming - Automatic variables are only visible within the function in which they are defined: also called local variables - Lifetime and visibility coincide for automatic variables: they only exist while the function is executing and are only visible within that function void function1 () { int num1 ; // other statements } void function2 () { int num2 ; num1 = 10 ; // illegal, not visible num2 = 20 ; // ok, within function2 } Initialization - Must explicitly initialize an automatic variable, if desired: int n = 33 ; External Variables 5.23 - External variables are defined outside of all functions, and are visible to all functions of a program below it ==> - Often defined at the top of a program, and so are also called global variables #include #include // or on Unix char ch = 'a' ; // external variable ch void getachar () ; void putachar () ; main () { while ( ch != '\r' ) // or '\n' on Unix { getachar () ; putachar () ; } cout << endl ; } void getachar () { ch = getch () ; } void putachar () { cout << ch ; } Role of External Variables 5.24 - Used when they must be visible to more than one function ==> - Can cause organizational problems - Less need for them in object-oriented programming Initialization - Done when the program is loaded - Default initialization value = 0 Lifetime and Visibility -They exist for the life of the program -And visible below where they are defined Static Variables - Will discuss static automatic variables - Static external variables have meaning only in multifile programs (later) - "Static" doesn't change the visibility of automatic variables - But static automatic variables come into existence at the first call to their function and remain in existence for the life of the program - Static automatic variables are used when we need to remember their values between function calls, as in the "running average" function below: #include 5.25 float getavg ( float newdata ) ; main () { float data = 1, avg ; cout << "Enter a number "; cin >> data; while ( data != 0 ) { avg = getavg ( data ) ; cout << "New average: " << avg << endl; cout << "Enter a number "; cin >> data; } } float getavg ( float newdata ) { static float total = 0 ; static int count = 0 ; total += newdata ; count++ ; return total / count ; } Initialization - Takes place only once: at first call Storage - Table 5.2 compares storage classes Returning by Reference 5.26 - Can also return a value by reference ==> - Can use a function call on the left of an assignment statement #include int x ; int & setx () ; main () { setx() = 92 ; cout << "x = " << x << endl ; } int & setx () { return x ; // returns value to be modified } Function Calls on the Left of the Equal Sign - A function that returns a reference can be used like a variable (much as an ordinary function can be used as a value) - It returns an alias to the variable in the function's return statement (x above) - ==> it can be treated like x - Rules: 1) can't return a constant 2) can't return an automatic variable (why? answer: it disappears after call) Don't Worry Yet 5.27 - But it's good for operator overloading const Function Arguments - Problem: passing large variables by value ==> much copying (inefficient) - Cure(?): pass by reference ==> - Problem: don't want the function to modify the variable - Cure: use a const pass by reference: #include void aFunc ( int & a, const int & b ) ; main () { int alpha = 7, beta = 11 ; aFunc ( alpha, beta ) ; } void aFunc ( int & a, const int & b ) { a = 107 ; // b = 111 ; /* illegal */ } - Philosophy: it's better for the compiler to catch an illegal variable change than to discover it at run time - If you pass a const variable to a reference argument, it MUST be const (no problem with a "by value" argument, since it is copied to its parameter)