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

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

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

Introducing Structure into Your Programs

x = power( x , power( 2.0 , 2.0 ));

initial value

1 Converted

3.0

 

to type int

5 result stored

2 power( 2.0 , 2 )

back in x

 

4.0 (type double)

 

 

Converted

 

 

to type int 3

4 power( 3.0 , 4 )

81.0 (type double)

Figure 5-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 therefore 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.

239

Chapter 5

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.

int index = 2; double value = 10.0;

double result = power(value, index);

index 2

Temporary copies of the arguments are made for use in the function

value 10.0

copy of value

copy of index

10.0

2

double power ( double x

,

int n )

{

 

 

 

...

The original arguments are not

 

accessible here, only the copies.

}

Figure 5-3

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.

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>

240

Introducing Structure into Your Programs

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

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

241

Chapter 5

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)

 

{

 

int num = 3;

 

 

 

int* pnum = &num;

// Pointer to num

cout << endl

<< “Address passed = “ << pnum;

cout << endl

<< “incr10(pnum) = “ << incr10(pnum);

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.

242

Introducing Structure into Your Programs

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.

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)

{

243

Chapter 5

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

}

 

 

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 prototype, 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 we 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 we 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 notation throughout, in spite of the fact that we 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)

{

244

Introducing Structure into Your Programs

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

// 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, 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 Multi-Dimensional Arrays to a Function

Passing a multi-dimensional 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]);

245

Chapter 5

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.

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 Multi-Dimensional 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

 

246

Introducing Structure into Your Programs

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. 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 multi-dimensional 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 function. 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;

cout << endl

247

Chapter 5

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

cout << endl

<< “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(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 parameter 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 contradictory 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.

248