return_type function_name( arg_type argument, ... ) { local_variable_type local_variables; executable statement(s); return return_value; }
int add( int a, int b ) { int sum; sum = a + b; return sum; }
Return Type
- The "return type" indicates what kind of data this function will return. In the example above,the function returns an int.
- Some languages differentiate between "subroutines", which do not return a value, and "functions", which do. In C there are no subroutines, only functions, but functions are not required to return a value. The correct way to indicate that a function does not return a value is to use the return type "void". ( This is a way of explicitly saying that the function returns nothing. )
- If no return type is given, the compiler will normally assume the function returns an int. This can cause problems if the function does not in fact return an int.
Function Name
- The function name is an identifier by which this function will be known, and obeys the same naming rules as applied to variable names ( Alphanumeric characters, beginning with alpha, maximum 31 significant characters, etc. )
Formal Parameter List
- Following the function name are a pair of parentheses containing a list of the formal parameters, ( arguments ) which receive the data passed to the function.
- The ANSI standard requires that the type of each formal parameter to be listed individually within the parentheses as shown in the above example. Even if several parameters are of the same type, each must have its type given explicitly.
- If a function takes no parameters, the parameters may be left empty. The compiler will not perform any type checking on function calls in this case. A better approach is to include the keyword "void" within the parentheses, to explicitly state that the function takes no parameters.
Function Body
- The body of the function is enclosed within curly {} braces, just as the "main" function with which we have been dealing so far, and contains the instructions that will be executed when this function is called.
- The function body starts by declaring all local variables, prior to any executable statements.
- In C99 variables can be declared any time before they are used, but in general it is still best to declare all variables that will be used at the beginning of the function, or at the beginning of the block in which they are used for very local variables. ( See scope below. )
- There are actually two schools of thought on this issue:
- Declaring all variables at the top of the function puts them all together, in one comprehensive list. If you print out the program, then all the variables will print on the same page, making a sort of "checklist" of variables to be taken care of.
- If you only work on the computer, and never from printouts, it can be difficult to continuously scroll back and forth between the variable declarations at the beginning of the function and the part of the program you are working on. Declaring the variables just before you use them keeps the declaration and use on the same screen without scrolling.
- ( As a compromise, you can declare the variables just before use while working on the program, and then move them all up to the top of the function after the program is running
The Return Statement
- The return statement exits the called function and returns control back to the calling function.
- Once a return statement is executed, no further instructions within the function are executed.
- A single return value ( of the appropriate type ) may be returned.
- Parentheses are allowed but not required around the return value.
- A function with a void return type will not have a return value after the return statement.
- More than one return statement may appear in a function, but only one will ever be executed by any given function call.
- ( All returns other than the last need to be controlled by logic such as "if" blocks. )
- If a function does not contain a return statement, most compilers will add one automatically at the end of the routine, and may generate a warning message. The return value, if any, is undefined in this case.
- "main( )" is technically a function, and should return 0 upon successful completion, or a non-zero value otherwise. This is ignored by many programmers, but some compilers will issue warning messages if main() does not contain a return statement.
int add( int a, int b ); int add( int, int );
double myfunction( int, float );
void yourfunction( void );
void func( double x, double z, double b, double c ); int main( void ) { double x( 1.0 ), y( 2.0 ), z( 3.0 ); func( x, y, z, x );
int add_two( int x ); // Adds 2 to its argument and returns the result main() { // <----------------------------------------------------------- int main( void ) int y = 5; cout << "Before calling any function, y = " << y << endl; add_two( y ); cout << "After calling the function once, y = " << y << endl; y = add_two( y ); "After calling the function twice, y = " << y << endl; return 0; } // main int add_two( int x ) { // <---------------------------------------- int add_two( int ) cout << "In function, x changed from " << x; x += 2; cout << " to " << x << endl; return x; } // add_twoOutput:
Before calling any function, y = 5 In function, x changed from 5 to 7 After calling the function once, y = 5 In function, x changed from 5 to 7 After calling the function twice, y = 7
Pass By Value
- Ordinary data types ( ints, floats, doubles, chars, etc ) are passed by value in C/C++, which means that only the numerical value is passed to the function, and used to initialize the values of the functions formal parameters.
- Under the pass-by-value mechanism, the parameter variables within a function receive a copy of the variables ( data ) passed to them.
- Any changes made to the variables within a function are local to that function only, and do not affect the variables in main ( or whatever other function called the current function. ) This is true whether the variables have the same name in both functions or whether the names are different.
Passing Arrays and/or Array Elements
- When one element of an array is passed to a function, it is passed in the same manner as the type of data contained in the array. ( I.e. pass-by-value for basic types. )
- However when the entire array is passed, it is effectively passed by reference. ( Actually by pointer/address, to be explained completely in the section on pointer variables. )
- See "Passing Arrays to Functions" below.
( Pass by Pointer / Address )
- Pass by pointer / address is another type of data passing that will be covered later under the section on pointer variables.
- ( It is often mistakenly termed pass-by-reference in some C textbooks, because the actual pass-by-reference passing mechanism is only available in C++. )
- Pass by pointer / address requires use of the address operator ( & ) and the pointer dereference operator ( * ), to be covered later.
( Pass by Reference )
- C++ introduced a new passing mechanism, pass by reference, that is not available in ordinary C, and will not be discussed further in these notes.
/* Program Illustrating the use of Arrays and Functions */ #include <stdlib.h> #include <stdio.h> // Finds max in the array double maxArray( const float numbers[ ], int arraySize ); int main( void ) { double array1[ ] = { 10.0, 20.0, 100.0, 0.001 }; double array2[ 2 ][ 3 ] = { { 5.0, 10.0, 20.0 }, { 8.0, 15.0, 42.0 } }; int sizes[ 2 ] = { 4, 3 }; double max1, max2, max3, max4, max5; max1 = maxArray( array1, 4 ); max2 = maxArray( array1, sizes[ 0 ] ); max3 = maxArray( array2[ 1 ], 3 ); // Advanced max4 = maxArray( array2[ 0 ], 6 ); // Very Advanced max5 = maxArray( array1, -4 ); // Generates an error - returns 0.0; printf( "Maximums are %f, %f, %f, %f, and %f\n", max1, max2, max3, max4, max5 ); return 0; } double maxArray( const double numbers[ ], int arraySize ) { /* Function to find the maximum in an array of doubles Note the use of the keyword "const" to prevent changing array data */ int i; double max; if( arraySize <= 0 ) { return 0.0; } max = numbers[ 0 ]; for( i = 1; i < arraySize; i++ ) max = ( numbers[ i ] > max ) ? numbers[ i ] : max; return max; }
int arrayFunction( int nRows, int nCols, double x[ nRows ], double y[ nRows ][ nCols ] );the variable dimension on x is informative to the human but not necessary for the computer, since we could have declared it as x[ ]. However the nCols dimension on y is very useful, because otherwise the function would have to be written for arrays with pre-determined row sizes, and now we can write a function that will work for arrays with any row length.
double maxArray( const double numbers[ ], int arraySize, int errorCode[ ] ) { /* Function to find the maximum in an array of doubles Note the use of the keyword "const" to prevent changing array data */ int i; double max; if( arraySize <= 0 ) { errorCode[ 0 ] = -1; // Errors found in input. Results invalid. return 0.0; } errorCode[ 0 ] = 0; // No errors in input max = numbers[ 0 ]; for( i = 1; i < arraySize; i++ ) max = ( numbers[ i ] > max ) ? numbers[ i ] : max; return max; }
Write the following functions:
long int factorial( int X ) { if( X <= 1 ) return 1; return X * factorial( X - 1 ); }
for( int i = 0; i < limit; i++ ) {
- In this case the loop counter i exists within the body of the loop, and ceases to exist when the loop exits.
- There will be no conflict between this variable i and any other declared at a more general scope. ( See below. )
Variable Eclipsing
- If the same variable name is used in multiple scopes ( e.g. global, local, very local ), and the scopes overlap,then in the regions of overlap the more specific variable will eclipse, or hide the more general variable(s).
- When the more specific variable goes out of scope, then the next more general variable becomes visible again, unchanged by whatever may have occurred while it was eclipsed.
- Example. In the code below the comments indicate which X will be printed at which locations.
void func( int ); double x( 0.0 ); // Global scope int main( void ) { printf( "First x = %f\n", x ); // prints the global, 0.0 int x = 42; // Ordinary local printf( "Second x = %d\n", x ); // prints the ordinary local, 42 if( x > 40 ) { char x( 'A' ); // Very local, value = 65 printf( "Third x = %c\n", x ); // Prints very local, 'A' func( x ); // Passes the very local char, converted to an int. } printf( "Fifth x = %d\n", x ); // Ordinary local 42 again return 0; } void func( int x ) { // local parameter printf( "Fourth x = %d\n", x ); // Local parameter, 65 return; }
![]()
Program Output:
First x = 0.0
Second x = 42
Third x = A
Fourth x = 65
Fifth x = 42
void staticExampleFunction( void ); int main( void ) { for( int i = 0; i < 5; i++ ) staticExampleFunction( ); return 0; } // main void staticExampleFunction( void ) { int normalInt = 0; static int staticInt = 0; printf( "The normal int = %d. The static int = %d.\n", ++normalInt, ++staticInt ); return; }Output:
The normal int = 1. The static int = 1. The normal int = 1. The static int = 2. The normal int = 1. The static int = 3. The normal int = 1. The static int = 4. The normal int = 1. The static int = 5.
Extern Variables
- The "extern" keyword applied to a variable indicates that it is declared and allocated space in some other file.
- A declaration with the word "extern" is like a function prototype - It tells the compiler of the existence of the variable, without actually creating it or allocating any space for it.
- All such variables must be declared exactly once, ( i.e. in one file only of a multi-file development project ) without the "extern", so that space can be allocated for it.
- Exterm variables are global in the file in which they are declared without "extern", but may be either local or global in other files.
- Extern will be covered more fully under the topic of multi-file development.
Register Variables
- The keyword "register" suggests to the compiler that the given variable be stored in one of the CPU registers, ( for faster access ), instead of in regular memory.
- Register variables act as auto variables, except they do not have an "address", and so cannot be referred to by pointer variables or by the address operator, &.
- Loop counters are the most common and obvious use of register variables.
- Modern optimizing compilers have elminated most need for the keyword register.
The following example shows all possible qualifiers for variables and function parameters, and how those qualifiers affect the variables in three key areas:
- Storage duration, indicating whether the item continues to exist when it goes out of scope ( static storage ), or whether it is re-created and re-initialized every time that it goes into scope ( auto storage. )
- Scope, indicating whether the item is available to the remainder of the file ( file scope ), or only through the remainder of the block in which it is defined ( block scope. )
- Linkage, indicating whether the item is also accessible from other files ( external linkage ) or whether it is private to multiple functions only within this file ( internal linkage ), or whether it is accessible only within a block ( no linkage, i.e. none. )
int a; extern int b; static int c; void f( int d, register int e ) { auto int g; int h; static int i; extern int j; register int k; }
Name Storage Duration Scope Linkage a staticfile
external b static file ??? - see below c static file internal d auto block none e auto block none f auto block none g auto block none h auto block none i static block none j static block ??? - see below k auto block none??? - The linkage for b and j depends on their original declaration, but are normally external
There is also one additional qualifier that can be applied to functions only: inline
The ordinary function call-and-return mechanism involves a certain amount of overhead, to save the state of the original function on the stack, create stack space for a return address and the local ( auto ) variables needed for the new function and its parameters, transfer control to the new function, do the work, store the return value back on the stack, clean up the stack, and then transfer control back to the original calling function.
For certain small fast functions, this overhead can add significantly to the processing time of the function, often greatly surpassing the effort needed to perform the work of the function.
Therefore the inline qualifier applied to a function suggests to the compiler to simply copy the instructions for the function into this location in the calling function's code, instead of invoking all of the overhead of the call and return process.
An alternative to inlined functions is parameterized macros, implemented with #define, which are covered elsewhere.
The following topics are not covered here, but may be found in many books on C Programming