next up previous
Next: About this document ...

CSCI.1200 - Computer Science II
Summer, 2002
Worksheet 2
Functions, Function Calls, Variable Scoping

Reading: Deitel & Deitel, Chapter 3, sections 3.1 - 3.11

Functions

The programs that we have seen so far involve only a single function, main, but any program of any size will involve multiple functions. Beginning programmers have a tendency to put all of their code in main, but this quickly leads to a large function which is hard to read and hard to debug. Good programs consist of lots of small functions, each of which usually does only one thing.

Here is a small program which demonstrates the use of function calls in C or C++.

     1	#include <iostream>
     2  using namespace std;
     3  int square(int);
     4  int main()
     5  {
     6     int x, xsquared;
     7     cout << "Enter a number: ";
     8     cin >> x;
     9     xsquared = square(x);
    10     cout << "The square of " << x << " is " << xsquared << endl;
    11     return 0;
    12  }
    13  int square(int n)
    14  {
    15     int nsquared;
    16     nsquared = n * n;
    17     return nsquared;
    18	}

Let's go through it line by line:

3 int square(int);

This is called a function prototype. If a function is to be called before it is defined, there must be a prototype. This tells the compiler that there will be a function defined later called square, which takes one argument of type int, and returns an integer. An argument is a value which is passed from the calling function to the called function. There is no limit on the number or type of arguments that a function can have. The return type of a function can be any type which has been defined. In addition, a function can be of type void which means that it does not return anything. Notice that a function cannot return more than one value.

9 xsquared = square(x);

This is where the function square is called or invoked. When a function is called, processing of the calling function (main in this case) stops, and processing is transferred to the code of the called function (square in this case). The next statement executed is the first statement of the called function.

The function square is passed one argument, in this case, the variable x. The function square will return an integer value, which is copied to the variable xsquared.

    13	int square(int n)
    14	{
    15	    int nsquared;
    16	    nsquared = n * n;
    17	    return nsquared;
    18	}

This is where the function square is actually defined. The format of a function definition is:

return-value-type function-name (parameter list)
{
declarations and statements
}

The variable n is a formal parameter. Ordinarily, when a function is called, the value of each argument, called the actual parameter, is computed and this value is copied to the formal parameter. (This parameter passing method is known as call-by-value; another method, call-by-reference, is described later in this worksheet.)

Line 17 is a return statement. Any function other than a void function must have at least one return statement, in which a value is returned. The keyword return must be followed by an expression which is of the same type as the type of the function. Functions of type void may also have one or more return statements which are not followed by any expression. Once a return statement is encountered in a function, processing returns to the calling function. Any code which directly follows a return statement is called dead code because it will never be executed; you should never have dead code in your programs. Note: since return can be followed by an expression, the square function could be written more briefly:

    int square(int n)
    {
       return n * n;
    }

There is no limit to the number and type of arguments that a function can take. A void function called fctn which takes three arguments, two integers and a floating point number, would have a function prototype like this:
void fctn(int, int, double);
and when it is actually declared, the first line of the declaration would look like this:
void fctn(int n1, int n2, double f1)

When calling a function, the order of the actual parameters determines which values get copied to the formal parameters. The value of the first argument (first actual parameter) is copied to the first formal parameter, the value of the second argument is copied to the second formal parameter, and so on. The compiler will check to confirm that the number and type of actual parameters conform to the number and type of formal parameters. It also checks to make sure that the number and type of formal parameters in the function definition agrees with the function prototype.

Exercise 1: Write a function sum which takes three arguments, all double precision floating point numbers, and returns the sum of these three. Write a main which prompts the user to enter three floating point numbers and passes them to sum, which will return a value to main. main should print this value on the screen.

Scope of variables

Variables defined inside of a function are local to that function. No other function knows about them or can use them. An attempt to access the variable x declared in the function square from outside would result in a compiler error.

It is possible to declare variables outside of any function. These variables are called global, and all functions defined after the declaration of a global variable can use it. Here is an example:

