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

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

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

More on Classes

The functions implementing the >= and <= operators are the same as the first two functions, but with the <= operator replacing each use of <, and >= instead of >; there’s little point in reproducing them at this stage. The operator==() functions are also very similar:

//Function for testing if constant is == the volume of a CBox object int operator==(const double& value, const CBox& aBox)

{

return value == aBox.Volume();

}

//Function for testing if CBox object is == a constant

int operator==(const CBox& aBox, const double& value)

{

return value == aBox;

}

You now have a complete set of comparison operators for CBox objects. Keep in mind that these also work with expressions as long as the expressions result in objects of the required type, so you are able to combine them with the use of other overloaded operators.

Combining CBox Objects

Now you come to the question of overloading the operators +, *, /, and %. I will take them in order. The add operation that you already have from Ex8_06.cpp has this prototype:

CBox operator+(const CBox& aBox);

// Function adding two CBox objects

Although the original implementation of this isn’t an ideal solution, let’s use it anyway to avoid overcomplicating the class. A better version would need to examine whether the operands had any faces with the same dimensions and if so, join along those faces, but coding that could get a bit messy. Of course, if this were a practical application, a better add operation could be developed later and substituted for the existing version, and any programs written using the original would still run without change. The separation of the interface to a class from its implementation is crucial to good C++ programming.

Notice that I conveniently forgot the subtraction operator. This is a judicious oversight to avoid the complications inherent in implementing this. If you’re really enthusiastic about it, and you think it’s a sensible idea, you can give it a try — but you need to decide what to do when the result has a negative volume. If you allow the concept, you need to resolve which box dimension or dimensions are to be negative, and how such a box is to be handled in subsequent operations.

The multiply operation is very easy. It represents the process of creating a box to contain n boxes, where n is the multiplier. The simplest solution would be to take the m_Length and m_Width of the object to be packed and multiply the height by n to get the new CBox object. You can make it a little more clever by checking whether or not the multiplier is even and, if it is, stack the boxes side-by-side by doubling the m_Width value and only multiplying the m_Height value by half of n. This mechanism is illustrated in Figure 8-6.

439

Chapter 8

L

CBox Multiply: n odd

W

: 3*aBox

3*H

L

W

H

L

CBox Multiply: n even

: 6*aBox 2*W

3*H

Figure 8-6

Of course, you don’t need to check which is the larger of the length and width for the new object because the constructor will sort it out automatically. You can write the version of the operator*() function as a member function with the left operand as a CBox object:

// CBox multiply operator this*n CBox operator*(int n) const

{

if(n % 2)

 

return CBox(m_Length, m_Width, n*m_Height);

// n odd

440

More on Classes

else

 

return CBox(m_Length, 2.0*m_Width, (n/2)*m_Height);

// n even

}

Here, you use the % operator to determine whether n is even or odd. If n is odd, the value of n % 2 is 1 and the if statement is true. If it’s even, n % 2 is 0 and the statement is false.

You can now use the function you have just written in the implementation of the version with the left operand as an integer. You can write this as an ordinary non-member function:

// CBox multiply operator n*aBox

CBox operator*(int n, const CBox& aBox)

{

return aBox*n;

}

This version of the multiply operation simply reverses the order of the operands so as to use the previous version of the function directly. That completes the set of arithmetic operators for CBox objects that you defined. We can finally look at the two analytical operator functions, operator/() and operator%().

Analyzing CBox Objects

As I have said, the division operation determines how many CBox objects are identical to that specified by the right operand can be contained in the CBox object specified by the left operand. To keep it relatively simple, assume that all the CBox objects are packed the right way up, that is, with the height dimensions vertical. Also assume that they are all packed the same way round, so that their length dimensions are aligned. Without these assumptions, it can get rather complicated.

The problem then amounts to determining how many of the right-operand objects can be placed in a single layer, and then deciding how many layers we can get inside the left-operand CBox.

