Chapter 6 Functions
Functions are a basic building block for writing C/C++ programs.
Breaking a program up into separate functions, each of which
performs a particular task, makes it easier to develop and debug
a program.
6.1 Functions
There are several advantages of using functions in program
development. Functions allow for breaking down the program into
discrete units. Programs that use functions are easier to
design, program, debug and maintain. It is possible to perform
separate compilation of functions. Functions can return data via
arguments and can return a value. Functions have local variables
plus have access to global variables.
The general structure of a function is as follows:
storage_class function_return_type function_name(arguments)
declaration of argument types;
{
local_variable declarations;
body_of_the_function;
}
The components are:
storage_class - This is an optional item that indicates the
storage class of a function. If this is not present, the default
storage class type is extern. The only other allowable storage
class is static. Neither auto nor register are valid storage
class types for functions.
function-return_type - This tells the type of data item that
will be returned by this function. The function can only return
one value and it will be of this type. All standard C data
types, plus constructed data types, plus 'void' are allowed.
function_name - The name of the function which follows the rules
previously stated for variable names. This name must be unique
within the program.
arguments - These are optional. Some functions do not
require arguments to be passed to them, so the argument list is
empty, getchar(), for example. Arguments are separated by
commas with a maximum of 16 arguments. The argument list must
be enclosed in parentheses with no semi-colon following the function
header. With a traditional or K & R C compiler the data type of
the arguments is listed separately from the actual argument list.
With an ANSI C or C++ compiler the data type of each argument is
listed in the argument list.
declaration of argument types - For each argument passed to
the function, its data type must be declared. The declaration
must occur before the opening curly brace of the function. Each
list of arguments of a specific type must end with a semi-colon.
All variable declarations are treated as local or auto class
variables. This style of argument type declaration is used only
with traditional or K & R style C compilers.
{ - This marks the beginning of the function.
local_variable declarations - Declare any variables needed to
accomplish the task of the function. These are auto class
variables by default and are visible only to the function and
disappear when the function passes control back to the calling
function. Other storage class type variables may be declared at
this location.
body_of_the_function - C and C++ statements that perform the
task of the function which can also include calls to other C or
C++ functions, assembler routines, Pascal procedures, or FORTRAN
subroutines.
} - The closing curly brace indicates the end of the
function. This forces a return value of zero for the
function_return_type specified unless a prior return statement
has explicitly stated a value that is to be returned.
Listing 6-1
#include
int main()
{
int age;
int getInteger( char [], int, int );
age = getInteger( "Enter your age: ", 21, 50 );
cout << "Glad to here you are "
<< age
<< " years old."
<< endl;
return 0;
}
int getInteger( char prompt[], int min, int max )
{
int temp, valid = 0;
do
{
cout << prompt;
cin >> temp;
if( temp >= min && temp <= max )
valid = 1;
else
{
cout << "Input must be between "
<< min
<< " and "
<< max
<< ". Try Again!" << endl;
valid = 0;
}
} while( !valid );
return temp;
}
Notice that in the above example the prototype of the function to
be called
int getInteger( char [], int, int );
is in the function that will call that function. The prototype
must appear before the first call to the function. In the
function prototype, only the data types of the arguments need be
present, not the actual argument names as appears in the
function.
6.2 Returning Values
The return statement allows a function to return a value of the
stated data item type. This statement immediately pushes a value
onto the return stack and causes control to move to the ending
curly brace, }, of the function, which returns control back to
the calling function. Without a return statement a function
implicitly returns a value of zero for the data type for which
the function was typed. The general form of the return statement
is:
return(value);
Listing 6-2
#include <iostream.h>
int main()
{
int ch, type, chkletter();
cout << "\nPress any key followed by RETURN:";
cin >> ch;
type = chkletter( ch );
switch(type)
{
case 0:
cout << "\nNon alpha";
break;
case 1:
cout << "\nUppercase alpha";
break;
case 2:
cout << "\nLowercase alpha";
break;
}
return 0;
}
int chkletter( int c)
{
if(c >= 'A' && c <= 'Z')
return( 1 );
if(c >= 'a' && c <= 'z')
return( 2 );
}
6.3 Passing Arguments
Arguments can be constants or variables holding values. The
default method is that arguments are passed by value. Passing by
value means that only a copy of the value held in the argument is
brought into the locally declared argument within the function.
Passing by value prevents the function from altering the original
variable's value in the calling function.
Fig 6-1
int main()
{
.
.
.
x = add(10,20);
}
int add( int a, int b)
{
return(a+b);
}
C and C++ supports calling functions and passing arguments by
reference. Passing arguments by reference means passing the
actual address of a variable so that the called function can
affect data stored in the original variable. To pass an address
of a variable requires that the address of operator, &, be used
on the calling side. The address passed is then received in a
pointer type data item. Pointer is a data type just as int and
float are data types. Pointer type variables are intended to
hold memory addresses. These memory addresses represent the
locations in computer memory where data values are stored. To
look at the values at those address, the value at the address
operator, *, must be used to dereference the pointer holding the
memory address and obtain the value stored at that memory
address.
Listing 6-3
#include <stdio.h>
int main()
{
int x , y;
void swap( int *, int *);
x = 10;
y = 20;
swap( &x, &y );
printf("%d %d",x,y);
return 0;
}
void swap(int *a, int *b)
{
int temp;
temp = *a; // store the value at the address held
// in pointer a
*a = *b; // store the value at the address held
// in pointer b into the value at the
// address held in pointer a
*b = temp; // store the value held in temp into
// the value at the address held in
// pointer b
}
6.4 Command Line Arguments
The main() function can have arguments passed to it from the
command line. Three arguments can be passed to the main()
function; argc which gives the number of arguments on the
command line; argv which holds the actual arguments from the
command line; and, envp which holds the current settings for any
environment block variables, this is an optional argument and is
usually not included.
What is the command line? The operating system has a task
running that reads the command line associated with the operating
system prompt. The command line is anything from just after the
operating system prompt upto and including the first newline
character. Anything typed on the command line can be passed to a
C, C++ or assembly language program.
Listing 6-4
#include <iostream.h>
int main( int argc, char *argv[], char *envp[] )
{
int indx;
cout << "\nNumber of arguments is " << argc;
for( indx = 0; indx < argc; ++indx )
cout << "\nARGV[ " << indx << "]= " << argv[indx];
for( indx = 0; envp[indx]; ++indx )
cout << "\nENVP[ " << indx << "]= " << envp[indx];
return 0;
}
Notice that there are two arrays passed to the man() function,
char *argv[] and char *envp[]. These arguments are declared as
arrays of pointers to character type data. The concept of
pointers will be discussed in a later chapter but for now assume
that these arguments hold lists of strings.
6.5 Default Arguments
Another improvement to functions in C++ is that you can specify
the default values for the arguments when you provide a prototype
for a function. For example, if you are defining a function
named create_window that sets up a window (a rectangular region)
in a graphics display and fills it with a background color, you
may opt to specify default values for the window's location,
size, and background color, as follows:
// A function with default argument values
// Assume that Window is a user-defined type
Window create_window(int x = 0, int y = 0, int width = 100,
int height = 50, int bgpixel = 0 );
With create_window declared this way, you can use any of the
following calls to create new windows;
Window w;
// The following is the same as:
create_window(0,0,100,50,0);
w = create_window();
// This is the same as:
create_window(100,0,100,50,0);
w = create_window(100);
// Equivalent to:
create_window(30,20,100,50,0);
w = create_window(30, 20 );
As you can see from the examples, it is impossible to give a
nondefault value for the height argument without specifying the
values for x, y, and width as well, because height comes after
them and the compiler can only match arguments by position.
In other words, the first argument you specify in a call to
create_window always matches x, the second one matches y, and so
on. Thus, you can leave only trailing arguments unspecified.
6.6 Functions with an Unspecified Number of Parameters
Using the ellipsis, ..., with C++ function prototypes, means that
the function can be specified with an unknown number and type of
parameters. This feature can be used to suppress parameter type
checking and to allow flexibility in the interface to the
function.
C++ allows functions be to declared with an unspecified number of
arguments. Ellipsis marks are used to indicate this, as follows:
return_type function_name( ... )
The function printf(), from header stdio.h, is declared as
int printf( char *, ... );
Calls to printf() must have at least one argument, namely a
string, beyond this, the additional arguments are unspecified
both in type and in number.
Argument checking is turned off when a function is declared to
have an unspecified number of arguments. It is therefore
recommend against using this capability unless it is absolutely
necessary.
Header stdarg.h contains a set of macros for accessing
unspecified arguments. The reader is urged to study the macros
in this header file.
6.7 Inline Functions
Inline functions are like preprocessor macros, because the
compiler substitutes the entire function body for each inline
function call. The inline functions are provided to support
efficient implementation of OOP techniques in C++. Because the
OOP approach requires extensive use of member functions, the
overhead of function calls can hurt the performance of a program.
For smaller functions, you can use the inline specifier to avoid
the overhead of function calls.
On the surface, inline functions look like preprocessor macros,
but the two differ in a crucial aspect. Unlike the treatment of
macros, the compiler treats inline functions as true functions.
To see how this can be an important factor, consider the
following example. Suppose you have defined a macro named
multiply as follows:
#define multiply(x,y) (x*y)
If you were to use this macro as follows:
x = multiply( 4+1, 6);
By straightforward substitution of the multiply macro, the
preprocessor will transform the right-hand side of this statement
into the following code:
x = (4+1*6);
This evaluates to 10 instead of the result of multiplying (4+1)
and 6, which should have been 30. Of course, you know that the
solution is to use parentheses around the macro arguments, but
consider what happens when you define an inline function exactly
as you defined the macro:
Listing 6-5
#include
// Define inline function to multiply two integers
inline int multiply ( int x, int y )
{
return( x * y );
}
// an overloaded version that multiplies two doubles
inline double multiply( double x, double y )
{
return( x * y );
}
int main()
{
cout << "Product of 5 and 6 "
<< multiply( 4+1, 6 );
cout << "Product of 3.1 and 10.0 "
<< multiply( 3.0+.1, 10.0 );
return 0;
}
When you compile and run this program, it correctly produces the
following output:
Product of 5 and 6 = 30
Product of 3.1 and 10.0 = 31.000000
As you can see from this example, inline functions never have the
kind of errors that plague ill-defined macros. Additionally,
because inline functions are true functions, you can overload
them and rely on the compiler to use the correct function based
on the argument types.
Because the body of an inline function is duplicated wherever
that function is called, you should use inline functions only
when the functions are small in size. In addition, any looping
construct that appears within an inline function will cause the
compiler to force the function to not be inline. Most compilers
will generate a warning to the effect that the function is being
treated as a non-inline function.
6.8 Reference Types as Arguments
C normally passes arguments by value. This means that when you
call a function with some arguments, the values of the arguments
are copied to a special area of memory known as the stack. The
function uses these copies for its operation. To see the effect
of call by value, consider the following code:
Fig 6-2
void twice( int a )
{
a *= 2;
}
.
.
int x = 5;
// call the "twice" function
twice( x );
printf( "x = %d\n", x);
You will find that this program prints 5 as the value of x, not
10, even though the function twice multiplies its argument by 2.
This is because the function twice receives a copy of x and
whatever changes it makes to that copy are lost on return from
the function.
In C, the only way you can change the value of a variable through
a function is by explicitly passing the address of the variable
to the function. For example, to double the value of a variable,
you can write the function twice as follows:
Fig 6-3
void twice( int *a )
{
*a *= 2;
}
.
.
int x = 5;
// call the "twice" function
twice( &x );
printf( "x = %d\n", x);
This time, the program prints 10 as the result. Thus, you can
pass pointers to alter variables through a function call, but the
syntax is messy. In the function, you have to dereference the
argument by using the * operator.
C++ provides a way of passing arguments by reference by
introducing the concept of a reference, which is the idea of
defining an alias or alternative name for any instance of data.
The syntax is to append an ampersand (&) to the name of the data
type. For example, if you have the following:
int i = 5;
int *p_i = &i; // a pointer to in initialized to point to i
int &r_i = i; // a reference to the int variable i
then you can use r_i anywhere you would use i or *p_i. In fact,
if you write this:
r_i += 10; // adds 10 to i
i will change to 15, because r_i is simply another name for i.
Using reference types, you can rewrite the function named twice
to multiply an integer by 2 in a much simpler manner:
Fig 6-4
void twice( int& a )
{
a *= 2;
}
.
.
int x = 5;
// call the "twice" function
twice( x );
cout << "x = " << x;
As expected, the program prints 10 as the result, but it looks a
lot simpler than trying to accomplish the same task using
pointers.
Another reason for passing arguments by reference is that when
structures or classes are passed by value, there is the overhead
of copying objects to and from the stack. Passing a reference to
an object avoids this unnecessary copying and allows an efficient
implementation of OOP.
6.9 Overloaded Functions
C++ provides the ability to overload functions. Function
overloading is a type of polymorphism and is one way of allowing
the programming environment to be dynamically extended.
In C++, two or more functions can share the same name.
Therefore, a program could have several functions to perform the
absolute value function with all of them named abs. The
functions are distinguished from each other by have the types of
their arguments differ or by having the number of their arguments
differ or both. Because these functions share the same name they
are said to be overloaded. The compiler will automatically
select the correct version to call based upon the number and/or
type of arguments used to call the function.
Listing 6-6
#include <iostream.h>
//
// prototype functions
//
int abs( int );
long abs( long );
float abs( float );
double abs( double );
int main()
{
int intValue;
long longValue;
float floatValue;
double doubleValue;
//
// ask for values
//
cout << "\nEnter a negative integer value: ";
cin >> intValue;
cout << "\nEnter a negative long integer value: ";
cin >> longValue;
cout << "\nEnter a negative floating point value: ";
cin >> floatValue;
cout << "\nEnter a negative double floating point value: ";
cin >> doubleValue;
cout << "\nAbsolute values are: " << endl;
cout << "\t Integer: " << abs( intValue ) << endl;
cout << "\t Long: " << abs( longValue ) << endl;
cout << "\t Floating Point: " << abs( floatValue ) << endl;
cout << "\t Double Floating Point: " << abs( doubleValue )
<< endl;
return 0;
}
int abs( int x )
{
return (x < 0 ? (-1 * x ) : x);
}
long abs( long x )
{
return (x < 0 ? (-1L * x ) : x);
}
float abs( float x )
{
return (x < 0 ? (-1 * x ) : x);
}
double abs( double x )
{
return (x < 0 ? ((double)-1 * x ) : x);
}
This program defines four functions called abs(). With function
overloading, a single name can be used to describe a general
class of action. Unlike in C, there is no need for four
differently named functions, one for each data type to be
handled. In C++, the compiler determines which function is
appropriate to perform the task. This is a rudimentary form of
polymorphism, which is simply one interface representing multiple
methods or functions.