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

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

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

Defining Your Own Data Types

You then have a statement that calculates the volume of box1 as the product of its three data members, and this value is output to the screen. Next, you output the sum of the data members of box2 by writing the expression for the sum of the data members directly in the output statement. The final action in the program is to output the number of bytes occupied by box1, which is produced by the operator sizeof.

If you run this program, you should get this output:

Volume of box1 = 33696

box2 has sides which total 66.5 inches. A CBox object occupies 24 bytes.

The last line shows that the object box1 occupies 24 bytes of memory, which is a result of having 3 data members of 8 bytes each. The statement that produced the last line of output could equally well have been written like this:

cout << endl

// Display the size of a box in memory

<<“A CBox object occupies “

<<sizeof (CBox) << “ bytes.”;

Here, I have used the type name between parentheses, rather than a specific object name as the operand for the sizeof operator. You’ll remember that this is standard syntax for the sizeof operator, as you saw in Chapter 4.

This example has demonstrated the mechanism for accessing the public data members of a class. It also shows that they can be used in exactly the same way as ordinary variables. You are now ready to break new ground by taking a look at member functions of a class.

Member Functions of a Class

A member function of a class is a function that has its definition or its prototype within the class definition. It operates on any object of the class of which it is a member, and has access to all the members of a class for that object.

Try It Out

Adding a Member Function to CBox

To see how you access the members of the class from within a function member, create an example extending the CBox class to include a member function that calculates the volume of the CBox object.

//Ex7_03.cpp

//Calculating the volume of a box with a member function #include <iostream>

using std::cout; using std::endl;

class CBox

// Class definition at global scope

{

 

public:

 

double m_Length;

// Length of a box in inches

double m_Width;

// Width of a box in inches

double m_Height;

// Height of a box in inches

339

Chapter 7

// Function to calculate the volume of a box double Volume()

{

return m_Length*m_Width*m_Height;

}

};

 

int main()

 

{

 

CBox box1;

// Declare box1 of type CBox

CBox box2;

// Declare box2 of type CBox

double boxVolume = 0.0;

// Stores the volume of a box

box1.m_Height = 18.0;

// Define the values

box1.m_Length = 78.0;

// of the members of

box1.m_Width = 24.0;

// the object box1

box2.m_Height = box1.m_Height - 10;

// Define box2

box2.m_Length = box1.m_Length/2.0;

// members in

box2.m_Width = 0.25*box1.m_Length;

// terms of box1

boxVolume = box1.Volume();

// Calculate volume of box1

cout << endl

 

<< “Volume of box1 = “ << boxVolume;

cout << endl

<<“Volume of box2 = “

<<box2.Volume();

cout << endl

<<“A CBox object occupies “

<<sizeof box1 << “ bytes.”;

cout << endl; return 0;

}

How It Works

The new code that you add to the CBox class definition is shaded. It’s just the definition of the Volume() function, which is a member function of the class. It also has the same access attribute as the data members: public. This is because every class member that you declare following an access attribute will have that access attribute, until another one is specified within the class definition. The Volume() function returns the volume of a CBox object as a value of type double. The expression in the return statement is just the product of the three data members of the class.

There’s no need to qualify the names of the class members in any way when you accessing them in member functions. The unqualified member names automatically refer to the members of the object that is current when the member function is executed.

340

Defining Your Own Data Types

The member function Volume() is used in the highlighted statements in main(), after initializing the data members (as in the first example). Using the same name for a variable in main() causes no conflict or problem. You can call a member function of a particular object by writing the name of the object to be processed, followed by a period, followed by the member function name. As noted previously, the function automatically accesses the data members of the object for which it was called, so the first use of Volume() calculates the volume of box1. Using only the name of a member will always refer to the member of the object for which the member function has been called.

The member function is used a second time directly in the output statement to produce the volume of box2. If you execute this example, it produces this output:

Volume of box1 = 33696

Volume of box2 = 6084

A CBox object occupies 24 bytes.

Note that the CBox object is still the same number of bytes. Adding a function member to a class doesn’t affect the size of the objects. Obviously, a member function has to be stored in memory somewhere, but there’s only one copy regardless of how many class objects you create, and the memory occupied by member functions isn’t counted when the sizeof operator produces the number of bytes that an object occupies.

