• No results found

Abstract Data Types and C++ Classes

2.4 CLASSES AND PARAMETERS

Every programmer requires an unshakable understanding of functions and parameters. The realm of OOP requires extra understanding because classes can be used as the type of a function’s parameter, or as the type of the return value from a function. This section illustrates several such functions, including a review of different kinds of parameters. The examples use a new class called point. PROGRAMMING EXAMPLE: The Point Class

The new class is a data type to store and manipulate the location of a single point on a plane, as shown in Figure 2.9. The example point in Figure 2.9(a) lies at a location with coordinates x = –1.0 and y = 0.8. The point

class has the member functions listed here:

• There is a constructor to initialize a point. The constructor’s parameters use default arguments that we’ll discuss in a moment.

• There is a member function to shift a point by given amounts along the x and y axes, as shown in Figure 2.9(b).

• There is a member function to rotate a point by 90° in a clockwise direction around the origin, as shown in Figure 2.9(c).

• There are two constant member func-tions that allow us to retrieve the cur-rent x and y coordinates of a point.

These functions are simple, yet they form the basis for an actual data type that is used in drawing programs and other graphics applica-tions. All the member functions, including the constructor, are listed in the header file of Figure 2.10 on page 64, with an implementa-tion in Figure 2.11 on page 65. After you’ve looked through the figures, we’ll review the implementations, starting with default argu-ments, which are used in an interesting way in the point’s constructor. around the origin. The coordinates of point FIGURE 2.9 Three Points in a Plane

A Header File

// FILE: point.h

// CLASS PROVIDED: point (part of the namespace main_savitch_2A) //

// CONSTRUCTOR for the point class:

//

// Postcondition: The point has been set to (initial_x, initial_y).

//

// MODIFICATION MEMBER FUNCTIONS for the point class:

//

// Postcondition: The point has been moved by x_amount along the x axis // and by y_amount along the y axis.

//

//

// Postcondition: The point has been rotated clockwise 90 degrees around the origin.

//

// CONSTANT MEMBER FUNCTIONS for the point class:

//

// Postcondition: The value returned is the x coordinate of the point.

//

//

// Postcondition: The value returned is the y coordinate of the point.

//

// VALUE SEMANTICS for the point class:

// Assignments and the copy constructor may be used with point objects.

#ifndef MAIN_SAVITCH_POINT_H

#define MAIN_SAVITCH_POINT_H namespace main_savitch_2A {

class point {

public:

// CONSTRUCTOR

point(double initial_x = 0.0, double initial_y = 0.0);

// MODIFICATION MEMBER FUNCTIONS

void shift(double x_amount, double y_amount);

void rotate90( );

// CONSTANT MEMBER FUNCTIONS

double get_x( ) const { return x; } double get_y( ) const { return y; } private:

double x; // x coordinate of this point double y; // y coordinate of this point };

}

#endif

FIGURE 2.10 Header File for the Point Class

point(double initial_x = 0.0, double initial_y = 0.0)

void shift(double x_amount, double y_amount)

void rotate90( )

double get_x( ) const

double get_y( ) const

www.cs.colorado.edu/~main/chapter2/point.h W W W

Classes and Parameters 65

Default Arguments

A default argument is a value that will be used for an argument when a pro-grammer does not provide an actual argument. Default arguments may be listed in the prototype of any function. For example, here is a modified version of a function prototype that we used on page 15:

int date_check(int year, int month , int day );

The exact behavior of date_check is not important. The important thing is that we have added default arguments for the month and day parameters. As shown An Implementation File

// FILE: point.cxx

// CLASS IMPLEMENTED: point (see point.h for documentation)

#include "point.h"

namespace main_savitch_2A {

{ // Constructor sets the point to a given position.

x = initial_x;

y = initial_y;

}

{

x += x_amount;

y += y_amount;

}

{

double new_x;

double new_y;

new_x = y; // For a 90-degree clockwise rotation, the new x is the original y, new_y = -x; // and the new y is -1 times the original x.

x = new_x;

y = new_y;

} }

FIGURE 2.11 Implementation File for the Point Class

point::point(double initial_x, double initial_y)

void point::shift(double x_amount, double y_amount)

void point::rotate90( )

www.cs.colorado.edu/~main/chapter2/point.cxx W W W

= 1 = 1

in the shaded part of the example, the default argument appears with an equals sign after the parameter name. Once a default argument is available, the func-tion can be called with or without certain arguments.

For example, a program can call date_check with just the year argument:

date_check(2000);

Since the last two arguments were omitted in this function call, the default argu-ments (month = 1 and day = 1) will be used. The function call is identical to call-ingdate_check(2000, 1, 1).

