Functions and Program Struct:
Functions break large computing tasks into smaller ones, and enable people to build on what others have done instead of starting over from scratch. Appropriate functions hide details of operation from parts of the program that don't need to know about them, thus clarifying the whole, and easing the pain of making changes.
Basics of functions:
So what defines a function? It has a name that you call it by, and a list of zero or more arguments or parameters that you hand to it for it to act on or to direct its work; it has a body containing the actual instructions (statements) for carrying out the task the function is supposed to perform; and it may give you back a return value, of a particular type.
Here is a very simple function, which accepts one argument, multiplies it by 2, and hands that value back:
int multbytwo(int x)
{
int retval;
retval = x * 2;
return retval;
}
On the first line we see the return type of the function (int), the name of the function (multbytwo),
and a list of the function's arguments, enclosed in parentheses. Each argument has both a name and a type;
multbytwo accepts one argument, of type int, named x. The name x is arbitrary, and is used only within the
definition of multbytwo. The caller of this function only needs to know that a single argument of type int is expected;
the caller does not need to know what name the function will use internally to refer to that argument.
(In particular, the caller does not have to pass the value of a variable named x.)
Next we see, surrounded by the familiar braces, the body of the function itself. This function consists of one declaration (of a local variable retval) and two statements. The first statement is a conventional expression statement, which computes and assigns a value to retval, and the second statement is a return statement, which causes the function to return to its caller, and also specifies the value which the function returns to its caller.
The return statement can return the value of any expression, so we don't really need the local retval variable; the function could be collapsed to
int multbytwo(int x)
{
return x * 2;
}
How do we call a function? We've been doing so informally since day one, but now we have a chance to call one that we've written, in full detail. Here is a tiny skeletal program to call multby2:
#include <stdio.h>
extern int multbytwo(int);
int main()
{
int i, j;
i = 3;
j = multbytwo(i);
printf("%d\n", j);
return 0;
}
C Preprocessor:
-
The C preprocessor is a very simple but powerful tool in the C programming language. Every C program is processed by the preprocessor prior to compiling it. The preprocessor allows to combine files using the #include or to define constants and macros using #define.
Most often when you use the C preprocessor you will not have to invoke it explicitly: the C compiler will do so automatically. However, the preprocessor is sometimes useful individually. It is started using the command cpp. The following program excerpt (preprocess.txt - note the .txt extension: the file is NOT a C program, but a small excerpt of a program to illustrate the working of the preprocessor) defines two macros max and square and shows how these may be called:
#define max(A, B) ((A) > (B) ? (A) : (B))
#define square(X) X * X
max(a, b);
max(a+1, b+1);
square(x);
square(x+1);
This program excerpt can be processed manually using cpp as follows:
Pass-by-value parameters:
In C++, the default parameter passing method is pass-by-value or sometimes called pass-by-copy.
The following function adds two floats and returns the result in a float:
float addf(float a, float b)
{
float c=a+b;
a = a + 10; // to demonstrate 'pass-by-value'
return c;
}
I have deliberately inserted the nonsense instruction
a = a + 10;
for demonstration purposes; whilst this has an effect on the local variable a it has no effect on the argument in the caller. Thus:
float x=10, y=20, z=0;
z = addf(x, y);
cout<< x<<", "<< y<<", "<< z << endl;
will result in 10, 20, 30
Pass-by-reference parameters:
Passing by by reference refers to a method of passing the address of an argument in the calling function to a corresponding parameter in the called function.
In C, the corresponding parameter in the called function must be declared as a pointer type.
In C++, the corresponding parameter can be declared as any reference type, not just a pointer type.
The following function swap uses reference parameters:
void swap(int& a,int& b)
{
int temp = a;
a = b;
b = emp;
}
Now, swap does have an effect on the arguments in the caller - as it must to be of any use. Thus:
float x=10, y=20, z=0;
swap(x, y);
cout<< x<<", "<< y<< endl;
will result in 20, 10
Notice that the caller need not make any special indication of the reference nature of the arguments - that is all handled by the definition of the function: int& a,int& b.
Thus, void swap(int& a,int& b)
Just to drive the point home, let us examine a naive swap which uses pass-by-value.
void swapSilly(int a,int b) //naive -- pass by value
{
int temp=a;
a=b;
b=temp;
}
Now, swap Silly does NOT have an effect on the arguments in the caller, hence the function has no effect. Thus:
float x=10, y=20, z=0;
swapSilly(x, y);
printf ("%d , %d \n",x,y);
will result in 10, 20
Example program:
1. Swap two numbers
pass-by-reference via pointers
Just for completeness, we will include a version of swap which demonstrates how pass-by-reference can be programmed via pointers. This is how it must be done on C, which does not have a reference type qualifier.
The following function swapP uses pointer parameters:
void swapP(int* pa,int* pb)
{
int temp=*pa;
*pa=*pb;
*pb=temp;
}
Notice that it is what pa, pb point-to that are swapped, not pa, pb themselves, i.e. *pa, *pb - dereferenced.
swapP has an effect on the variables pointed-to by the caller arguments. Thus:
float x=10, y=20, z=0;
swapP(&x, &y);
cout<< x<<", "<< y<< endl;
will result in 20, 10
Note that in this case the caller must pass pointers to the variables, i.e. &x, &y are passed. This, in addition to the extra complexity of the function, makes this programmed pass-by-reference error-prone and generally less satisfactory than proper pass-by-reference.
It is worth noting that in the case of swapP the pointer values are still passed-by-value - it's just that the values are pointers, and so can be dereferenced to access the variables that they reference - in the caller!
Semantics of parameter passing:
The distinctions between the previous examples can be clearly defined by considering the detailed semantics of parameter passing by value and by reference.
7.4.5.0.1 Pass-by-value
Recall the example of pass by value. Here x, y are local variables in the caller; when they are created, they are initialised with the values 10, 20.
float x=10, y=20;
When swapSilly is called, swapSilly(x, y); the following happens: variables local to swapSilly are created (int a, int b)x, y and these are initialised with the values of - almost as if we had the definition: int a=x, int b=y. Hence, computations involving a, b are on these entirely separate and local variables.
Pass-by-reference
Using the same example, x, y are local variables in the caller; when they are created, they are initialised with the values 10, 20. Now, recalling chapter 5, one can define a reference float& rx and initialise it with x - not the value of x, rx is an alias for x - whatever is assigned to rx is assigned to x: x and rx refer to the same object in memory.
float x=10, y=20; float& rx=x;
Likewise when void swap(int& a,int& b) is called, swap(x, y); the following happens: reference variables are created
(int& a, int& b) and these are initialised with variables x, y - almost as if we had the definition: int& a=x, int& b=y. Hence, computations involving a, b also involve x, y.
Pass-by-reference via pointer
Again using the same example, x, y are local variables in the caller; when they are created, they are initialised with the values 10, 20.
Likewise when void swapP(int* pa, int* pb) is called, swap(&x, &y); the following happens: temporary pointer variables are created, and these are initialised with pointers to variables x, y, i.e. as if
(int *px = &x, int* py = &y).
Pointer variables local to swapP are created (int* pa, int* pb) and these are initialised with the values of temporaries px, py - almost as if we had the definition: int* pa=px, int* pb=py.
Computations involving pa, pb are on these entirely separate and local pointer variables, but, being pointer variables, they can be dereferenced to access the actual variables x, y. Hence, computations involving *pa, *pb also involve x, y.
Java pass by value only
Well, sort-of! All elementary types, e.g. int, float, double are passed by value only. Thus, a function like swap cannot be done.
On the other hand, just like arrays in the next section, in Java all non-elementary objects are always references.
Arrays as Parameters
Arrays are always pass-by-reference - though implicitly so.
When passing arrays as parameters, you need declare only that it is an array - you need not give its length. For example, a function getline which reads a line of characters:
int getline(char s[],int lim)
{
...
s[i]=c;// or *(s+i)= c;
...
}
As we have already noted in chapter 6, C++ arrays are closely related to pointers, hence the following is entirely equivalent:
int getline(char *s, int lim)
{
...
*(s+i)=c; // or, s[i]=c;
...
}
Function getline can be called either as:
int nchars;
const int n=100;
char s[101];
nchars = getline(s, n); // or
nchars = getline(&s[0], n);
Default parameters:
In a function definition a formal parameter can be given a default value.
Example.
void init(int count=0, int start=1, int end=100)
{
// body ...
}
Call:
init(); //is equivalent to
init(0,1,100)
//or
init(22,23); //equivalent to
init(22,23,100);
Note: only trailing parameters can be defaulted:
void init(int count=0,int start,int end); //ILLEGAL
In normal programming you should use default parameters only if you have a compelling reason to do so. But, can be very useful in some class member functions, see later chapters.
Function return values:
- Functions can return the following values:
- All elementary types: int, char, float, etc. ....
- Pointers - to anything, but beware of dangling pointers
- see chapter 6. References - but beware of dangling references, see chapter 6.
- They cannot return arrays, but, of course, they can return a pointer to an array.
- Only one value can be returned.