Tải bản đầy đủ (.pdf) (139 trang)

Ivor Horton’s BeginningVisual C++ 2008 phần 3 ppt

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.43 MB, 139 trang )

function, but this doesn’t have to be so. You can use longer more expressive parameter names in the func-
tion prototype to aid understanding of the significance of the parameters and then use shorter parameter
names in the function definition where the longer names would make the code in the body of the func-
tion less readable.
If you like, you can even omit the names altogether in the prototype, and just write:
double power(double, int);
This provides enough information for the compiler to do its job; however, it’s better practice to use some
meaningful name in a prototype because it aids readability and, in some cases, makes all the difference
between clear code and confusing code. If you have a function with two parameters of the same type
(suppose our index was also of type
double in the function power(), for example), the use of suitable
names indicates which parameter appears first and which second.
Try It Out Using a Function
You can see how all this goes together in an example exercising the power() function.
// Ex5_01.cpp
// Declaring, defining, and using a function
#include <iostream>
using std::cout;
using std::endl;
double power(double x, int n); // Function prototype
int main(void)
{
int index = 3; // Raise to this power
double x = 3.0; // Different x from that in function power
double y = 0.0;
y = power(5.0, 3); // Passing constants as arguments
cout << endl
<< “5.0 cubed = “ << y;
cout << endl
<< “3.0 cubed = “
<< power(3.0, index); // Outputting return value


x = power(x, power(2.0, 2.0)); // Using a function as an argument
cout << endl // with auto conversion of 2nd parameter
<< “x = “ << x;
cout << endl;
return 0;
}
// Function to compute positive integral powers of a double value
// First argument is value, second argument is power index
double power(double x, int n)
244
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 244
{ // Function body starts here
double result = 1.0; // Result stored here
for(int i = 1; i <= n; i++)
result *= x;
return result;
} // and ends here
This program shows some of the ways in which you can use the function power(), specifying the argu-
ments to the function in a variety of ways. If you run this example, you get the following output:
5.0 cubed = 125
3.0 cubed = 27
x = 81
How It Works
After the usual #include statement for input/output and the using declarations, you have the proto-
type for the function
power(). If you were to delete this and try recompiling the program, the compiler
wouldn’t be able to process the calls to the function in
main() and would instead generate a whole series
of error messages:

error C3861: ‘power’: identifier not found
and the error message:
error C2365: ‘power’ : redefinition; previous definition was ‘formerly unknown
identifier’
In a change from previous examples, I’ve used the new keyword void in the function main() where the
parameter list would usually appear to indicate that no parameters are to be supplied. Previously, I left
the parentheses enclosing the parameter list empty, which is also interpreted in C++ as indicating that
there are no parameters; but it’s better to specify the fact by using the keyword
void. As you saw, the
keyword
void can also be used as the return type for a function to indicate that no value is returned. If
you specify the return type of a function as
void, you must not place a value in any return statement
within the function; otherwise, you get an error message from the compiler.
You gathered from some of the previous examples that using a function is very simple. To use the func-
tion
power() to calculate 5.0
3
and store the result in a variable y in our example, you have the following
statement:
y = power(5.0, 3);
The values 5.0 and 3 here are the arguments to the function. They happen to be constants, but you can use
any expression as an argument, as long as a value of the correct type is ultimately produced. The arguments
to the
power() function substitute for the parameters x and n, which were used in the definition of the func-
tion. The computation is performed using these values and then a copy of the result, 125, is returned to the
calling function,
main(), which is then stored in y. You can think of the function as having this value in the
statement or expression in which it appears. You then output the value of
y:

cout << endl
<< “5.0 cubed = “ << y;
245
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 245
The next call of the function is used within the output statement:
cout << endl
<< “3.0 cubed = “
<< power(3.0, index); // Outputting return value
Here, the value returned by the function is transferred directly to the output stream. Because you haven’t
stored the returned value anywhere, it is otherwise unavailable to you. The first argument in the call of
the function here is a constant; the second argument is a variable.
The function
power() is used next in this statement:
x = power(x, power(2.0, 2.0)); // Using a function as an argument
Here the power() function is called twice. The first call to the function is the rightmost in the expression,
and the result supplies the value for the second argument to the leftmost call. Although the arguments
in the sub-expression
power(2.0, 2.0) are both specified as the double literal 2.0, the function is actu-
ally called with the first argument as 2.0 and the second argument as the integer literal, 2. The compiler
converts the
double value specified for the second argument to type int because it knows from the
function prototype (shown again below) that the type of the second parameter has been specified as
int.
double power(double x, int n); // Function prototype
The double result 4.0 is returned by the first call to the power() function, and after conversion to type
int, the value 4 is passed as the second argument in the next call of the function, with x as the first argu-
ment. Because
x has the value 3.0, the value of 3.0
4

