FUNCTIONS Aims
To enable
students to: ·
handle
function prototypes ·
understand
reference parameters ·
default
parameter values ·
use
function and operator overloading ·
inline
functions Handle function
prototypes
Whenever
writing source code the compiler must have seen a function before it is used.
If the compiler has not previously encountered a function it will raise an error
on its use - as far as the compiler is concerned the invocation of the function
is just a series of unrecognisable characters. In OOP
programming each class should reside in a separate source file. In this
situation how can the source in one file make use of function declarations
in another? The answer is to use function prototypes. A function
prototype tells the compiler what a function looks like, but does not actually
define it. For example, a function may exist entitled max that takes in three numbers, x, y and z, whose purpose is to return the
largest of the three numbers. The declaration of the prototype appears as float
max (float x, float y, float z); Note the ; at the end of the line. This
indicates to the compiler that a prototype has bee provided. As no function
declaration is provided at this point the variables x,y and z are not needed, the prototype can be reduced to: float
max (float, float, float); Both forms
of prototype are equally valid. The float preceding the function name max indicates that it returns a float value, in this case the largest of
the three values provided. Understand
reference parameters
In order
for a function to perform any useful task it must be able to take in and pass
back values. Frequently functions are written to take in values through a
parameter list (the variable names included within the brackets) and pass back
a value to the caller through the return type (the data type preceding the name
of the function). The values
being passed in are said to be passed by value. The compiler provides an exact
copy of the variable being passed by the caller for the function to work on.
The compiler can manipulate this data in any way it likes, but none of the
changes will be propagated back to the caller. The only value that the function
can pass back is through the return type. Consequently the function must be
used in the form of an expression, as in: float
max (float, float); … void
main () { float fValue1, fValue2 = 2f, fValue3 = 4f,
fValue4 = 0f; fValue1 = max (fValue2, fValue3, fValue4); } After
execution fValue1 holds the value of 4. Obviously
this can be limiting, so C++ provides the ability to pass parameters by
reference. In this case any changes made by the function to a variable passed
in by reference will be propagated back to the caller. To indicate to the
compiler that a parameter is to be passed by reference the & character is placed after the
parameter type. If the max function introduced previously was
intended to change the first parameter passed in to 0 after execution the prototype
becomes: float
max (float &, float, float); while the
invocation stays the same: void
main () { float fValue1, fValue2 = 2l, fValue3 = 4l,
fValue4 = 0l; fValue1 = max (fValue2, fValue3, fValue4); } After
execution fValue1 holds 4 and fValue2 contains 0. It is
possible to pass in values by reference to a function and ensure it can not
change the value of the variables by preceding the parameter definition with
the const keyword, as in float
max (float &, const float &, const float &); This
version of the max function takes three reference
parameters, ensuring that neither the second or third values passed in are
changed during its execution. Why use the
const keyword and not just restrict the
function to using call by value? There are two reasons, the first is that the
function can not make any changes to the variable within itself - any attempt
to generates a compilation error, but the most important is efficiency. As
discussed previously, passing parameters by value involves the compiler making
an exact copy of the data involved and storing it on the function stack. If a
large amount of data is being passed between two functions this can involve a
considerable amount of work for the machine copying data around between
different parts of the program. Reference parameters can be used to efficiently
pass structures between functions. A structure in C++ is a collection of data
items grouped together, for example the following defines a structure called
person and declares a variable of that type: struct
person// Declare person
struct type { intiAge;// Declare
member types float fWeight; charcName[25]; }; person
me; When
passing a variable by reference only a pointer to it is passed between the
functions, in the case of the variable me this will be a pointer to the first element of the structure (in this
case iAge). If the structure was passed by
value the system would have to make an exact copy of the variable me - a time consuming task. Passing
structures by reference permits large amounts of data to be passed between
functions with minimal overhead, by preceding the parameter with the const keyword the data passed is
protected from modification. Default parameter
values
When
writing programs it pays to make them as flexible as possible. This has a knock
on effect on individual functions - they must be able to handle many diverse
situations efficiently. To describe a given situation to a function may require
many parameters, in most situations many of them may have the same value - some
may only take different values in very rare situations. To minimise the number
of parameters that must be passed to each invocation of a function it is
possible to make use of what are termed default parameters. When a
function is prototyped or defined the developer can specify a default value for
some (or all) of the parameters. Callers of the function do not need to provide
values for such parameters, the function will use the default values provided
by the original author of the code, for example the following code defines a
function taking four parameters, the last two having default values: float
func1 (int a, int b, int c=10, int d=5); The
function func1 can be called as follows: void
main () { func1 ();// Illegal - must supply t least two parameters func1 (27, 1);// The function will work on the values 27, 1, 10, 5 func1 (27, 1, 5, 77); // The function will
work on 27, 1, 5, 77 } The
following points must be observed when using default parameters: They must
be the last argument(s) in a function call, the following prototype is illegal
because the default parameters a and b precede the standard arguments c and d: float
func1 (int a = 10, int b = 5, int c, int d); A default
parameter can not be redefined even if the redefinition is identical. The
assignment of 5 to d in the function declaration is not possible as this has already
taken place in the prototype: // Prototype for func1 function. float
func1 (int a, int b, int c=10, int d=5); ... // Declaration for func1 function. float
func1 (int a, int b, int c, int d=5) { ... } Use function and
operator overloading
Function
overloading is the practice of supplying more than one definition for a given
function name in the same scope (source file, class, etc.) - i.e. the same
function name can manipulate different types of data. The compiler is left to
pick the appropriate version of the function based on the arguments with which
it is called - no notice is taken of the return type. The following
declarations define two versions of the function max, each taking different arguments. The function max is considered an overloaded
function. float max( float f1, float f2 ) { if (f1 >
f2) { return f1; } else { return f2; } } int max( int i1, int i2 ) { if (d1 >
d2) { return d1; } else { return d2; } } The
function max can be used as follows: main() { int i = max( 12, 8 ); float f = max( 32.9f, 17.4f ); return i + (int)f; } Why use overloaded
functions? Overloaded functions enable programmers to specify different
behaviours for a function, depending on the types or number of arguments. For
example, a print function that takes a string (or char *) argument performs very different
tasks than one that takes an argument of type float. Overloading permits uniform naming - developers do not
have to invent names such as sz_print
or f_print. Just as
functions can be overloaded, so can operators (+, -, /, etc.). It is possible for a programmer to redefine the
behaviour of an operator for a particular class. The name of an overloaded
operator is operatorx, where x is the operator. For example, to
overload the addition operator, you define a function called operator+. Similarly, to overload the
addition/assignment operator, +=,
define a function called operator+=. Operator overloading is frequently
used to make programmers intentions clearer, for example a class, CMyStyring,is defined to manipulate strings and it would be desirable to be able to
compare the data contained within two instances using the following code: if
(object1 == object2) Unfortunately
the standard definition for the == operator will not compare the data contained within the class, but
rather the location in memory of the instance. Even if the two objects
contained an identical string, because they exist in different memory locations
the comparison will fail. To make the above line of code work in the way
expected it is necessary to overload the == operator, as in: int
CMyStrig::operator== (char *rhs) { // Compare the right hand side of the ==
operator with the string value // stored within the class by member
variable m_string. if (strcmp (rhs, m_string) == 0) { return TRUE; } else { return FALSE; } } Two forms
of operator exist, unary and binary. To overload a unary operator for a
classthe following form of prototype
for a member function (and associated definition) is used: ret_type
operator!(); To overload
a binary operator for a classthe
following form of prototype for a member function (and associated definition)
is used: ret_type
operator==(arg); Inline functions
Normally
when a section of code calls upon a function (either user written or provided
in a library) the program has to store the current values of any variables
local to the current function, the state of the registers in the processor,
etc. before jumping to another section of the program where the function is
defined. It is possible to eliminate this overhead through the use of inline
functions. An inline
function is very similar to a constant defined by the #define statement, i.e. all references to
the function are expanded in place within the source file. The program does not
need to jump to another place in order to execute the function, instead its
body is copied into the source file at the location it is called. While
considerably improving execution speed it makes the resultant program much
larger - each reference to an inline function produces a copy of it.
Consequently inline functions should only be used for small, frequently used
pieces of code - large or infrequently used ones should be defined normally. There are
two ways to define an inline function. The first is to place the definition of
the function within the body of a class definition, such as: class CAccount { public: CAccount(double initial_balance) { balance = initial_balance; } double
GetBalance(); double
Deposit( double Amount ); double
Withdraw( double Amount ); private: double
balance; }; where the CAccount function is implicitly inline. The second
is to use the inline keyword as in inline
double Account::GetBalance() { return balance; } |
|
||
|
|||
|
Last updated: 11th July 2006. copyright © 2006 Greystoke Systems Ltd. Web address: http://www.gsys.biz/Documents/Services/Tuition/CityAndGuilds/7261-249/Functions.htm |