The names of the class data members in the member function automatically refer to the data members of the specific object used to call the function, and the function can only be called for a particular object of the class. In this case, this is done by using the direct member access operator with the name of an object.

If you try to call a member function without specifying an object name, your program will not compile.

Positioning a Member Function Definition

A member function definition need not be placed inside the class definition. If you want to put it outside the class definition, you need to put the prototype for the function inside the class. If we rewrite the previous class with the function definition outside, the class definition looks like this:

class CBox

// Class definition at global scope

{

 

public:

 

double m_Length;

// Length of a box in inches

double m_Width;

// Width of a box in inches

double m_Height;

// Height of a box in inches

double Volume(void);

// Member function prototype

};

 

 

 

Now you need to write the function definition, but because it appears outside the definition of the class, there has to be some way of telling the compiler that the function belongs to the class CBox. This is done by prefixing the function name with the name of the class and separating the two with the scope resolution operator, ::, which is formed from two successive colons. The function definition would now look like this:

// Function to calculate the volume of a box double CBox::Volume()

{

return m_Length*m_Width*m_Height;

}

341

Chapter 7

It produces the same output as the last example; however, it isn’t exactly the same program. In the second case, all calls to the function are treated in the way that you’re already familiar with. However, when you define a function within the definition of the class as in Ex7_03.cpp, the compiler implicitly treated the function as an inline function.

Inline Functions

With an inline function, the compiler tries to expand the code in the body of the function in place of a call to the function. This avoids much of the overhead of calling the function and, therefore, speeds up your code. This is illustrated in Figure 7-5.

Function declared as inline in a class

inline void function()

{ body }

The compiler replaces calls of inline funtion with body code for the function, suitably adjusted to avoid problems with variable names or scope.

int main(void)

{

...

function();

{ body }

...

function();

{ body }

....

}

Figure 7-5

Of course, the compiler ensures that expanding a function inline doesn’t cause any problems with variable names or scope.

The compiler may not always be able to insert the code for a function inline (such as with recursive functions or functions for which you have obtained an address), but generally it will work. It’s best used for very short, simple functions, such as our function Volume() in the CBox class because such functions execute faster and inserting the body code does not significantly increase the size of the executable module.

With the function definition outside of the class definition, the compiler treats the function as a normal function and a call of the function will work in the usual way; however, it’s also possible to tell the compiler that, if possible, you would like the function to be considered as inline. This is done by simply placing the keyword inline at the beginning of the function header. So, for this function, the definition would be as follows:

// Function to calculate the volume of a box inline double CBox::Volume()

{

return m_Length*m_Width*m_Height;

}

342

Defining Your Own Data Types

With this definition for the function, the program would be exactly the same as the original. This enables you to put the member function definitions outside of the class definition, if you so choose, and still retain the execution performance benefits of inlining.

You can apply the keyword inline to ordinary functions in your program that have nothing to do with classes and get the same effect. Remember, however, that it’s best used for short, simple functions.

You now need to understand a little more about what happens when you declare an object of a class.

Class Constructors

In the previous program example, you declared the CBox objects, box1 and box2, and then laboriously worked through each of the data members for each object in order to assign an initial value to it. This is unsatisfactory from several points of view. First of all, it would be easy to overlook initializing a data member, particularly with a class which had many more data members than our CBox class. Initializing the data members of several objects of a complex class could involve pages of assignment statements. The final constraint on this approach arises when you get to defining data members of a class that don’t have the attribute public — you won’t be able to access them from outside the class anyway. There has to be a better way, and of course there is — it’s known as the class constructor.

What Is a Constructor?

A class constructor is a special function in a class which is called when a new object of the class is created. It therefore provides the opportunity to initialize objects as they are created and to ensure that data members only contain valid values. A class may have several constructors enabling you to create objects in various ways.

You have no leeway in naming the constructors in a class — they always have the same name as the class in which they are defined. The function CBox(), for example, is a constructor for our class CBox. It also has no return type. It’s wrong to specify a return type for a constructor; you must not even write it as void. The primary purpose of a class constructor is to assign initial values to the data elements of the class, and no return type for a constructor is necessary or permitted.

Try It Out

Adding a Constructor to the CBox class

Let’s extend our CBox class to incorporate a constructor.

//Ex7_04.cpp

//Using a constructor #include <iostream> using std::cout; using std::endl;