#include <iostream>
using namespace std;
int x;  // a global variable
int main()
{
   ...
   x = 17;  // main can access the global variable x
   ...
}

void SomeFunction()
{
    x = 46; // any other function can also access x
}

Global variables should be used sparingly because they make programs harder to debug and modify. If many functions can modify a global variable, it is easy to lose track of which functions are doing what in a large program.

Two or more functions can have local variables of the same name. If a function declares a local variable which has the same name as a global variable, this declaration masks the global variable and any reference inside that function to the variable of that name would be assumed to be the local variable, not the global variable. In the above example, suppose SomeFunction had declared a local variable called x inside it. Now there are two variables with the name x, one global and one local. In the statement x = 46; the reference to x would be to the local variable, not the global variable.

Call-by-value vs. Call-by-reference

Parameter passing in C and C++ is normally call-by-value. When a function is called, and one or more variables are passed as arguments, the called function makes copies of the values of these arguments. This means that ordinarily a called function cannot change the value of a variable in the calling function. Here is an example:

#include<iostream>
using namespace std;
void Silly(int); // function prototype
int main()
{
   int x;
   x = 17;
   Silly(x);
   cout << x << endl;
   return 0;
}

void Silly(int n)
{
   n = 46;
}

This program will print 17, not 46, because in Silly a copy of the value of x is made and assigned to n, so changing the value of n in the called function has no effect on the value of the actual parameter in the calling function. Note that the variable n in Silly could have been called x, without changing the functioning of the program.

C++ (but not C) allows a different type of parameter passing called call-by-reference. In call-by-reference the called function, instead of making a copy of the value of a parameter, identifies the formal parameter with the address of the actual parameter. This means that the called function can change the value of a variable in the calling function. To make a parameter a reference parameter, precede its name with an &. You must also follow the type in the argument list of the function prototype with an &. Here is an example:

#include<iostream>
using namespace std;
void Silly(int &); // function prototype
int main()
{
   int x;
   x = 17;
   Silly(x);
   cout << x << endl;
   return 0;
}

void Silly(int & n)
{
   n = 46;
}
This program will print 46.

Exercise 2: What would the following program print? (answer on last page)

#include <iostream>
using namespace std;
int y,z;
int function(int, int &);
int main()
{
   int v,w,x,y;
   w = 1;
   x = 2;
   y = 3;
   z = 4; 
   v = function(w,x);
   cout << v << " " << w << " " << x
        << " " << y << " " << z << endl;
   return 0;
}

int function(int m, int & n)
{
   int w;
   w = 5;
   m = 6;
   n = 7;
   y = 8;
   z = 9;
   return 10;
}

Arrays as function arguments

Arrays can be used as arguments to functions, but array parameter passing is always call-by-reference; in other words, the function can change individual values in an array and the changes will be retained when control returns to the calling function.

When arrays are passed as arguments, it is not necessary to pass in the array size; rather, the fact that a variable is an array is signified by following the variable name with a pair of empty square brackets ([]). Here is an example:

1     #include <iostream>
2     using namespace std;
3     void fctn(int[]); // function prototype
4     int main()
5     {
6         int a[5];
7         for (int i = 0; i < 5; ++i) 
8               a[i] = i;
9         fctn(a);
10        for (i = 0; i < 5; ++i) 
11             cout << a[i] << ' ';
12        cout << endl;
13        return 0;
14    }
15    	
16    void fctn(int z[])
17    {
18    	   z[1] = 345;
19    	   z[3] = 678;
20    }

The above program prints 0 345 2 678 4. Notice how the formal parameter of an array is declared in the function prototype (line 3) and in the function definition (line 16). Also notice how the array is passed as an argument (line 9) and notice that the elements of the array which are changed in the function remain changed in the calling function.

The advantage of this method is that a single function can be passed arrays of different sizes, but the disadvantage is that the function does not know how large an array is. Often, the solution to this problem is to pass in another parameter to the function which gives the size of the array.

