Previous | Table of Contents | Next |
C++ uses the term function to refer to all procedures, whether or not they have side effects or return a value. Every function has a definition and zero or more declarations, which will usually appear in different translation units. A typical function definition looks like
return-type name (parameters) block
although this description is too simple to cover, for example, functions that return pointers to functions. The parameters are a sequence of zero or more declarations, each of which declares a single variable. Because a block begins with { and ends with }, the last character of a function definition is always }. A function that does not return a value can be declared to return a value of type void.
Here is a function that computes the absolute value of its integer argument:
int abs(int x) { if (x < 0) x = -x; return x; }
From this example, we can see that the parameter x can be changed. This change does not affect the value of the argument, so that after
int n = -37; int m = abs(n);
the value of n is still -37.
More specifically, the semantics of passing an argument to a function are the same as the semantics of using that argument to initialize the formal parameter. So calling abs(n) does not change the value of n for the same reason that saying
int x = n; if (x < 0) x = -x;
does not change the value of n.
On the other hand, there is nothing to stop a parameter from having a reference type, in which case changing the parameter does change the argument:
void clobber(int& z) { z = 0; }
Now, the semantics of
clobber(n);
are the same as
int& z = n; z = 0;
Both forms set the value of n to 0.
A function can return a reference, too, so that
int& R(int& x) { return x; }
defines a function called R that accepts an integer lvalue and returns that same lvalue. Here,
R(n) = 0;
would have the same effect as
n = 0;
because calling R(n) executes R with its parameter x bound to the object n. When R returns, what it returns is therefore a reference to n, so assigning to R(n) has the same effect as assigning to n.
Functions can be recursive, but a function definition cannot physically appear inside another function definition. The restriction on nesting simplifies the implementation compared with Algol or Pascal, because it means that a function cannot ever reference the stack frame of any other function directly.
7.2.8.1. Default Arguments
It can be useful to allow the user of a function to omit one or more arguments when the omitted arguments have obvious, commonly used values. For example, consider a function that writes a newline character on a file:
void write_newline(ostream& s) { s << \n; }
We might want to be able to say that calling
write_newline();
is equivalent to calling
write_newline(cout);
One way to tell the compiler about this abbreviation is
void write_newline(ostream& s = cout) { s << \n; }
= cout is a default argument, which is a value to be used in place of an omitted argument. Only trailing arguments are permitted to be left out in this way.
7.2.8.2. Exiting from a Function
There are three ways for control to leave a function. The normal way is by executing a
return expression;
statement, which uses expression to initialize the value returned by the function. If the function returns void, expression must not appear. Moreover, control is allowed to flow off the end of a function that returns void; doing so is equivalent to saying
return;
immediately before the last } of the function.
The third way of leaving a function is by executing a throw statement. If there is no try statement in the function that catches the exception being thrown, control automatically propagates from the function to its caller, then to the caller of that function, and so on. For example:
void toss(int v) { throw v; }
is a function that always exits by throwing an exception. If we now write
void pass(int p) { try { toss(p); } catch (char c) { return c+1; } }
and then call pass in the following context:
int n = 0; try { pass(1234); } catch (int i) { n = i; }
the effect will be to set n to 1234. The reason is that we have called pass(1234), which calls toss(1234), which throws the value 1234. Executing the throw terminates execution of toss and takes us back to pass. However, the catch clause in pass is looking for a char, not an int, so control passes through pass and back to the catch that expects an int value.
One of the most common ways to use exceptions is for the particular exceptions a function might throw to be part of its interface. One can imagine a description that says This function returns the square root of its argument, except that if the argument is negative, it throws the following exception.... To make such specifications more explicit, a function definition can include an exception specification. Heres an example:
double sqrt(double x) throw(bad_sqrt) { /* ... */ }
Such a specification is a promise that the function will throw only the exception(s) named; a runtime check ensures that the promise is kept.
Previous | Table of Contents | Next |