is computed and the result, 81.0, stored in x. This
sequence of events is illustrated in Figure 5-2.
Figure 5-2
x = power( x , power( 2.0 , 2.0 ));
power( 2.0 , 2 )
initial value
3.0
1
result stored
back in x
Converted
to type int
Converted
to type int
4.0 (type double)
81.0 (type double)
power( 3.0 , 4 )
2
3
4
5
246
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 246
This statement involves two implicit conversions from type double to type int that were inserted by
the compiler. There’s a possible loss of data when converting from type
double to type int so the com-
piler issues warning message when this occurs, even though the compiler has itself inserted this conver-
sion. Generally relying on automatic conversions where there is potential for data loss is a dangerous
programming practice, and it is not at all obvious from the code that this conversion is intended. It is far

better to be explicit in your code by using the
static_cast operator when necessary. The statement in
the example is much better written as:
x = power(x, static_cast<int>(power(2.0, 2)));
Coding the statement like this avoids both the compiler warning messages that the original version caused.
Using a static cast does not remove the possibility of losing data in the conversion of data from one type
to another. Because you specified it though, it is clear that this is what you intended, recognizing that data
loss might occur.
Passing Arguments to a Function
It’s very important to understand how arguments are passed to a function, as it affects how you write
functions and how they ultimately operate. There are also a number of pitfalls to be avoided, so we’ll
look at the mechanism for this quite closely.
The arguments you specify when a function is called should usually correspond in type and sequence
to the parameters appearing in the definition of the function. As you saw in the last example, if the
type of an argument specified in a function call doesn’t correspond with the type of parameter in the
function definition, (where possible) it converts to the required type, obeying the same rules as those
for casting operands that were discussed in Chapter 2. If this proves not to be possible, you get an
error message from the compiler; however, even if the conversion is possible and the code compiles,
it could well result in the loss of data (for example from type
long to type short) and should there-
fore be avoided.
There are two mechanisms used generally in C++ to pass arguments to functions. The first mechanism
applies when you specify the parameters in the function definition as ordinary variables (not references).
This is called the pass-by-value method of transferring data to a function so let’s look into that first of all.
The Pass-by-value Mechanism
With this mechanism, the variables or constants that you specify as arguments are not passed to a function
at all. Instead, copies of the arguments are created and these copies are used as the values to be transferred.
Figure 5-3 shows this in a diagram using the example of our
power() function.
Each time you call the function

