Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning Visual C++ 2005 (2006) [eng]-1

.pdf
Скачиваний:
94
Добавлен:
16.08.2013
Размер:
18.66 Mб
Скачать

Introducing Structure into Your Programs

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.

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 modified 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;

 

cout << endl

<< “incr10(num) = “ << incr10(num);

cout << endl

249

Chapter 5

<< “num = “ << num;

cout << endl

<< “incr10(value) = “ << incr10(value);

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

250

Introducing Structure into Your Programs

command for the program. Values passed from the command line as arguments to main() are always 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 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 element 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 program. 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 elements — 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 5 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;

}

251

Chapter 5

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 arguments 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 command line values in the right pane.

Here is some typical output from this example entered in a command window:

C:\Visual C++ 2005\Examples\Ex5_09 trying multiple “argument values” 4.5 0.0 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 output the value of argc and then the values of each argument from the argv array in the for loop. 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 lit in the function definition. For example:

int sumValues(int first,...)

{

//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 stdarg.h header to help you do this. It’s easiest to show how these are used with an example.

252

Introducing Structure into Your Programs

Try It Out

Receiving a Variable Number of Arguments

This program uses a function that just sums the values of the arguments passed to it.

//Ex5_10.cpp

//Handling a variable number of arguments #include <iostream>

#include “stdarg.h” 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(2, 4, 6, 8, 10, 12) << endl;

cout << sum(11, 22, 33, 44, 55, 66, 77, 66, 99) << endl;

}

This example produces the following output:

10

172630728

Press any key to continue . . .

How It Works

The main() function calls the sum() function in the two output statements, in the first instance with six arguments and in the second with nine arguments.

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 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.

253

Chapter 5

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 stdarg.h header file and the pointer is used to point to each argument 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 increments 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 rests 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.

Returning Values from a Function

All the example functions that you have created have returned a single value. Is it possible to return anything 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

254

Introducing Structure into Your Programs

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 memory 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

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;

255

Chapter 5

}

// 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. We 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 original variable value. The memory previously allocated to result becomes available for other purposes, and here it has evidently been used for something else.

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 trying 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 continued 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);

256

Introducing Structure into Your Programs

*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

<< “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;

 

}

 

257

Chapter 5

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 9.) 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)

{

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;

258