Normally, arguments are passed by value. This means the function's parameters hold copies of its arguments. Hence, a function can modify its parameters without modifying the calling function's copies. Usually this is a good thing, but in some situations the caller wants its data modified. For example, the following function swaps the arguments stored in its parameters:
void swap(int x, int
y)
{
int temp = x;
x = y;
y = temp;
} // this fails
This is the way swap() might be called:
int main()
{
int a(42), b(35);
cout << "a = " <<
a << '\n'; // prints "a = 42"
cout << "b = " <<
b << '\n'; // prints "b = 35"
swap(a,
b);
cout << "a = " <<
a << '\n'; // prints "a = 42"
cout << "b = " <<
b << '\n'; // prints "b = 35"
return 0;
}
Unfortunately, swap(a, b) fails to swap a and b. A frame is created with x = 42, temp = 42, and y = 35. x and y are swapped:
then the frame is destroyed, leaving a and b unchanged.
The traditional solution to this problem is to use pointers. Instead of passing the values of a and b to swap, their addresses are passed:
void swap(int* x,
int* y)
{
int temp = *x;
*x = *y;
*y = temp;
}
Of course the client must pass addresses, not integers, to swap():
int main()
{
int a = 42, b = 35;
// etc.
cout << "a = " <<
a << '\n';
cout << "b = " <<
b << '\n';
swap(&a,
&b);
cout << "a = " <<
a << '\n';
cout << "b = " <<
b << '\n';
// etc.
return 0;
}
Here's what the frames look like:
I have two objections to the implementation of swap(). First, all the pointer dereferencing is confusing and scary. I suppose I could get used to it, but what about my poor client. He too must know enough about pointers to remember that it's &a and &b, not a and b that must be passed to swap().
C++ offers another solution. A reference is basically a constant pointer, only C++ automatically dereferences them and automatically converts variables assigned to them into addresses. For example, assume x is an integer variable:
int x = 42;
An integer reference is declared like this:
int& y = x;
This is translated to:
int const *y = &x;
The assignment:
x = x + y;
is translated to:
x = x + *y;
With references we can re implement swap() as follows:
void swap(int& x,
int& y)
{
int temp = x;
x = y;
y = temp;
}
The client doesn't need to compute addresses:
int main()
{
int a = 42, b = 35;
// etc.
cout << "a = " <<
a << '\n';
cout << "b = " <<
b << '\n';
swap(a,
b);
cout << "a = " <<
a << '\n';
cout << "b = " <<
b << '\n';
// etc.
return 0;
}
Passing references is more efficient than passing values because only addresses are copied, but passing references is risky because programmers aren't protected from inadvertently modifying the client's variables. We can have the best of both worlds by declaring parameters to be constant. For example, assume members of a user defined type are large:
struct Shape { ... };
A function that expects a shape as an input can declare a constant reference parameter:
double surfaceArea(const shape& s) { ... }
Any attempt to modify s in the block of the function generates a compiler error.
Functions that return references can be called from the left side of an assignment operator. For example:
char& getChar(char[] str, int pos)
{
if (pos < 0 || strlen(str) <=
pos)
{
cerr << "Position out of
range\n"
exit(1);
}
return str[pos];
}
When getChar() is not called on the left of an assignment operator, C++ performs automatic dereferencing, as usual:
char animal[] = "Cat";
cout << getChar(animal, 0); // prints 'C'
Calling getChar() on the left of an assignment operator suppresses the dereferencing operation:
getChar(animal, 0) = 'B'; // turns Cat into Bat
cout << getChar(animal, 0); // prints 'B'
A great feature of C++ is that function names can be overloaded or shared. The compiler will determine which function to call by matching the argument types with the parameter types.
For example, assume three geometric types are declared[1]:
struct Triangle { ... };
struct Rectangle { ... };
struct Circle { ... };
We would like to write functions that compute the areas of these shapes. In C++ we can give all of them the same name:
double area(Triangle x) { ... }
double area(Circle x) { ... }
double area(Rectangle x) { ... }
These three functions are called variants of area. When the compiler sees a call to area():
Circle c;
Triangle t;
Rectangle r;
cout << area(t); // calls triangle variant of area
cout << area(r); // calls rectangle variant of area
cout << area(c); // calls circle variant of area
The C++ compiler uses the types of the arguments: t, r, and c, to decide which variant to call.
Compare this with C. The three area functions must have distinct names:
double triangle_area(Triangle x) { ... }
double circle_area(Circle x) { ... }
double rectangle_area(Rectangle x) { ... }
The poor user must look up the name each time he wants to compute the area of a triangle. Was it triangle_area, area_triangle, triangleArea, etc.?
C++ allows default arguments to be specified when a function is defined. For example:
void init(int nums[], int size = 3, int fill = 0)
{
for(int i = 0; i <
size; i++)
nums[i] = fill;
}
Initializes an array, nums, of length size with a fill value. Here are some sample calls:
int vals[5] = {100,
200, 300, 400, 500};
init(vals, 5, 20); // vals = {20, 20,
20, 20, 20}
init(vals, 2); // vals = {0, 0, 20,
20, 20}
init(vals); // vals = {0, 0, 0,
20, 20}
Note that in the second call, the fill argument was assumed to be 0. In the third call the fill was assumed to be 0 and the size was assumed to be 3.
Also note that arrays are not passed by value, so we are not initializing a private copy of vals.
Pushing and popping stack frames can be inefficient. It almost seems wasteful for short functions. C++ solves this problem with inline functions. Calls to an inline function use the substitution model instead of the stack model. To do this, just place the inline specifier in front of the function definition:
inline double square(double x) { return x * x; }
inline double cube(double x) { return x * x * x; }
Warning: only declare a function to be inline if its block is simple. Functions containing loops in their blocks should not be declared inline. Also, an inline function must be defined in every file where it is called. For this reason it is common to place inline functions in header files that can be included in files where the function is called.
A powerful way to define a family of functions instead of a single function is a template. For example, the swap function only swaps integer variables. We'll need to define another swap function to swap doubles or strings or employee records. We can define all of these swap functions in one definition by declaring the type of the parameters to also be a parameter!
template <typename
Data>
void swap(Data& x, Data& y)
{
Data temp = x;
x = y;
y = temp;
}
(The "class" keyword can be used instead of "typename".) When the C++ compiler sees a call to swap, it determines the type of the arguments and automatically generates definitions of the needed swap variants. For example, when C++ compiles:
int main()
{
int a = 42, b = 35;
swap(a, b); // ok
string u = "hello", v =
"goodbye";
swap(u, v); // ok
return 0;
}
It automatically generates the definitions:
void swap(int& x, int& y)
{
int
temp = x;
x = y;
y = temp;
}
and
void swap(string&
x, string& y)
{
string
temp = x;
x = y;
y = temp;
}
Because of overloading, the functions can share the swap name.
C and C++ allow programmers to declare macros:
#define min(x, y)
x<y?x:y
The C/C++ pre-processor replaces all macro calls using normal-order evaluation. For example, the pre-processor replaces the line:
min(2 + 3, 2 * 2)
with the line:
2 + 3?2 + 3:2 * 2