The function can also be called with a year and a month, omitting the day, as in this example:

date_check(2000, 7);

In this example, the default argument will be used for the day, so the function call is identical to calling date_check(2000, 7, 1).

default

arguments are especially convenient for constructors

The general rules for providing and using default arguments are summarized in Figure 2.12 on page 67. Default arguments are especially convenient for structors, such as the point’s constructor in Figure 2.10 on page 64. The con-structor’s prototype has these two default arguments:

point(double initial_x , double initial_y );

Both arguments of the constructor have a default of the double number 0.0, as shown in these three declarations of point objects:

point a(-1, 0.8);

point b(-1);

point c;

The third use of our constructor—simply —is interesting because defaults are used for both arguments. In effect, we have a constructor with no arguments. A constructor with no arguments is a default constructor. As we saw with the throttle, it’s important always to provide a default constructor. One way to provide a default constructor is to have a constructor with a complete set of default arguments.

A DEFAULT CONSTRUCTOR CAN BE PROVIDED BY USING DEFAULT ARGUMENTS

A good way to provide a default constructor is to have one constructor with default arguments for all of its arguments. Don’t forget that default constructors are always used with no argument list; not even the parentheses are present. So to use the point’s default constructor, we write .

= 0.0 = 0.0

Uses the usual constructor with two arguments Uses-1 for the first argument and uses the default argument, initial_y = 0.0, for the second argument Uses default arguments for both initial_x = 0.0 and initial_y = 0.0

point c;

PR O G R A M M I N G TI P





point c;

Classes and Parameters 67

Parameters

review of function parameters Classes can be used as the type of a function’s parameter, just like any other data

type. We’ll review three different kinds of parameters, with examples that use the new point class.

.

FIGURE 2.12 Default Arguments

Default Arguments

A default argument is a value that will be used for an argument when no actual argument is provided. The usage follows the format and rules listed here.

Syntax in a prototype’s parameter list:

<type name> <variable name> = <default value>

Example:

int date_check(int year, int month , int date );

1. The default argument is specified only once—in the prototype—and not in the function’s imple-mentation.

2. A function with several arguments does not need to specify default arguments for every argu-ment. But if only some of the arguments have defaults, then those arguments must be right-most in the parameter list.

3. In a function call, arguments with default values may be omitted from the right end of the actual argument list. For example:

date_check(2000);

date_check(2000, 7);

date_check(2000, 7, 22);

= 1 = 1

Uses default arguments for month = 1 and date = 1 Uses default argument for date = 1

Does not use the default arguments at all

Value parameters. The simplest parameters are value parameters.

To illustrate a value parameter, we’ll write a simple function. The function we have in mind has one value parameter, a point that we’ll call p. The integer returned by the function is the number of 90° rota-tions that would be needed to move p into the upper-right quad-rant, as shown in Figure 2.13.

This point, p, needs three 90°

rotations to move it to the upper- right quadrant.

FIGURE 2.13 A Rotating Point

Here is the function’s implementation:

int rotations_needed( )

// Postcondition: The value returned is the number of 90-degree // clockwise rotations needed to move p into the upper-right // quadrant (where x >= 0 and y >= 0).

{

int answer;

answer = 0;

while ((p.get_x( ) < 0) || (p.get_y( ) < 0)) {

p.rotate90( );

++answer;

}

return answer;

}

In C++, a value parameter is declared by placing the type name followed by the parameter name. So, we have written in the parameter list. The effect of a value parameter is that any change made to the parameter within the body of the function does not change the actual argument from the calling program.

Let’s look at an example in a program:

point sample(6, -4); // Constructor places the point at x = 6, y = –4.

cout << " x coordinate is " << sample.get_x( )

<< " y coordinate is " << sample.get_y( ) << endl;

cout << " Rotations: " << rotations_needed(sample) << endl;

cout << " x coordinate is " << sample.get_x( )

<< " y coordinate is " << sample.get_y( ) << endl;

formal

parameters and arguments

After the constructor, the code prints a message with the point’s coordinates.

Then, in the second output statement, rotations_needed is called. The func-tion’s parameter (p in this case) is referred to as the formal parameter to distin-guish it from the value that is passed in during the function call. The passed value (sample in this case) is the argument (sometimes called the actual argument or the actual parameter).

the effect of a value parameter