You can code this as a member function like this:

int operator/(const CBox& aBox)

{

int tc1 = 0; // Temporary for number in horizontal plane this way int tc2 = 0; // Temporary for number in a plane that way

tc1 = static_cast<int>((m_Length / aBox.m_Length))* static_cast<int>((m_Width / aBox.m_Width)); // to fit this way

tc2 = static_cast<int>((m_Length / aBox.m_Width))* static_cast<int>((m_Width / aBox.m_Length)); // and that way

//Return best fit

return static_cast<int>((m_Height/aBox.m_Height)*(tc1>tc2 ? tc1 : tc2));

}

This function first determines how many of the right-operand CBox objects can fit in a layer with their lengths aligned with the length dimension of the left-operand CBox. This is stored in tc1. You then calculate how many can fit in a layer with the lengths of the right-operand CBoxes lying in the width direction of the left-operand CBox. Finally you multiply the larger of tc1 and tc2 by the number of layers you can pack in, and return that value. This process is illustrated in Figure 8-7.

441

Chapter 8

 

W = 2

bBox

W = 6

aBox

 

H = 1

 

 

 

W

8/3 = 2

bBox bBox

6/2 = 3 bBox

bBox

bBox bBox

2/1

With this configuration 12 can be stored

Figure 8-7

8/2 = 4

bBox

bBox

bBox

bBox

6/2 = 3

 

 

 

bBox

bBox

bBox

bBox

2/1

 

 

 

Result is 16

We look at two possibilities: fitting bBox into aBox with the length aligned with that of aBox and then with the length of bBox aligned with the width of aBox. You can see from Figure 8-7 that the best packing results from rotating bBox so that the width divides into the length of aBox.

The other analytical operator function, operator%(), for obtaining the free volume in a packed aBox is easier because you can use the operator you have just written to implement it. You can write it as an ordinary global function because you don’t need access to the private members of the class.

// Operator to return the free volume in a packed box double operator%(const CBox& aBox, const CBox& bBox)

{

return aBox.Volume() - ((aBox/bBox)*bBox.Volume());

}

This computation falls out very easily using existing class functions. The result is the volume of the big box, aBox, minus the volume of the bBox boxes that can be stored in it. The number of bBox objects packed into aBox is given by the expression aBox/bBox, which uses the previous overloaded operator. You multiply this by the volume of bBox objects to get the volume to be subtracted from the volume of the large box, aBox.

442

More on Classes

That completes the class interface. Clearly, there are many more functions that might be required for a production problem solver, but, as an interesting working model demonstrating how you can produce a class for solving a particular kind of problem, it will suffice. Now you can go ahead and try it out on a real problem.

Try It Out

A Multifile Project Using the CBox Class

Before you can actually start writing the code to use the CBox class and its overloaded operators, first you need to assemble the definition for the class into a coherent whole. You’re going to take a rather different approach from what you’ve seen previously, in that you’re going to write multiple files for the project. You’re also going to start using the facilities that Visual C++ 2005 provides for creating and maintaining code for our classes. This means that you do less of the work, but it will also mean that the code is slightly different in places.

Start by creating a new WIN32 project for a console application called Ex8_08 and check the Empty project application option. If you select the Class View tab, you’ll see the window shown in Figure 8-8.

Figure 8-8

This shows a view of all the classes in a project, but of course, there are none here for the moment. Although there are no classes defined — or anything else for that matter — Visual C++ 2005 has already made provision for including some. You can use Visual C++ 2005 to create a skeleton for our CBox class, and the files that relate to it too. Right-click Ex8_08 in Class View and select Add/Class from the popup menu that appears. You can then select C++ from the class categories in the left pane of the Add

443

Chapter 8

Class dialog box that displays and the C++ Class template in the right pane and press Enter. You can then enter the name of the class that you want to create, CBox, in the Generic Class Wizard dialog box as shown in Figure 8-9.