power(), the compiler arranges for copies of the arguments that you
specify to be stored in a temporary location in memory. During execution of the functions, all references
to the function parameters are mapped to these temporary copies of the arguments.
247
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 247
Figure 5-3
Try It Out Passing-by-value
One consequence of the pass-by-value mechanism is that a function can’t directly modify the arguments
passed. You can demonstrate this by deliberately trying to do so in an example:
// Ex5_02.cpp
// A futile attempt to modify caller arguments
#include <iostream>
using std::cout;
using std::endl;
int incr10(int num); // Function prototype
int main(void)
{
int num = 3;
cout << endl
<< “incr10(num) = “ << incr10(num)
<< endl
int index = 2;
double value = 10.0;
double result = power(value, index);
Temporary copies of the arguments
are made for use in the function
The original arguments are not
accessible here, only the copies.
double power ( double x , int n )

{



}
copy of value copy of index
index 2
10.0
value
2
10.0
248
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 248
<< “num = “ << num;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(int num) // Using the same name might help
{
num += 10; // Increment the caller argument – hopefully
return num; // Return the incremented value
}
Of course, this program is doomed to failure. If you run it, you get this output:
incr10(num) = 13
num = 3
How It Works
The output confirms that the original value of num remains untouched. The incrementing occurred on
the copy of

num that was generated and was eventually discarded on exiting from the function.
Clearly, the pass-by-value mechanism provides you with a high degree of protection from having your
caller arguments mauled by a rogue function, but it is conceivable that you might actually want to arrange
to modify caller arguments. Of course, there is a way to do this. Didn’t you just know that pointers would
turn out to be incredibly useful?
Pointers as Arguments to a Function
When you use a pointer as an argument, the pass-by-value mechanism still operates as before; however,
a pointer is an address of another variable, and if you take a copy of this address, the copy still points to
the same variable. This is how specifying a pointer as a parameter enables your function to get at a caller
argument.
Try It Out Pass-by-pointer
You can change the last example to use a pointer to demonstrate the effect:
// Ex5_03.cpp
// A successful attempt to modify caller arguments
#include <iostream>
using std::cout;
using std::endl;
int incr10(int* num); // Function prototype
int main(void)
{
249
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 249
int num = 3;
int* pnum = &num; // Pointer to num
cout << endl
<< “Address passed = “ << pnum;
int result = incr10(pnum);
cout << endl
<< “incr10(pnum) = “ << result;

cout << endl
<< “num = “ << num;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(int* num) // Function with pointer argument
{
cout << endl
<< “Address received = “ << num;
*num += 10; // Increment the caller argument
// - confidently
return *num; // Return the incremented value
}
The output from this example is:
Address passed = 0012FF6C
Address received = 0012FF6C
incr10(pnum) = 13
num = 13
The address values produced by your computer may be different from those shown above, but the two
values should be identical to each other.
How It Works
In this example, the principal alterations from the previous version relate to passing a pointer, pnum, in
place of the original variable,
num. The prototype for the function now has the parameter type specified
as a pointer to
int, and the main() function has the pointer pnum declared and initialized with the address
of
num. The function main(), and the function incr10(), output the address sent and the address received
respectively, to verify that the same address is indeed being used in both places. Because the incr10() func-

tion is writing to
cout, you now call it before the output statement and store the return value in result:
int result = incr10(pnum);
cout << endl
<< “incr10(pnum) = “ << result;
250
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 250
This ensures proper sequencing of the output. The output shows that this time the variable num has been
incremented and has a value that’s now identical to that returned by the function.
In the rewritten version of the function
incr10(), both the statement incrementing the value passed to
the function and the
return statement now de-reference the pointer to use the value stored.
Passing Arrays to a Function
You can also pass an array to a function, but in this case the array is not copied, even though a pass-by-
value method of passing arguments still applies. The array name is converted to a pointer, and a copy of
the pointer to the beginning of the array is passed by value to the function. This is quite advantageous
because copying large arrays is very time consuming. As you may have worked out, however, elements
of the array may be changed within a function and thus an array is the only type that cannot be passed
by value.
Try It Out Passing Arrays
You can illustrate the ins and outs of this by writing a function to compute the average of a number of
values passed to a function in an array.
// Ex5_04.cpp
// Passing an array to a function
#include <iostream>
using std::cout;
using std::endl;
double average(double array[], int count); //Function prototype

int main(void)
{
double values[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
cout << endl
<< “Average = “
<< average(values, (sizeof values)/(sizeof values[0]));
cout << endl;
return 0;
}
// Function to compute an average
double average(double array[], int count)
{
double sum = 0.0; // Accumulate total in here
for(int i = 0; i < count; i++)
sum += array[i]; // Sum array elements
return sum/count; // Return average
}
251
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 251
The program produces the following output:
Average = 5.5
How It Works
The average()function is designed to work with an array of any length. As you can see from the proto-
type, it accepts two arguments: the array and a count of the number of elements. Because you want it to
work with arrays of arbitrary length, the array parameter appears without a dimension specified.
The function is called in
main() in this statement,
cout << endl
<< “Average = “

<< average(values, (sizeof values)/(sizeof values[0]));
The function is called with the first argument as the array name, values, and the second argument as an
expression that evaluates to the number of elements in the array.
You’ll recall this expression, using the operator
sizeof, from when you looked at arrays in Chapter 4.
Within the body of the function, the computation is expressed in the way you would expect. There’s
no significant difference between this and the way you would write the same computation if you
implemented it directly in
main().
The output confirms that everything works as we anticipated.
Try It Out Using Pointer Notation When Passing Arrays
You haven’t exhausted all the possibilities here. As you determined at the outset, the array name is passed
as a pointer — to be precise, as a copy of a pointer — so within the function you are not obliged to work
with the data as an array at all. You could modify the function in the example to work with pointer nota-
tion throughout, in spite of the fact that you are using an array.
// Ex5_05.cpp
// Handling an array in a function as a pointer
#include <iostream>
using std::cout;
using std::endl;
double average(double* array, int count); //Function prototype
int main(void)
{
double values[] = { 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0 };
cout << endl
<< “Average = “
<< average(values, (sizeof values)/(sizeof values[0]));
252
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 252

cout << endl;
return 0;
}
// Function to compute an average
double average(double* array, int count)
{
double sum = 0.0; // Accumulate total in here
for(int i = 0; i < count; i++)
sum += *array++; // Sum array elements
return sum/count; // Return average
}
The output is exactly the same as in the previous example.
How It Works
As you can see, the program needed very few changes to make it work with the array as a pointer. The
prototype and the function header have been changed, although neither change is absolutely necessary.
If you change both back to the original version with the first parameter specified as a
double array and
leave the function body written in terms of a pointer, it works just as well. The most interesting aspect of
this version is the body of the
for loop statement:
sum += *array++; // Sum array elements
Here you apparently break the rule about not being able to modify an address specified as an array name
because you are incrementing the address stored in
array. In fact, you aren’t breaking the rule at all.
Remember that the pass-by-value mechanism makes a copy of the original array address and passes that
to the function, so you are just modifying the copy here — the original array address is quite unaffected.
As a result, whenever you pass a one-dimensional array to a function, you are free to treat the value passed
as a pointer in every sense, and change the address in any way that you want.
Passing Multidimensional Arrays to a Function
Passing a multidimensional array to a function is quite straightforward. The following statement

declares a two dimensional array,
beans:
double beans[2][4];
You could then write the prototype of a hypothetical function, yield(), like this:
double yield(double beans[2][4]);
You may be wondering how the compiler can know that this is defining an array of the dimensions
shown as an argument, and not a single array element. The answer is simple — you can’t write a
single array element as a parameter in a function definition or prototype, although you can pass one
as an argument when you call a function. For a parameter accepting a single element of an array as
an argument, the parameter would have just a variable name. The array context doesn’t apply.
253
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 253
When you are defining a multi-dimensional array as a parameter, you can also omit the first dimension
value. Of course, the function needs some way of knowing the extent of the first dimension. For example,
you could write this:
double yield(double beans[][4], int index);
Here, the second parameter would provide the necessary information about the first dimension. The
function can operate with a two-dimensional array with the value for the first dimension specified by
the second argument to the function and with the second dimension fixed at 4.
Try It Out Passing Multidimensional Arrays
You define such a function in the following example:
// Ex5_06.cpp
// Passing a two-dimensional array to a function
#include <iostream>
using std::cout;
using std::endl;
double yield(double array[][4], int n);
int main(void)
{

double beans[3][4] = { { 1.0, 2.0, 3.0, 4.0 },
{ 5.0, 6.0, 7.0, 8.0 },
{ 9.0, 10.0, 11.0, 12.0 } };
cout << endl
<< “Yield = “ << yield(beans, sizeof beans/sizeof beans[0]);
cout << endl;
return 0;
}
// Function to compute total yield
double yield(double beans[][4], int count)
{
double sum = 0.0;
for(int i = 0; i < count; i++) // Loop through number of rows
for(int j = 0; j < 4; j++) // Loop through elements in a row
sum += beans[i][j];
return sum;
}
The output from this example is:
Yield = 78
How It Works
I have used different names for the parameters in the function header from those in the prototype, just
to remind you that this is possible — but in this case, it doesn’t really improve the program at all. The
first parameter is defined as an array of an arbitrary number of rows, each row having four elements.
254
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 254
You actually call the function using the array beans with three rows. The second argument is specified
by dividing the total size of the array in bytes by the size of the first row. This evaluates to the number
of rows in the array.
The computation in the function is simply a nested

for loop with the inner loop summing elements of a
single row and the outer loop repeating this for each row.
Using a pointer in a function rather than a multidimensional array as an argument doesn’t really apply
particularly well in this example. When the array is passed, it passes an address value which points to
an array of four elements (a row). This doesn’t lend itself to an easy pointer operation within the func-
tion. You would need to modify the statement in the nested
for loop to the following:
sum += *(*(beans + i) + j);
So the computation is probably clearer in array notation.
References as Arguments to a Function
We now come to the second of the two mechanisms for passing arguments to a function. Specifying a
parameter to a function as a reference changes the method of passing data for that parameter. The method
used is not pass-by-value, where an argument is copied before being transferred to the function, but pass-
by-reference where the parameter acts as an alias for the argument passed. This eliminates any copying
and allows the function to access the caller argument directly. It also means that the de-referencing, which
is required when passing and using a pointer to a value, is also unnecessary.
Try It Out Pass-by-reference
Let’s go back to a revised version of a very simple example, Ex5_03.cpp, to see how it would work
using reference parameters:
// Ex5_07.cpp
// Using a reference to modify caller arguments
#include <iostream>
using std::cout;
using std::endl;
int incr10(int& num); // Function prototype
int main(void)
{
int num = 3;
int value = 6;
int result = incr10(num);

cout << endl
<< “incr10(num) = “ << result;
cout << endl
<< “num = “ << num;
255
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 255
result = incr10(value);
cout << endl
<< “incr10(value) = “ << result;
cout << endl
<< “value = “ << value;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(int& num) // Function with reference argument
{
cout << endl
<< “Value received = “ << num;
num += 10; // Increment the caller argument
// - confidently
return num; // Return the incremented value
}
This program produces the output:
Value received = 3
incr10(num) = 13
num = 13
Value received = 6
incr10(value) = 16

value = 16
How It Works
You should find the way this works quite remarkable. This is essentially the same as Ex5_03.cpp, except
that the function uses a reference as a parameter. The prototype has been changed to reflect this. When the
function is called, the argument is specified just as though it was a pass-by-value operation, so it’s used in
the same way as the earlier version. The argument value isn’t passed to the function. The function param-
eter is initialized with the address of the argument, so whenever the parameter
num is used in the function,
it accesses the caller argument directly.
Just to reassure you that there’s nothing fishy about the use of the identifier
num in main() as well as in
the function, the function is called a second time with the variable
value as the argument. At first sight,
this may give you the impression that it contradicts what I said was a basic property of a reference —
that after declared and initialized, it couldn’t be reassigned to another variable. The reason it isn’t con-
tradictory is that a reference as a function parameter is created and initialized each time the function is
called and is destroyed when the function ends, so you get a completely new reference created each time
you use the function.
Within the function, the value received from the calling program is displayed onscreen. Although the
statement is essentially the same as the one used to output the address stored in a pointer, because
num
is now a reference, you obtain the data value rather than the address.
256
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 256
This clearly demonstrates the difference between a reference and a pointer. A reference is an alias for
another variable, and therefore can be used as an alternative way of referring to it. It is equivalent to
using the original variable name.
The output shows that the function
incr10() is directly modifying the variable passed as a caller argument.

You will find that if you try to use a numeric value, such as 20, as an argument to
incr10(), the compiler
outputs an error message. This is because the compiler recognizes that a reference parameter can be modi-
fied within a function, and the last thing you want is to have your constants changing value now and
again. This would introduce a kind of excitement into your programs that you could probably do without.
This security is all very well, but if the function didn’t modify the value, you wouldn’t want the compiler to
create all these error messages every time you pass a reference argument that was a constant. Surely there
ought to be some way to accommodate this? As Ollie would have said, ‘There most certainly is, Stanley!’
Use of the const Modifier
You can apply the const modifier to a parameter to a function to tell the compiler that you don’t intend
to modify it in any way. This causes the compiler to check that your code indeed does not modify the
argument, and there are no error messages when you use a constant argument.
Try It Out Passing a const
You can modify the previous program to show how the const modifier changes the situation.
// Ex5_08.cpp
// Using a reference to modify caller arguments
#include <iostream>
using std::cout;
using std::endl;
int incr10(const int& num); // Function prototype
int main(void)
{
const int num = 3; // Declared const to test for temporary creation
int value = 6;
int result = incr10(num);
cout << endl
<< “incr10(num) = “ << result;
cout << endl
<< “num = “ << num;
result = incr10(value);

cout << endl
257
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 257
<< “incr10(value) = “ << result;
cout << endl
<< “value = “ << value;
cout << endl;
return 0;
}
// Function to increment a variable by 10
int incr10(const int& num) // Function with const reference argument
{
cout << endl
<< “Value received = “ << num;
// num += 10; // this statement would now be illegal
return num+10; // Return the incremented value
}
The output when you execute this is:
Value received = 3
incr10(num) = 13
num = 3
Value received = 6
incr10(value) = 16
value = 6
How It Works
You declare the variable num in main() as const to show that when the parameter to the function
incr10() is declared as const, you no longer get a compiler message when passing a const object.
It has also been necessary to comment out the statement that increments
num in the function incr10(). If

you uncomment this line, you’ll find the program no longer compiles because the compiler won’t allow
num to appear on the left side of an assignment. When you specified num as const in the function header
and prototype, you promised not to modify it, so the compiler checks that you kept your word.
Everything works as before, except that the variables in
main() are no longer changed in the function.
By using reference arguments, you now have the best of both worlds. On one hand, you can write a
function that can access caller arguments directly, and avoid the copying that is implicit in the pass-by-
value mechanism. On the other hand, where you don’t intend to modify an argument, you can get all
the protection against accidental modification you need by using a
const modifier with a reference.
Arguments to main()
You can define main() with no parameters (or better, with the parameter list as void) or you can specify
a parameter list that allows the
main() function to obtain values from the command line from the execute
command for the program. Values passed from the command line as arguments to
main() are always
258
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 258
interpreted as strings. If you want to get data into main() from the command line, you must define it
like this:
int main(int argc, char* argv[])
{
// Code for main()…
}
The first parameter is the count of the number of strings found on the command line including the program
name, and the second parameter is an array that contains pointers to these strings plus an additional ele-
ment that is null. Thus
argc is always at least 1 because you at least must enter the name of the program.
The number of arguments received depends on what you enter on the command line to execute the pro-

gram. For example, suppose that you execute the
DoThat program with the command:
DoThat.exe
There is just the name of the .exe file for the program so argc is 1 and the argv array contains two ele-
ments —
argv[0] pointing to the string “DoThat.exe” and argv[1] that contains null.
Suppose you enter this on the command line:
DoThat or else “my friend” 999.9
Now argc is 5 and argv contains six elements, the last element being 0 and the first five pointing to the
strings:
“DoThat” “or” “else” “my friend” “999.9”
You can see from this that if you want to have a string that includes spaces received as a single string
you must enclose it between double quotes. You can also see that numerical values are read as strings
so if you want conversion to the numerical value that is up to you.
Let’s see it working.
Try It Out Receiving Command-Line Arguments
This program just lists the arguments it receives from the command line.
// Ex5_09.cpp
// Reading command line arguments
#include <iostream>
using std::cout;
using std::endl;
int main(int argc, char* argv[])
{
cout << endl << “argc = “ << argc << endl;
cout << “Command line arguments received are:” << endl;
for(int i = 0 ; i <argc ; i++)
cout << “argument “ << (i+1) << “: “ << argv[i] << endl;
return 0;
}

259
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 259
You have two choices when entering the command-line arguments. After you build the example in the
IDE, you can open a command window at the folder containing the
.exe file and then enter the program
name followed by the command-line arguments. Alternatively, you can specify the command-line argu-
ments in the IDE before you execute the program. Just open the project properties window by selecting
Project > Properties from the main menu and then extend the Configuration Properties tree
in the left pane by clicking the plus sign (+). Click the
Debugging folder to see where you can enter com-
mand line values in the right pane.
I enter the following in the command window with the current directory containing the
.exe file for the
program:
Ex5_09 trying multiple “argument values” 4.5 0.0
Here is the output from resulting from my input:
argc = 6
Command line arguments received are:
argument 1: Ex5_09
argument 2: trying
argument 3: multiple
argument 4: argument values
argument 5: 4.5
argument 6: 0.0
How It Works
The program first outputs the value of argc and then the values of each argument from the argv
array in the for loop. You can see from the output that the first argument value is the program name.
“argument values” is treated as a single argument because of the enclosing double quotes.
You could make use of the fact that the last element in

argv is null and code the output of the command-
line argument values like this:
int i = 0;
while(argv[i] != 0)
cout << “argument “ << (i+1) << “: “ << argv[i++] << endl;
The while loop ends when argv[argc] is reached because that element is null.
Accepting a Variable Number of Function Arguments
You can define a function so that it allows any number of arguments to be passed to it. You indicate that
a variable number of arguments can be supplied when a function is called by placing an ellipsis (which
is three periods,
) at the end of the parameter list in the function definition. For example:
int sumValues(int first, )
{
260
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 260
//Code for the function
}
There must be at least one ordinary parameter, but you can have more. The ellipsis must always be placed
at the end of the parameter list.
Obviously there is no information about the type or number of arguments in the variable list, so your code
must figure out what is passed to the function when it is called. The native C++ library defines
va_start,
va_arg, and va_end macros in the <cstdarg> header to help you do this. It’s easiest to show how these
are used with an example.
Try It Out Receiving a Variable Number of Arguments
This program uses a function that just sums the values of a variable number of arguments passed to it.
// Ex5_10.cpp
// Handling a variable number of arguments
#include <iostream>

#include <cstdarg>
using std::cout;
using std::endl;
int sum(int count, )
{
if(count <= 0)
return 0;
va_list arg_ptr; // Declare argument list pointer
va_start(arg_ptr, count); // Set arg_ptr to 1st optional argument
int sum =0;
for(int i = 0 ; i<count ; i++)
sum += va_arg(arg_ptr, int); // Add int value from arg_ptr and increment
va_end(arg_ptr); // Reset the pointer to null
return sum;
}
int main(int argc, char* argv[])
{
cout << sum(6, 2, 4, 6, 8, 10, 12) << endl;
cout << sum(9, 11, 22, 33, 44, 55, 66, 77, 66, 99) << endl;
}
This example produces the following output:
42
473
261
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 261
How It Works
The main() function calls the sum() function in the two output statements, in the first instance with
seven arguments and in the second with ten arguments. The first argument in each case specifies the
number of arguments that follow. It’s important not to forget this because if you omit the first count

argument, the result will be rubbish.
The
sum() function has a single normal parameter of type int that represents the count of the number
of arguments that follow. The ellipsis in the parameter list indicates an arbitrary number of arguments
can be passed. Basically you have two ways of determining how many arguments there are when the
function is called — you can require that the number of arguments is specified by a fixed parameter as
in the case of
sum(), or you can require that the last argument has a special marker value that you can
check for and recognize.
To start processing the variable argument list you declare a pointer of type
va_list:
va_list arg_ptr; // Declare argument list pointer
The va_list type is defined in the <cstdarg> header file and the pointer is used to point to each argu-
ment in turn.
The
va_start macro is used to initialize arg_ptr so that it points to the first argument in the list:
va_start(arg_ptr, count); // Set arg_ptr to 1st optional argument
The second argument to the macro is the name of the fixed parameter that precedes the ellipsis in the
parameter, and this is used by the macro to determine where the first variable argument is.
You retrieve the values of the arguments in the list in the
for loop:
int sum =0;
for(int i = 0 ; i<count ; i++)
sum += va_arg(arg_ptr, int); // Add int value from arg_ptr and increment
The va_arg macro returns the value of the argument at the location specified by arg_ptr and incre-
ments
arg_ptr to point to the next argument value. The second argument to the va_arg macro is the
argument type, and this determines the value that you get as well as how
arg_ptr increments so if this
is not correct you get chaos; the program probably executes, but the values you retrieve are rubbish and

arg_ptr is incremented incorrectly to access more rubbish.
When you are finished retrieving argument values, you reset
arg_ptr with the statement:
va_end(arg_ptr); // Reset the pointer to null
The va_end macro resets the pointer of type va_list that you pass as the argument to it to null. It’s a
good idea to always do this because after processing the arguments
arg_ptr points to a location that
does not contain valid data.
262
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 262
Returning Values from a Function
All the example functions that you have created have returned a single value. Is it possible to return any-
thing other than a single value? Well, not directly, but as I said earlier, the single value returned needn’t
be a numeric value; it could also be an address, which provides the key to returning any amount of data.
You simply use a pointer. Unfortunately, this also is where the pitfalls start, so you need to keep your
wits about you for the adventure ahead.
Returning a Pointer
Returning a pointer value is easy. A pointer value is just an address, so if you want to return the address
of some variable
value, you can just write the following:
return &value; // Returning an address
As long as the function header and function prototype indicate the return type appropriately, you have
no problem — or at least no apparent problem. Assuming that the variable
value is of type double, the
prototype of a function called
treble, which might contain the above return statement, could be as
follows:
double* treble(double data);
I have defined the parameter list arbitrarily here.

So let’s look at a function that returns a pointer. It’s only fair that I warn you in advance — this function
doesn’t work, but it is educational. Let’s assume that you need a function that returns a pointer to a mem-
ory location containing three times its argument value. Our first attempt the implement such a function
might look like this:
// Function to treble a value - mark 1
double* treble(double data)
{
double result = 0.0;
result = 3.0*data;
return &result;
}
Try It Out Returning a Bad Pointer
You could create a little test program to see what happens (remember that the treble function won’t
work as expected):
// Ex5_11.cpp
#include <iostream>
using std::cout;
using std::endl;
double* treble(double); // Function prototype
263
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 263
int main(void)
{
double num = 5.0; // Test value
double* ptr = 0; // Pointer to returned value
ptr = treble(num);
cout << endl
<< “Three times num = “ << 3.0*num;
cout << endl

<< “Result = “ << *ptr; // Display 3*num
cout << endl;
return 0;
}
// Function to treble a value - mark 1
double* treble(double data)
{
double result = 0.0;
result = 3.0*data;
return &result;
}
There’s a hint that everything is not as it should be because compiling this program results in a warning
from the compiler:
warning C4172: returning address of local variable or temporary
The output that I got from executing the program was:
Three times num = 15
Result = 4.10416e-230
How It Works (or Why It Doesn’t)
The function main() calls the function treble() and stores the address returned in the pointer ptr,
which should point to a value which is three times the argument,
num. You then display the result of
computing three times
num, followed by the value at the address returned from the function.
Clearly, the second line of output doesn’t reflect the correct value of 15, but where’s the error? Well, it’s
not exactly a secret because the compiler gives fair warning of the problem. The error arises because the
variable
result in the function treble() is created when the function begins execution, and is destroyed
on exiting from the function — so the memory that the pointer is pointing to no longer contains the origi-
nal variable value. The memory previously allocated to
result becomes available for other purposes,

and here it has evidently been used for something else.
264
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 264
A Cast Iron Rule for Returning Addresses
There is an absolutely cast iron rule for returning addresses:
Never ever return the address of a local automatic variable from a function.
You obviously can’t use a function that doesn’t work, so what can you do to rectify that? You could use a
reference parameter and modify the original variable, but that’s not what you set out to do. You are try-
ing to return a pointer to some useful data so that, ultimately, you can return more than a single item of
data. One answer lies in dynamic memory allocation (you saw this in action in the last chapter). With the
operator
new, you can create a new variable in the free store that continues to exist until it is eventually
destroyed by
delete — or until the program ends. With this approach, the function looks like this:
// Function to treble a value - mark 2
double* treble(double data)
{
double* result = new double(0.0);
*result = 3.0*data;
return result;
}
Rather than declaring result as of type double, you now declare it to be of type double* and store
in it the address returned by the operator
new. Because the result is a pointer, the rest of the function is
changed to reflect this, and the address contained in the result is finally returned to the calling program.
You could exercise this version by replacing the function in the last working example with this version.
You need to remember that with dynamic memory allocation from within a native C++ function such
as this, more memory is allocated each time the function is called. The onus is on the calling program to
delete the memory when it’s no longer required. It’s easy to forget to do this in practice, with the result

that the free store is gradually eaten up until, at some point, it is exhausted and the program fails. As
mentioned before, this sort of problem is referred to as a memory leak.
Here you can see how the function would be used. The only necessary change to the original code is to
use
delete to free the memory as soon as you have finished with the pointer returned by the treble()
function.
#include <iostream>
using std::cout;
using std::endl;
double* treble(double); // Function prototype
int main(void)
{
double num = 5.0; // Test value
double* ptr = 0; // Pointer to returned value
ptr = treble(num);
cout << endl
265
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 265
<< “Three times num = “ << 3.0*num;
cout << endl
<< “Result = “ << *ptr; // Display 3*num
delete ptr; // Don’t forget to free the memory
cout << endl;
return 0;
}
// Function to treble a value - mark 2
double* treble(double data)
{
double* result = new double(0.0);

*result = 3.0*data;
return result;
}
Returning a Reference
You can also return a reference from a function. This is just as fraught with potential errors as returning a
pointer, so you need to take care with this too. Because a reference has no existence in its own right (it’s
always an alias for something else), you must be sure that the object that it refers to still exists after the
function completes execution. It’s very easy to forget this when you use references in a function because
they appear to be just like ordinary variables.
References as return types are of primary significance in the context of object-oriented programming. As
you will see later in the book, they enable you to do things that would be impossible without them. (This
particularly applies to “operator overloading,” which I’ll come to in Chapter 8). The principal characteristic
of a reference-type return value is that it’s an lvalue. This means that you can use the result of a function
that returns a reference on the left side of an assignment statement.
Try It Out Returning a Reference
Next, look at one example that illustrates the use of reference return types, and also demonstrates how
a function can be used on the left of an assignment operation when it returns an lvalue. This example
assumes that you have an array containing a mixed set of values. Whenever you want to insert a new
value into the array, you want to replace the element with the lowest value.
// Ex5_12.cpp
// Returning a reference
#include <iostream>
#include <iomanip>
using std::cout;
using std::endl;
using std::setw;
double& lowest(double values[], int length); // Prototype of function
// returning a reference
int main(void)
{

266
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 266
double array[] = { 3.0, 10.0, 1.5, 15.0, 2.7, 23.0,
4.5, 12.0, 6.8, 13.5, 2.1, 14.0 };
int len = sizeof array/sizeof array[0]; // Initialize to number
// of elements
cout << endl;
for(int i = 0; i < len; i++)
cout << setw(6) << array[i];
lowest(array, len) = 6.9; // Change lowest to 6.9
lowest(array, len) = 7.9; // Change lowest to 7.9
cout << endl;
for(int i = 0; i < len; i++)
cout << setw(6) << array[i];
cout << endl;
return 0;
}
double& lowest(double a[], int len)
{
int j = 0; // Index of lowest element
for(int i = 1; i < len; i++)
if(a[j] > a[i]) // Test for a lower value
j = i; // if so update j
return a[j]; // Return reference to lowest
// element
}
The output from this example is:
3 10 1.5 15 2.7 23 4.5 12 6.8 13.5 2.1 14
3 10 6.9 15 2.7 23 4.5 12 6.8 13.5 7.9 14

How It Works
Let’s first take a look at how the function is implemented. The prototype for the function lowest() uses
double& as the specification of the return type, which is therefore of type ‘reference to double’. You write
a reference type return value in exactly the same way as you have already seen for variable declarations,
appending the
& to the data type. The function has two parameters specified — a one-dimensional array
of type
double and a parameter of type int that specifies the length of the array.
The body of the function has a straightforward
for loop to determine which element of the array passed
contains the lowest value. The index,
j, of the array element with the lowest value is arbitrarily set to 0
at the outset, and then modified within the loop if the current element,
a[i], is less than a[j]. Thus, on
exit from the loop,
j contains the index value corresponding to the array element with the lowest value.
The
return statement is as follows:
return a[j]; // Return reference to lowest element
In spite of the fact that this looks identical to the statement that would return a value, because the return
type was declared as a reference, this returns a reference to the array element
a[j] rather than the value
267
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 267
that the element contains. The address of a[j] is used to initialize the reference to be returned. This ref-
erence is created by the compiler because the return type was declared as a reference.
Don’t confuse returning
&a[j] with returning a reference. If you write &a[j] as the return value, you
are specifying the address of

a[j], which is a pointer. If you do this after having specified the return type
as a reference, you get an error message from the compiler. Specifically, you get this:
error C2440: ‘return’ : cannot convert from ‘double *__w64 ‘ to ‘double &’
The function main(), which exercises the lowest()function, is very simple. An array of type double is
declared and initialized with 12 arbitrary values, and an
int variable len is initialized to the length of
the array. The initial values in the array are output for comparison purposes.
Again, the program uses the stream manipulator
setw() to space the values uniformly, requiring the
#include directive for <iomanip>.
The function
main() then calls the function lowest() on the left side of an assignment to change the
lowest value in the array. This is done twice to show that it does actually work and is not an accident.
The contents of the array are then output to the display again, with the same field width as before, so
corresponding values line up.
As you can see from the output with the first call to
lowest(), the third element of the array, array[2],
contained the lowest value, so the function returned a reference to it and its value was changed to 6.9.
Similarly, on the second call,
array[10] was changed to 7.9. This demonstrates quite clearly that return-
ing a reference allows the use of the function on the left side of an assignment statement. The effect is as
if the variable specified in the
return statement appeared on the left of the assignment.
Of course, if you want to, you can also use it on the right side of an assignment, or in any other suitable
expression. If you had two arrays,
X and Y, with the number of array elements specified by lenx and
leny respectively, you could set the lowest element in the array x to twice the lowest element in the
array
y with this statement:
lowest(x, lenx) = 2.0*lowest(y, leny);

This statement would call your lowest()function twice — once with arguments y and leny in the expres-
sion on the right side of the assignment and once with arguments
x and lenx to obtain the address where
the result of the right-hand expression is to be stored.
A Teflon-Coated Rule: Returning References
A similar rule to the one concerning the return of a pointer from a function also applies to returning
references:
Never ever return a reference to a local variable from a function.
I’ll leave the topic of returning a reference from a function for now, but I haven’t finished with it yet. I
will come back to it again in the context of user-defined types and object-oriented programming, when
you will unearth a few more magical things that you can do with references.
268
Chapter 5: Introducing Structure into Your Programs
25905c05.qxd:WroxPro 2/21/08 8:48 AM Page 268

×