With a value parameter, the argument provides the initial value for the formal parameter. To be more precise, the formal parameter is implemented as a local variable of the function, and the class’s copy constructor is used to initialize the formal parameter as a copy of the actual argument. This is the only connection between the argument and the formal parameter. So, if the formal parameter p changes in our function body, the argument sample remains unchanged in the calling program. In our example, p will rotate three times, ending up at x = 4 and y = 6. The function returns the number of rotations (3), and sample still has its

point p

point p

Classes and Parameters 69

original value in the calling program. Therefore, the complete output from the code is:

x coordinate is 6 y coordinate is -4 Rotations: 3

x coordinate is 6 y coordinate is -4

The value of the argument, sample, did not change.

Reference parameters. Reference parameters are important types of parameters in C++. Our example of a reference parameter is similar to the

rotations_needed function. But this time the point p will be a reference param-eter. The new function does not return a value; it merely rotates p into the upper-right quadrant, as shown here:

void rotate_to_upper_right( )

// Postcondition: The point p has been rotated in 90-degree // increments until p has been moved into the upper-right // quadrant (where x >= 0 and y >= 0).

{

while ((p.get_x( ) < 0) || (p.get_y( ) < 0)) p.rotate90( );

}

In C++, a reference parameter is declared by placing the type name followed by the symbol & and the parameter name. So, we have written in the parameter list.

the effect of a reference parameter Here is the key to reference parameters: Any use of the parameter within the

body of the function will access the argument in the calling program. Let’s look at an example in a program:

Value Parameters

A value parameter is declared by writing the type name followed by the parameter name. With a value parameter, the argument provides the initial value for the formal parameter.

The value parameter is implemented as a local variable of the function, so that any changes made to the parameter in the body of the function will leave the argument unaltered.

Example:

int rotations_needed(point p);

point& p

point& p

point sample(6, -4); // Constructor places point at x = 6, y = –4.

cout << " x coordinate is " << sample.get_x( )

<< " y coordinate is " << sample.get_y( ) << endl;

rotate_to_upper_right(sample);

cout << " x coordinate is " << sample.get_x( )

<< " y coordinate is " << sample.get_y( ) << endl;

As before, the code prints the point’s coordinates and then calls the function.

The formal parameter is still called p and the argument is still sample—butp is now a reference parameter.

Becausep is a reference parameter, any use of p within the body of the func-tion will actually access sample. Thus, it is the argument sample that is rotated into the upper-right quadrant. When the function returns, sample has a new value. The complete output from the code is:

x coordinate is 6 y coordinate is -4 x coordinate is 4 y coordinate is 6

The value of the argument sample was changed by the function.

USINGA WRONG ARGUMENT TYPE FOR A REFERENCE PARAMETER In order for a reference parameter to work correctly, the data type of an argument must match exactly with the data type of the formal parameter. For example, sup-pose we have this reference parameter:

void make_int_42(int& i)

// Postcondition: i has been set to 42.

{

i = 42;

}

Suppose we have an integer variable j, and we make the call make_int_42(j). Reference Parameters

A reference parameter is declared by writing the type name followed by the character & and the parameter name. With a reference parameter, any use of the parameter within the body of the function will access the argument from the calling program. Changes made to the formal parameter in the body of the function will alter the argument.

Example:

void rotate_to_upper_right(point& p);

PITFALL





Classes and Parameters 71

After the function returns, j will have the value 42. But you might be surprised at the output from this code:

double d;

d = 0;

make_int_42(d);

cout << d;

This example compiles, but because dis the wrong data type, a separate integer copy ofd is created to use as the argument. The double variable d is never changed to 42.

If the argument’s data type does not exactly match the data type of the formal parameter, then the compiler will try to convert the argument to the correct type. If the conversion is possible, then the compiler treats the argument like a value parameter, passing a copy of the argument to the function. Fortunately, most com-pilers provide a warning message such as “Temporary used for parameter ’i’

in call to ’make_int_42’”—just make sure that you pay attention to the com-piler’s warnings!

Const reference parameters. For large data types, value parameters are less efficient than reference parameters. This is because a value parameter must make an extra copy of the argument to use within the body of the function.

Hence, we generally prefer to use reference parameters. But often a reference parameter is unattractive because we don’t want a programmer to worry about whether the function changes the actual argument. Changes are a definite possi-bility with a reference parameter, but they cannot occur with a value parameter.

the effect of a const reference parameter Sometimes there is a solution that provides the efficiency of a reference

parameter along with the security of a value parameter. The new parameter type is called a const reference parameter, and it may be used whenever a function does not attempt to make any changes to the parameter. For example, we can write a function that computes the distance between two points. The function has two point parameters, and neither parameter is changed by the function. There-fore we can use const reference parameters, as shown in Figure 2.14. The figure shows a function that computes the distance between two points, using this prototype:

double distance( );

The const reference parameter uses the keyword const before the parame-ter’s type, and it also uses the symbol & after the type. A const reference param-eter is efficient (since it is a reference paramparam-eter), but a programmer is guaranteed that the actual argument will not be altered by the function. For example, in our implementation of distance we use only get_x and get_y, both of which are const member functions and therefore cannot change p1 and

p2. It is important that get_x and get_y are actually declared as const member functions, otherwise the compiler would not permit us to use them with the const reference parameters p1 and p2.

does not change d prints 0

const point& p1, const point& p2

.

A Function Implementation

double distance( )

// Postcondition: The value returned is the distance between p1 and p2.

// Library facilities used: cmath {

double a, b, c_squared;

// Calculate differences in x and y coordinates.

a = p1.get_x( ) - p2.get_x( ); // Difference in x coordinates b = p1.get_y( ) - p2.get_y( ); // Difference in y coordinates

// Use Pythagorean Theorem to calculate the square of the distance between the points.

c_squared = a*a + b*b;

return sqrt(c_squared);

}

FIGURE 2.14 A Function with Const Reference Parameters

const point& p1, const point& p2

a2 + b2= c2

a b

Pythagorean Theorem

c p1

p2

www.cs.colorado.edu/~main/chapter2/newpoint.cxx W W W

CLARIFYING THE CONST KEYWORD Part 3: Const Reference Parameters A const reference parameter is declared

by writing the keyword const before a reference parameter and placing & after the parameter’s type. The parameter is efficient, but unlike an ordinary reference parameter, the function cannot attempt to make any changes to the value of the parameter.

Example:

double distance( , ...

If you use const reference parameters, be sure to follow the consistency requirements in the Programming Tip on page 73.

1. DECLARED CONSTANTS:PAGE12

2. CONSTANT MEMBER FUNCTIONS:PAGE38

3. CONST REFERENCE PARAMETERS

4. STATIC MEMBER CONSTANTS:PAGE104

5. CONST ITERATORS: PAGE 144

6. CONST PARAMETERS THAT ARE POINTERS OR ARRAYS:PAGE171

7. THE CONST KEYWORD WITH A POINTER TO A NODE, AND THE NEED FOR TWO VERSIONS OF SOME MEMBER FUNCTIONS: PAGE 227

const point& p1

Classes and Parameters 73

USE CONST CONSISTENTLY

When you define a new class along with functions and member functions to manip-ulate the class, you should make a consistent use of const. In particular:

1. Any member functions that do not change the value of the object should be declared constant member functions. This is accomplished by placing the keywordconst after the parameter list in both the prototype and the head of the function’s definition (see page 38). For example, the prototype of the throttle’sflow function is:

2. Whenever you use the class as the type of a parameter, and the function does not alter the parameter, use a const reference parameter. This is accomplished by placing the keyword const before the parameter’s type in the parameter list, and placing the symbol & after the type name (see page 72); for example, the prototype:

double distance( );

You should not use const unless you intend to use it at every location that meets these requirements.

When the Type of a Function’s Return Value Is a Class

The type of a function’s return value may be a class. Here is a typical example:

middle(const point& p1, const point& p2) // Postcondition: The value returned is the point that // is halfway between p1 and p2.

{

double x_midpoint, y_midpoint;

// Compute the x and y midpoints.

x_midpoint = (p1.get_x( ) + p2.get_x( )) / 2;

y_midpoint = (p1.get_y( ) + p2.get_y( )) / 2;

// Construct a new point and return it.

point midpoint(x_midpoint, y_midpoint);

}

The function computes a new point in the local variable midpoint and then returns a copy of this point. Often the return value of a function is stored in a

PR O G R A M M I N G TI P

 

double flow( ) const;

const point& p1, const point& p2

point Point Returned

by the Middle Function

p1

p2 midpoint

return midpoint;

local variable such as midpoint, but not always. Here’s another example, in which one of the parameters is the return value:

slower(const throttle& t1, const throttle& t2) // Postcondition: The value returned is a copy of t1 or t2, whichever // has the slower flow. If the flows are equal, then t1 is returned.

{

if (t1.flow( ) <= t2.flow( )) else

}

By the way, the C++ return statement uses the copy constructor to copy the function’s return value to a temporary location before returning the value to the calling program.

Self-Test Exercises for Section 2.4

22. Add default arguments to your throttle constructor from Self-Test Exer-cise 12 on page 51. Once you have done this, are the other two

22. Add default arguments to your throttle constructor from Self-Test Exer-cise 12 on page 51. Once you have done this, are the other two