class CBox

// Class definition at global scope

{

 

public:

 

double m_Length;

// Length of a box in inches

double m_Width;

// Width of a box in inches

double m_Height;

// Height of a box in inches

343

Chapter 7

// Constructor definition CBox(double lv, double bv, double hv)

{

cout << endl << “Constructor called.”;

m_Length = lv;

// Set values of

m_Width = bv;

// data members

m_Height = hv;

 

}

// Function to calculate the volume of a box double Volume()

{

return m_Length* m_Width* m_Height;

}

};

 

int main()

 

{

 

CBox box1(78.0,24.0,18.0);

// Declare and initialize box1

CBox cigarBox(8.0,5.0,1.0);

// Declare and initialize cigarBox

double boxVolume = 0.0;

// Stores the volume of a box

boxVolume = box1.Volume();

// Calculate volume of box1

cout << endl

 

<< “Volume of box1 = “ << boxVolume;

cout << endl

<<“Volume of cigarBox = “

<<cigarBox.Volume();

cout << endl; return 0;

}

How It Works

The constructor CBox() has been written with three parameters of type double, corresponding to the initial values for the m_Length, m_Width and m_Height members of a CBox object. The first statement in the constructor outputs a message so that you can tell when it’s been called. You wouldn’t do this in production programs, but because it’s very helpful in showing when a constructor is called, it’s often used when testing a program. I’ll use it regularly for the purposes of illustration. The code in the body of the constructor is very simple. It just assigns the arguments that you pass to the constructor when you call it to the corresponding data members. If necessary, you could also include checks that valid, non-negative arguments are supplied and, in a real context, you probably would want to do this, but our primary interest here is in seeing how the mechanism works.

Within main(), you declare the object box1 with initializing values for the data members m_Length, m_Width, and m_Height, in sequence. These are in parentheses following the object name. This uses the functional notation for initialization that, as you saw in Chapter 2, can also be applied to initializing ordinary variables of basic types. You also declare a second object of type CBox, called cigarBox, which also has initializing values.

344

Defining Your Own Data Types

The volume of box1 is calculated using the member function Volume() as in the previous example and is then displayed on the screen. You also display the value of the volume of cigarBox. The output from the example is:

Constructor called.

Constructor called.

Volume of box1 = 33696

Volume of cigarBox = 40

The first two lines are output from the two calls of the constructor CBox(), once for each object declared. The constructor that you have supplied in the class definition is automatically called when a CBox object is declared, so both CBox objects are initialized with the initializing values appearing in the declaration.

These are passed to the constructor as arguments, in the sequence that they are written in the declaration. As you can see, the volume of box1 is the same as before and cigarBox has a volume looking suspiciously like the product of its dimensions, which is quite a relief.

The Default Constructor

Try modifying the last example by adding the declaration for box2 that we had previously:

CBox box2;

// Declare box2 of type CBox

Here, we’ve left box2 without initializing values. When you rebuild this version of the program, you get the error message:

error C2512: ‘CBox’: no appropriate default constructor available

This means that the compiler is looking for a default constructor for box2 (also referred to as the no-arg constructor because it doesn’t require arguments when it is called) because you haven’t supplied any initializing values for the data members. A default constructor is one that does not require any arguments to be supplied, which can be either a constructor that has no parameters specified in the constructor definition, or one whose arguments are all optional. Well, this statement was perfectly satisfactory in Ex7_02.cpp, so why doesn’t it work now?

The answer is that the previous example used a default no-argument constructor that was supplied by the compiler, and the compiler provided this constructor because you didn’t supply one. Because in this example you did supply a constructor, the compiler assumed that you were taking care of everything and didn’t supply the default. So, if you still want to use declarations for CBox objects that aren’t initialized, you have to include the default constructor yourself. What exactly does the default constructor look like? In the simplest case, it’s just a constructor that accepts no arguments; it doesn’t even need to do anything:

CBox()

//

Default

constructor

{}

//

Totally

devoid of statements

You can see such a constructor in action.

345

Chapter 7

Try It Out

Supplying a Default Constructor

Let’s add our version of the default constructor to the last example, along with the declaration for box2, plus the original assignments for the data members of box2. You must enlarge the default constructor just enough to show that it is called. Here is the next version of the program:

//Ex7_05.cpp