Figure 8-9

The name of the file that’s indicated on the dialog, Box.cpp, is used to contain the class implementation, which consists of the definitions for the function members of the class. This is the executable code for the class. You can change the name of this file if you want, but Box.cpp looks like a good name for the file in this case. The class definition will be stored in a file called Box.h. This is the standard way of structuring a program. Code that consists of class definitions is stored in files with the extension .h, and code that defines functions is stored in files with the extension .cpp. Usually, each class definition goes in its own .h file, and each class implementation goes in its own .cpp file.

When you click on the Finish button in the dialog box, two things happen:

1.A file Box.h is created, containing a skeleton definition for the class CBox. This includes a noargument constructor and a destructor.

2.A file Box.cpp is created containing a skeleton implementation for the functions in the class with definitions for the constructor and the destructor(both bodies are empty, of course.

The editor pane displaying the code should be as shown in Figure 8-10. If it is not presently displayed, just double-click CBox in Class View, and it should appear.

444

More on Classes

Figure 8-10

As you can see, there are two controls above the pane containing the code listing for the class. The left control displays the current class name, CBox, and clicking the button to the right of the class name displays the list of all the classes in the project. In general you can use this control to switch to another class by selecting it from the list, but here you have just one class defined. The control to the right relates to the members defined in the .cpp file for the current class, and clicking its button displays the members of the class. Selecting a member from the list causes its code to be visible in the pane below.

Let’s start developing the CBox class based on what Visual C++ has provided automatically for us.

Defining the CBox Class

If you click on the + to the left of Ex8_08 in the Class View, the tree expands and you see that CBox is now defined for the project. All the classes in a project are displayed in this tree. You can view the source code supplied for the definition of a class by double-clicking the class name in the tree, or by using the controls above the pane displaying the code as I described in the previous section.

The CBox class definition that was generated starts with a preprocessor directive:

#pragma once

The effect of this is to prevent file from being opened and included into the source code more than once by the compiler in a build. Typically a class definition is included into several files in a project because each file that references the name of a particular class needs access to its definition. In some instances, a header file may itself have #include directives for other header files. This can result in the possibility of the contents of a header file appearing more than once in the source code. Having more than one definition of a class in a build is not allowed and will be flagged as an error. Having the #pragma once directive at the start of every header file ensures this cannot happen.

Note that #pragma once is a Microsoft-specific directive that may not be supported in other development environments. If you are developing code that you anticipate may need to be compiled in other environments, you can use the following form of directive in a header file to achieve the same effect:

445

Chapter 8

//Box.h header file #ifndef BOX_H #define BOX_H

//Code that must not be included more than once

//such as the CBox class definition

#endif

The important lines are shaded and correspond to directives that supported by any ISO/ANSI C++ compiler. The lines following the #ifndef directive down to the #endif directive are included in a build as long as the symbol BOX_H is not defined. The line following #ifndef defines the symbol BOX_H thus ensuring that the code in this header file are be included a second time. Thus this has the same effect as placing the #pragma once directive at the beginning of a header file. Clearly the #pragma once directive is simpler and less cluttered so it’s better to use that when you only expect to be using your code in the Visual C++ 2005 development environment. You sometimes see the #ifndef/#endif combination written as:

#if !defined BOX_H

#define BOX_H

//Code that must not be included more than once

//such as the CBox class definition

#endif

The Box.cpp file that was generated by Class wizard contains the following code:

#include “Box.h”

CBox::CBox(void)

{

}

CBox::~CBox(void)

{

}

The first line is an #include preprocessor directive that has the effect of including the contents of the Box.h file(the class definition(into this file, Box.cpp. This is necessary because the code in Box.cpp refers to the CBox class name and the class definition needs to be available to assign meaning to the name CBox.

Adding Data Members

You can add the private data members m_Length, m_Width, and m_Height. Right-click CBox in Class View and select Add/Add Variable from the context menu. You can then specify the name, type, and access for first data member that you want to add to the class in the Add Member Variable Wizard dialog box.

The way you specify a new data member in this dialog box is quite explanatory. If you specify a lower limit for a data member, you must also specify an upper limit. When you specify limits, the constructor definition in the .cpp file will be modified to add a default value for the data member corresponding to the lower limit. You can add a comment in the lower input field if you wish. When you click on the OK button, the variable is added to the class definition along with the comment if you have supplied one. You should repeat the process for the other two class data members, m_Width and m_Height. The class definition in Box.h is then modified to look like this:

446

More on Classes

#pragma once

class CBox

{

public:

CBox(void);

public:

~CBox(void);

private:

//Length of a box in inches double m_Length;

//Width of a box in inches double m_Width;

//Height of a box in inches double m_Height;

};

Of course, you’re quite free to enter the declarations for these members manually, directly into the code, if you want. You always have the choice of whether you use the automation provided by the IDE. You can also manually delete anything that was generated automatically, but don’t forget that sometimes both the .h and .cpp file need to be changed.

It’s a good idea to save all the files whenever you make manual changes as this will cause the information in Class View to be updated.

If you look in the Box.cpp file, you’ll see that the wizard has also added an initialization list to the constructor definition for the data members you have added, with each variable initialized to 0. You’ll modify the constructor to do what you want next.

Defining the Constructor

You need to change the declaration of the no-arg constructor in the class definition so that it has arguments with default values, so modify it to:

CBox(double lv = 1.0, double wv = 1.0, double hv = 1.0);

Now you’re ready to implement it. Open the Box.cpp file if it isn’t open already and modify the constructor definition to:

CBox::CBox(double lv, double wv, double hv)

{

lv = lv <= 0.0 ? 1.0 : lv; wv = wv <= 0.0 ? 1.0 : wv; hv = hv <= 0.0 ? 1.0 : hv;

//Ensure positive

//dimensions for

//the object

m_Length = lv>wv ? lv : wv;

// Ensure that

m_Width = wv<lv ? wv : lv;

// length >= width

m_Height = hv;

 

}

Remember that the initializers for the parameters to a member function should only appear in the member declaration in the class definition, not in the definition of the function. If you put them in the function

447

Chapter 8

definition, your code will not compile. You’ve seen this code already, so I won’t discuss it again. It would be a good idea to save the file at this point by clicking the Save toolbar button. Get into the habit of saving the file you’re editing before you switch to something else. If you need to edit the constructor again, you can get to it easily by either double-clicking its entry in the lower pane on the Class View tab or by selecting it from the right drop-down menu above the pane displaying the code.

You can also get to a member function’s definition in a .cpp file or to its declaration in a .h file directly by right-clicking its name in the Class View pane and selecting the appropriate item from the context menu that appears.

Adding Function Members

You need to add all the functions you saw earlier to the CBox class. Previously, you defined several function members within the class definition, so that these functions were automatically inline. You can achieve the same result by entering the code in the class definition for these functions manually, or you can use the Add Member Function wizard.

You might think that you can define each inline function in the .cpp file, and add the keyword inline to the function definitions, but the problem here is that inline functions end up not being “‘real” functions. Because the code from the body of each function has to be inserted directly at the position it is called, the definitions of the functions need to be available when the file containing calls to the functions is compiled. If they’re not, you’ll get linker errors and your program will not run. If you want member functions to be inline, you must include the function definitions in the .h file for the class. They can be defined either within the class definition, or immediately following it in the .h file. You should put any global inline functions you need into a .h file and #include that file into any .cpp file that uses them.

To add the GetHeight() function as inline, right-click on CBox on the Class View tab and select Add > Add Function from the context menu. You then can enter the data defining the function in the dialog that is displayed, as Figure 8-11 shows.

Figure 8-11

448