A function can have no arguments. In C++, a function is called by using its name followed by a parenthesized list of arguments. So this function, from one of the example programs
// calculate length
int
str_length(char * str)
{
char *ptr=str;
int len=0;
while(*ptr++) len++;
return(len);
} // str_length
The return type is integer. This means that anywhere an integer can be used, a call to this function can be used. Its name is str_length and it takes one argument, a character pointer. At the end, it returns an integer value. In between the braces is the code the implements the function. Variables can be declared inside the function that are local to the function. This means that the variables, like len, are only used inside the function. This variable len would be different from another variable len used somewhere else in the program. A few other comments. If the function doesn't return anything, the return type is void. It is possible to make a function that takes an unknown number of arguments, but this works in a very different way.
Some details about the return statement. If the function
doesn't return a value, the return statement is optional as a return occurs
when the end of the function is reached. But a return statement can be
used if you want to exit the function early. In this case, the form is
just
return;
If there is a value to return, there are two forms.
Both forms work exactly the same.
return expr;
return (expr);
A function can return only one value.
About function calls and how they work.
A typical function call looks like this
len=str_length(instr);
When this is executed, the control transfers from where
the call to str_length is in the code to the beginning of the code in the
str_length body. The variables described in the function header are set
with the values in the function call. The variables in the function header
are called the
formal parameters. The values in the call to the
function are called the actual parameters. The formal parameters
are like local variables in the function code. The code in the function
is executes in the usual order. When a return statement is executed, control
goes back to the main program just at the point it left. If the function
returns a value, then it is as if that value was put in place of the function
call. So if the actual call was like this
len = str_length("hello");
// note the use of a string constant
Then control starts at the top of the str_length function.
The formal parameter is a character pointer. The type of a string constant
is a character pointer so the types match. All the arguments to the function
are evaluated before the code is executed. But C++ doesn't guarantee that
they are evaluated in any particular order. So don't assume it is left
to right. Then the formal parameter, str is set to the value "hello".
Two local variables are created, len ( set to 0) and ptr (set to str).
The while loop bumps the counter len for each character in the string and
stops when it sees the zero (Nul) at the end. So in this case, len = 5.
The return statement send the 5 back to the calling function. So the program
acts as if the line above was changed to
len = 5;
Then the program goes on from that point as if it had
never called the function.
Function declarations are used in cases where the compiler needs to know about the function before it sees the code. An example of this is in the example code. Here the main function comes before the str_len function. But the compiler needs to know about the str_len function inside main so it can tell if the call is legal or not. So we put the function declaration at the top of the file. The declaration looks just like the definition except for two things. The body code is missing and the formal parameters names are not needed. A common use of declarations is in header files so the compiler can know about functions that may be in other files and which may not have been written yet.
Functions in C++ may have default arguments. For example,
void str_print(char * str, int length =
10);
This declares a function with one default parameter. If
the function is called with only on parameter, like
str_print("hello");
Then the value of the second parameter is set to 10.
If a value is supplied, that is used rather than the default. The default
parameter is mentioned only in the declaration, not in the definition.
Also, if there is a default parameter, then all parameters after that one
nust also have defaults.
All parameters in a C++ function are pass-by-value.
There are two major ways information is passed to a function. One is pass-by-value.
This means that the expression that is the parameter is evaluated and the
result is stored in a local variable. This value is a copy of the original.
In this example from the code,
len=str_length(instr);
A copy of the value of instr (a pointer to an array)
is stored in the local variable str. If we were to change the str_length()
function to add a line
str="hello again";
this would have no affect on the value of instr. This
is because str is a local variable and is not related to instr.
The other main method is pass-by-reference. Instead of a copy of the value being passed, a reference to the actual parameter is passed. This means that if the formal parameter is changed, so is the actual parameter. There are times when this is what you want and the details are in the next section.
Recursion is supported by C++. This allows a function to call itself. There are many problems, for example, linked lists, where recursion makes the program easier to understand. Key to the ability to do recursion is the fact that the formal parameters are local variables. So each time a function calls itself, there is a new copy of the variables. It is important to remember that the computer views all function calls the same, even if it is calling itself.
We would see printed
before: 8
after: 8
And this is what we often want. But sometimes, we want to change the
actual parameter when we change the formal parameter. If we use pass_by_reference,
then when we change the formal parameter, the actual parameter is changed.
So in this example,
int outside_a=8;
cout << "before: " << outside_a
<< endl;
pbr(outside_a);
cout << "after: " << outside_a <<
endl;
where the function pbr() is defined as
void
cbv(int& inside_a) {
inside_a=7;
} // cbv
We would see printed
before: 8
after: 7
the corresponding code if we didn't have pass-by reference is
int outside_a=8;
cout << "before: " << outside_a
<< endl;
cbv(&outside_a);
cout << "after: " << outside_a <<
endl;
where the function cnv is defined as
void
cbv(int *inside_a) {
*inside_a=7;
} // cbv
We would see printed
before: 8
after: 7
This works because we passed the address (a pointer) of outside_a and
when we changed *inside_a, this changed the thing
it pointed at, which is outside_a.
Common uses for pass-by-reference are to update something when we want the return value to to tell us if it worked or not. Or when we need to change or return several value. It can also be be used even when we don't want to change the argument. If the thing we are passing is large, some kind of structure for example, pass-by-reference is better because pass-by-value makes a copy of the who structure, which can take time and uses memory. Also, you may have functions where you want to change the parameters sometimes and not others.
The pass-by-reference method is C++ is much easier to use and get right than the pointer method. If you forget the ampersand on a call, the function will still work but not do what you expect. This can be hard to debug.
References can be used outside of function calls. In this case they
are a kind of alias of another variable. Look at the following declarations:
int normal_a=42;
int *pointer_a = &normal_a;
int & ref_a = normal_a;
ref_a has become another name for normal_a. To use pointer_a
to change normal_a, you have to do
*pointer_a = 56;
But you can use
ref_a = 56;
pointer_a can be changed to point to another integer, but ref_a is
always attached to normal_a.
class string
{
public:
char * operator+(const
string str)
{
char *result= new[this->length() + str.length() + 1];
strcpy(result,this->value);
strcat(result,str.value);
return(result);
}
private:
char value[32];
}
iostream overloading
You can create
your own insertors by overloading the << operator. To print a name
with a label for a class you created:
ostream &
operator<<(ostream& os, const myclass &t)
{
os << "myclass:name is " << t.name << endl;
return os;
} // << operator
increment
and decrement overloading
It is possible
to overload the ++ and -- operators. You can even differentiate between
pre and post increment. The compiler sees the prefix call,
++a;
as a call to
operator++(a)
However, a postfix
call is seen differently:
a++;
operator++(a,int)
The compiler provides the dummy second argument.