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 }
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 }
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 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
..
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;
}
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