Library functions

There are an enormous number of library functions for C and C++, far too many to exhaustively list here, but here is a small list of commonly used functions. Many of these require that you use additional include files. If so, these are listed as well.

Character Handling
int isalpha(char c) returns 1 if c is in the range A .. Z or a .. z, otherwise it returns 0.

int isdigit(char c) returns 1 if c is in the range 0 .. 9, otherwise it returns 0.

char tolower(char c) If c is an upper case letter, it returns the lower case version; otherwise it returns c.

char toupper(char c) If c is a lower case letter, it returns the upper case version; otherwise it returns c.

Mathematics
#include <math.h>
double cos(double x) returns the cosine of x where x is measured in radians. There is a complete set of trig. functions like this.

double sqrt(double x) returns the square root of x.

double log(double x) returns the natural logarithm of x.

String Handling
strcpy(char s1[], char s2[]) copies the characters in s2 into s1 up to and including the first null character.

int strcmp(char s1[], char s2[]) returns a positive integer if s1 is lexically greater than s2 (i.e. would follow it alphabetically), a negative number if s1 is lexically less than s2, or zero if the two strings are the same up to the first null character.

strcat(char s1[], char s2[]) concatenates s2 onto the end of s1. For example if s1 is the string cat and s2 is the string bird, after calling this function, s1 would be the string catbird. Note that the size of the array s1 must be large enough to accommodate the longer string.

Character Input
#include<iostream>
cin.getline(char s[], int max) reads a line of input from standard input (normally the keyboard) into the string s up to a maximum of max characters or until the user hits the Return key. This differs from cin >> s; because the cin.getline function includes spaces as well while cin >> s; will read characters into s only up until the first space. The size of the array s should always be at least one larger than max because a \0 is appended onto the end of the string.

Exercise 3: It is instructive to see what is involved in writing some of these library functions. Write your own version of the functions strcpy and strcat, calling them mystrcpy and mystrcat. Here is a main to test your functions. Remember that you can assume that all strings are terminated with a '\0'.

#include <iostream>
using namespace std;
// put your function prototypes here
int main()
{
   char mystring[80];
   mystrcpy(mystring,"Programming");
   mystrcat(mystring, " is fun!");
   cout << mystring << endl;
   // should print "Programming is fun!"
}

Random Numbers

Many applications, simulations in particular, require the use of random numbers, and so there are a number of library functions which generate random numbers. The function int rand() returns a random integer in the range $0$ .. $2^{15}-1$ each time that it is called.

Random number sequences are based on a seed, i.e. an initial value. The default value of the seed is zero, and this means that you will get the same sequence of random numbers each time that you run your program. If you want a different sequence of random numbers each time that you run the program, you must initialize the seed to a different random value each time. The function that initializes the seed is void srand(int). One way to do this is by using the current time, which would be different each time the program is run. To do this, include this statement in your program before you start calling rand()
srand((unsigned)time(NULL));
If you use the time function, include the header file time.h.

Here is a short program which displays ten random numbers.


 
#include <iostream> 
#include <time.h>
using namespace std;
int main()
{
   int n;
   srand((unsigned)time(NULL));
   for (int i=0;i<10;i++) {
       n = rand();
       cout << n << endl;
   }
   return 0;
}

On Unix systems the comparable functions are random() and srandom() and random returns an integer in the range 0 .. $2^{31}-1$.

Most applications need a random number in a defined range. For example, a dice throwing simulation would require a random number in the range 1 .. 6. To convert from the return value of rand() to an integer in this range, use the modulo function. Any number modulo 6 will return a value in the range 0 .. 5, so the following statement will assign a random value in the range 1 .. 6 to x each time that it is called.
x = rand() % 6 + 1;

Answer to Exercise 2 10 1 7 3 9




next up previous
Next: About this document ...
Paul Lalli 2002-05-17