Like operators, functions can be overloaded, meaning that the same name may refer to multiple different functions.
7.1 Defining a Function
A function is uniquely represented by a name and a set of operand types. Its operands, referred to as parameters, are specified in a comma-separated list enclosed in parentheses. The actions that the function performs are specified in a block referred to as the function body. Every function has an associated return type.
Calling a Function
To invoke a function, we use the call operator, which is a pair of parentheses.The operands to the call operator are the name of the function and a (possible empty) comma-separated list of arguments.
Calling a function does two things:
1. It initialize the function parameters from the corresponding arguments
2. transfers control to the function being invoked: Execution of the calling function is suspended and execution of the called function begins.
Function Body is a Scope
The body of a function is a statement block, which defines the function's operation. The block forms a new scope, names defined inside the block is only accessible within the block, they are referred to aslocal variable.
Function complete when a return statement is encountered.
Parameter and Arguments
Like local variables, the parameters of a function provide named, local storage for use by the function. The difference is that parameters are defined inside the function's parameter list and are initialized by arguments passed to the function when the function is called.
An argument is an expression. It could be variable, literal constant or expression. We must pass exactly the same number of arguments as the function has parameters. The argument must have the same type or have a type that can be implicitly converted to the parameter type.
7.1.1 Function Return Type
The return type of a function can be built-in type (int, double), class type, or compound type (int&, string*).
A return type also can be void, which means that the function does not return a value.
Date &calendar(const char*); //return reference to Date
void process(); //process does not return a value
//ok: pointer to first element of the array
int *foo_bar()
...
The function returns a pointer to int and that pointer could point to an element in an array.
Function must specify a return type. It is illegal to define or declare a function without an explicit return type. (In pre-Standard C++, a function without an explicit return type was assumed to return an int.)
7.1.2 Function Parameter List
The parameter list of a function can be empty. Empty parameters can be written either with an empty list or parameter list containing single keyword void:
void process() //implicit void parameter list
void process(void) //equivalent declaration
A parameter list consists of a comma-separated list of parameter types and parameter names.Even when the types of two parameters are the same, the type must be repeated.
int manip (int v1, v2) //error
int manip (int v1, int v2) //ok
No two parameters can have the same name. A variable local to the function may not use the same name as the name of any of the function's parameters.
Names are optional, but normally all parameters are named. A parameter must be named to be used.
Function define & declaration
Generally, a function either has to be declared or defined before it is called:
1. Define function before it is called:
int addition (int a, int b)
{
int r;
r=a+b;
return (r);
}
int main ()
{
int z;
z = addition (5,3);
cout << "The result is " << z;
return 0;
}
2. Declare function before it is called:
void odd (int a); // declared functions, can neglect parameter name
void even (int a);
int main ()
{
int i;
do {
cout << "Type a number (0 to exit): ";
cin >> i;
odd (i);
} while (i!=0);
return 0;
}
void odd (int a)
{
if ((a%2)!=0) cout << "Number is odd.\n";
else even (a);
}
void even (int a)
{
if ((a%2)==0) cout << "Number is even.\n";
else odd (a);
}
7.2 Argument Passing
Each parameter is created anew on each call to the function. The value used to initialize a parameter is the corresponding argument passed in the call.
Parameters are initialized as variables:
1. If the parameter has a nonreference type, then the argument is copied.
2. If the parameter is a reference, then the parameter is just another name for the argument
7.2.1 Nonreference Parameters
When a parameter is initialized with a copy of the corresponding argument, the function has no access to the actual arguments of the call.
Pointer Parameters
A parameter can be a pointer, in which case the argument pointer is copied. If the function assign a new pointer value to the parameter, the calling pointer value is unchanged. But the function can assign through the pointer and change the value of the object to which the pointer points:
void reset (int *ip)
{
*ip = 0; //changes the value of the object to which ip points
ip = 0; //changes only the local value of ip; the argument is unchanged.
}
If we want to prevent changes to the value to which the pointer points, then the parameter should be defined as a pointer to const:
void use_ptr(const int *p)
{
... //use_ptr may read but not write to *p
}
When parameter is const pointer, we can use both int* or const int* as argument.
When parameter is plain pointer, we can only use int*.
Because we may initialize a pointer to const to point to a nonconst object but may not use a pointer to nonconst to point to a const object.
Const Parameters
We can call a function that takes a nonreference, nonconst parameter passing either a const or nonconst argument:int gcd (int v1, int v2)
{ ... }
int k = gcd (3, 6); //ok to use const to initialize nonconst parameter
If we make the parameter a const nonreference type, then the function cannot change its local copy of the argument. But we can still use const or nonconst as argument:
void fcn(const int i) //fcn can read but not write to i, we can pass nonconst to fcn
This is because initialization copies the value of the initializer, we can initialize a nonconst object from a const object, or vice versa.
Although the parameter is a const inside the function, the compiler otherwise treats the definition of fcn as if we had defined the parameter as a plain int:
void fcn(const int i) //fcn can read but not write to i
void fcn(int i) //error: redefines fcn(int)
This usage is to support compatibility with C, which makes no difference between functions taking const or nonconst parameters.
Limitation of coping arguments
1. When we want the function to be able to change the value of an argument
2. When we want to pass a large object as an argument. The time and space costs to copy the object are often too high
3. When there is no way to copy the object.
In these cases we can define the parameters as references or pointers.
7.2.2 Reference Parameters
Reference parameters refer directly to the objects to which they are bound rather than to copies of those objects. Each time the function is called, the reference parameter is created and bound to its corresponding argument:swap (int &i, int &j)
Using Reference Parameter to Return Additional Information
Another use of reference parameters is to return an additional result to the calling function (we need to have corresponding argument in the calling function).
Using (const) Reference to Avoid Copies
Reference parameters are useful when passing a large object to a function. For example, compare the length of two strings.
When the only reason to make a parameter a reference is to avoid copying the argument, the parameter should be const reference:
//compare the length of two strings
bool is Shorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
*For more info about const reference: P 59
const reference rules:
A nonconst reference may be attached only to an object of the same type as the reference itself.
A const reference may be bound to an object of a different but related type or to an rvalue. const reference can bind to nonconst variable.
References to const Are More Flexible
int incr(int &val)
{ return ++val; }
int main()
{ short v1 = 0;
const int v2 = 42;
int v3 = incr(v1); //error: v1 is not an int
v3 = incr(v2); //error: v2 is const
v3 = incr(0); //error: literals are not lvalues
v3 = incr(v1+v2); //error: addition doesn't yield an lvalue
int v4 = incr(v3); //ok
}
So we should use const reference when we don't need to modify the reference parameter.
Pass a Reference to a Pointer
//swap values of two pointers to int
void strswap(int *&v1, int *&v2)
{
int *tmp = v2;
v2 = v1;
v1 = tmp;
}
7.2.3 vector and Other Container Parameters
If we want to pass vector and other container parameters, we will use iterator://we should use const iterator when we don't need to modify the value
void print (vector<int>::const_iterator beg, vector<int>::const_iterator end)
{
while (beg != end)
{
cout << *beg++;
if (beg != end - 1) cout << " "; //no space after the last element
}
cout << endl;
}
7.2.4 Array Parameters
Array has two properties that will affect how to define/use function operate on arrays:
1. We cannot copy an array
2. When we use the name of an array, it is automatically converted to a pointer to the first element
When a parameter uses array syntax, it is treated as a pointer:
// three equivalent definitions
void printValues(int*)
void printValues(int [])
void printValues(int [10]) //the dimension is ignored
We should use the first way to define the parameter, so we know we are actually manipulate a pointer.The dimension is not passed to the function, so the function does not know the size of the array. (we may want to have another parameter to define the array size!)
Array Arguments
Most commonly, arrays are passed as plain, nonreference pointers. And we should use const pointer when we don't need to change the elements:
void f(const int*)
Passing an Array by Reference
If the parameter is a reference to the array, then the compiler does not convert an array argument into a pointer. In this case, the array size is part of the parameter and argument types. (We must use parentheses in the definition, as & has low precedence)
void printValues(int (&arr)[10]) //we must have the parentheses, as & has lower precedence
int main()
{
int i = 0, j[2] = {0,1};
int k[10] = {10, 10);
printValues(i); //error: argument is not an array of 10 ints
printValues(j); //error
printValues(k); //ok
return 0;
}
When passing array by reference:
+ We don't need to worry about the size in the function body, as we know the parameter must be an array of 10 elements.
- The function can only be called for arrays of exactly 10 ints.
Passing a Multidimensioned Array
//first parameter is an array whose elements are arrays of 10 ints
void printValue(int (*matrix)[10], int rowSize) //The inner parentheses is necessary
void printValue(int matrix[][10], int rowSize) //This is same as the above definition
7.2.5 Managing Arrays Passed to Functions
There are three common programming techniques to ensure that a function stays within the bounds of its array arguments.
1. Maker
We can use a maker at the end of the array to indicate the end of the array. E.G. C-style character strings.
2. Using the Standard Library Conventions
The second approach is to pass pointers to the first and one past the last element in the array. (This is more like iterator)
3. Explicitly Passing a Size Parameter
We can also define a second parameter that indicates the size of the array.
7.2.6 main: Handling Command-Line Options P244
7.2.7 Ellipsis Parameters P244
7.3 The return Statement
A return statement terminates the function that is currently executing and returns control to the function that called the now-terminated function. There are two return forms;
return;
return expression;
7.3.1 Functions with No Return Value
A return with no value may be used only in a function that has a return type of void. Because we may use the return value to assign a variable!
Functions that return void are not required to contain a return statement. In void function, an implicit return takes place after the function's final statement.
Typically, a void function uses a return to cause premature termination of the function:
void swap (int &v1, int &v2)
{
//if values are the same, terminate the function
if (v1 == v2)
return;
int tmp = v1;
v1 = v2;
v2 = tmp;
//no explicit return necessary
}
When use function with no return value, we can call it directly. (if function has return value, then we can use the return value in expression!)int main()
{
swap(v1, v2);
}
Function with void return type ordinarily do not use the second return form. But we can use the second form to return the result of calling another function that returns void:
void do_swap(int &v1, int &v2)
{
int tmp = v2;
v2 = v1;
v1 = tmp;
}
void swap(int &v1, int &v2)
{
if (v1 == v2)
return;
return do_swap(v1, v2); //here we can also ignore the return keyword, call do_swap directly
}
7.3.2 Functions that Return a Value
Every return in a function with a return type other than void must return a value. The value returned must have the same type as the function return type, or must have a type that can be implicitly converted to that type. E.G. P246
Return from main
The main function is allowed to terminate without a return. If control reaches the end of main and there is no return, then the compiler implicitly inserts a return of 0.
Normally, return value of 0 indicates success; most other values indicate failure. The value has machine-dependent value. But we can use cstdlib header to indicate success or failure which is machine-independent:
#include <cstdlib>
int main() //do have return value
{
if (some_failure)
return EXIT_FAILURE;
else
return EXIT_SUCCESS;
}
Returning a Nonreference Type
The value returned by a function is used to initialize a temporary object created at the point which the call was made. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression. E.G. P248
Never Return a Reference/Pointer to a Local Object
//return value refers to memory that is no longer available to the program
const string &mapip(const string& s)
{
string ret = s;
//transform ret in some way
return ret; //wrong, ret is already freed
}
Reference Returns Are Lvalues
char &get_val ( string &str, string::size_type ix)
{ return str[ix]; }
int main()
{
string s = "abc";
get_val(s, 0) = 'A'; //s become "Abc"
}
How the above function works:
Here the parameter string &str will generate a local reference (reference is like pointer: wikihttp://en.wikipedia.org/wiki/Reference_%28C%2B%2B%29). The reference stores an address that points to the first element of target string. When we returnstr[ix], there will be something link temporary variable used to store this address and return it to the calling function. So the calling function can still get the address evenstr is already freed when return to the calling function. And since the address is pointing to s, which is not freed. So the address is still valid. The above code is same as Exercise 7.19
We should use const char &get_val if we don't want the reference return to be modified.
7.3.3 Recursion
A function that calls itself, either directly or indirectly, is a recursive function.
A recursive function must always define a stopping condition; otherwise, the function will recursive forever.
The main function may not call itself.
//calculate factorial
int factorial (int val)
{
if (val > 1)
return factorial(val - 1) * val;
return 1;
}