//Supplying and using a default constructor #include <iostream >

using std::cout; using std::endl;

class CBox

// Class definition at global scope

{

 

public:

 

double m_Length;

// Length of a box in inches

double m_Width;

// Width of a box in inches

double m_Height;

// Height of a box in inches

// Constructor definition CBox(double lv, double bv, double hv)

{

cout << endl << “Constructor called.”;

m_Length = lv;

// Set values of

m_Width = bv;

// data members

m_Height = hv;

 

}

 

//Default constructor definition CBox()

{

cout << endl << “Default constructor called.”;

}

//Function to calculate the volume of a box double Volume()

{

return m_Length*m_Width*m_Height;

}

};

int main()

{

CBox box1(78.0,24.0,18.0);

// Declare and initialize box1

CBox box2;

// Declare box2 - no initial values

CBox cigarBox(8.0, 5.0, 1.0);

// Declare and initialize cigarBox

double boxVolume = 0.0;

// Stores the volume of a box

boxVolume = box1.Volume();

// Calculate volume of box1

cout << endl

 

346

 

 

Defining Your Own Data Types

 

 

 

<< “Volume of box1 = “ << boxVolume;

box2.m_Height = box1.m_Height - 10;

// Define box2

box2.m_Length = box1.m_Length / 2.0;

//

members in

box2.m_Width = 0.25*box1.m_Length;

//

terms of box1

cout << endl

<<“Volume of box2 = “

<<box2.Volume();

cout << endl

<<“Volume of cigarBox = “

<<cigarBox.Volume();

cout << endl; return 0;

}

How It Works

Now that you have included your own version of the default constructor, there are no error messages from the compiler and everything works. The program produces this output:

Constructor called.

Default constructor called.

Constructor called.

Volume of box1 = 33696

Volume of box2 = 6084

Volume of cigarBox = 40

All that the default constructor does is display a message. Evidently, it was called when you declared the object box2. You also get the correct value for the volumes of all three CBox objects, so the rest of the program is working as it should.

One aspect of this example that you may have noticed is that you now know we can overload constructors just as you overloaded functions in Chapter 6. You have just executed an example with two constructors that differ only in their parameter list. One has three parameters of type double and the other has no parameters at all.

Assigning Default Parameter Values in a Class

When discussing functions, you saw how you could specify default values for the parameters to a function in the function prototype. You can also do this for class member functions, including constructors. If you put the definition of the member function inside the class definition, you can put the default values for the parameters in the function header. If you include only the prototype of a function in the class definition, the default parameter values should go in the prototype.

If you decided that the default size for a CBox object was a unit box with all sides of length 1, you could alter the class definition in the last example to this:

class CBox

// Class definition at global scope

{

 

public:

 

347

Chapter 7

double m_Length; double m_Width; double m_Height;

//Length of a box in inches

//Width of a box in inches

//Height of a box in inches

// Constructor definition

CBox(double lv = 1.0, double bv = 1.0, double hv = 1.0)

{

cout << endl << “Constructor called.”;

m_Length = lv;

// Set values of

m_Width = bv;

// data members

m_Height = hv;

 

}

//Default constructor definition CBox()

{

cout << endl << “Default constructor called.”;

}

//Function to calculate the volume of a box double Volume()

{

return m_Length*m_Width*m_Height;

}

};

If you make this change to the last example, what happens? You get another error message from the compiler, of course. Amongst a lot of other stuff, you get these useful comments from the compiler:

warning C4520: ‘CBox’: multiple default constructors specified error C2668: ‘CBox::CBox’: ambiguous call to overloaded function

This means that the compiler can’t work out which of the two constructors to call — the one for which you have set default values for the parameters or the constructor that doesn’t accept any parameters. This is because the declaration of box2 requires a constructor without parameters and either constructor can now be called without parameters. The immediately obvious solution to this is to get rid of the constructor that accepts no parameters. This is actually beneficial. Without this constructor, any CBox object declared without being explicitly initialized will automatically have its members initialized to 1.

Try It Out

Supplying Default Values for Constructor Arguments

You can demonstrate this with the following simplified example:

//Ex7_06.cpp

//Supplying default values for constructor arguments #include <iostream>

using std::cout; using std::endl;

class CBox

// Class definition at global scope

{

 

public:

 

double m_Length;

// Length of a box in inches

348