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

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

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

Defining Your Own Data Types

}

//Function to compare two boxes which returns true (1)

//if the first is greater than the second, and false (0) otherwise int Compare(CBox xBox)

{

return this->Volume() > xBox.Volume();

}

private:

 

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

};

int main()

{

CBox

match(2.2,

1.1, 0.5);

//

Declare

match

box

CBox

cigar(8.0,

5.0,1.0);

//

Declare

cigar

box

if(cigar.Compare(match)) cout << endl

<< “match is smaller than cigar”;

else

cout << endl

<< “match is equal to or larger than cigar”;

cout << endl; return 0;

}

How It Works

The member function Compare() returns true if the prefixed CBox object in the function call has a greater volume than the CBox object specified as an argument, and false if it doesn’t. In the return statements, the prefixed object is referred to through the pointer this, used with the indirect member access operator, ->, that you saw earlier in this chapter.

Remember that you use the direct member access operator when accessing members through objects and the indirect member access operator when accessing members through pointers to objects. this is a pointer so you use the -> operator.

The -> operator works the same for pointers to class objects as it did when you were dealing with a struct. Here, using the pointer this demonstrates that it exists and does work, but it’s quite unnecessary to use it explicitly in this case. If you change the return statement in the Compare() function to be

return Volume() > xBox.Volume();

you’ll find that the program works just as well. Any references to unadorned member names are automatically assumed to be the members of the object pointed to by this.

359

Chapter 7

You use the Compare() function in main() to check the relationship between the volumes of the objects match and cigar. The output from the program is:

Constructor called. Constructor called.

match is smaller than cigar

This confirms that the cigar object is larger than the match object.

It also wasn’t essential to define the Compare() function as a class member. You could just as well have written it as an ordinary function with the objects as arguments. Note that this isn’t true of the function Volume(), because it needs to access the private data members of the class. Of course, if you implemented the Compare()function as an ordinary function, it wouldn’t have access to the pointer this, but it would still be very simple:

// Comparing two CBox objects - ordinary function version int Compare(CBox B1, CBox B2)

{

return B1.Volume() > B2.Volume();

}

This has both objects as arguments and returns true if the volume of the first is greater than the last. You would use this function to perform the same function as in the last example with this statement:

if(Compare(cigar, match)) cout << endl

<< “match is smaller than cigar”;

else

cout << endl

<< “match is equal to or larger than cigar”;

If anything, this looks slightly better and easier to read than the original version; however, there’s a much better way to do this, which you will learn about in the next chapter.

const Objects of a Class

The Volume() function that you defined for the CBox class does not alter the object for which it is called, neither does a function such as getHeight() that returns the value of the m_Height member. Likewise, the Compare() function in the previous example didn’t change the class objects at all. This may seem at first sight to be a mildly interesting but largely irrelevant observation, but it isn’t, it’s quite important.

Let’s think about it.

You will undoubtedly want to create class objects that are fixed from time to time, just like values such as pi or inchesPerFoot that you might declare as const double. Suppose you wanted to define a CBox object as const(because it was a very important standard sized box, for instance. You might define it with the following statement:

const CBox standard(3.0, 5.0, 8.0);

360

Defining Your Own Data Types

Now that you have defined your standard box having dimensions 3x5x8, you don’t want it messed about with. In particular, you don’t want to allow the values stored in its data members to be altered. How can you be sure they won’t be?

Well, you already are. If you declare an object of a class as const, the compiler will not allow any member function to be called for it that might alter it. You can demonstrate this quite easily by modifying the declaration for the object, cigar, in the previous example to:

const CBox cigar(8.0, 5.0,1.0);

// Declare cigar box

If you try recompiling the program with this change, it won’t compile. You see the error message:

error C2662: ‘compare’ : cannot convert ‘this’ pointer from ‘const class CBox’ to

‘class CBox &’

Conversion loses qualifiers

This is produced for the if statement that calls the Compare() member of cigar. An object that you declare as const will always have a this pointer that is const, so the compiler will not allow any member function to be called that does not assume the this pointer that is passed to it is const. You need to find out how to make the this pointer in a member function const.

const Member Functions of a Class

To make the this pointer in a member function const, you must declare the function as const within the class definition. Take a look at how you do that with the Compare() member of CBox. The class definition needs to be modified to the following:

class CBox

// Class definition at global scope

{

 

public:

 

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

 

}

 

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

{

return m_Length*m_Width*m_Height;

}

//Function to compare two boxes which returns true (1)

//if the first is greater than the second, and false (0) otherwise int Compare(CBox xBox) const

{

return this->Volume() > xBox.Volume();

}

private:

361

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

To specify that a member function is const, you just append the const keyword to the function header. Note that you can only do this with class member functions, not with ordinary global functions. Declaring a function as const is only meaningful in the case of a function that is a member of a class. The effect is to make the this pointer in the function const, which in turn means that you cannot write a data member of the class on the left of an assignment within the function definition; it will be flagged as an error by the compiler. A const member function cannot call a non-const member function of the same class, since this would potentially modify the object.

When you declare an object as const, the member functions that you call for it must be declared as const; otherwise the program will not compile.

Member Function Definitions Outside the Class

When the definition of a const member function appears outside the class, the header for the definition must have the keyword const added, just as the declaration within the class does. In fact, you should always declare all member functions that do not alter the class object for which they are called as const. With this in mind, the CBox class could be defined as:

class CBox

// Class definition at global scope

{

 

public:

 

// Constructor

 

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

double Volume() const;

// Calculate the volume of a box

int Compare(CBox xBox) const;

// Compare two boxes

private:

 

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

};

 

 

 

This assumes that all function members are defined separately, including the constructor. Both the Volume() and Compare() members have been declared as const. The Volume() function is now defined outside the class as:

double CBox::Volume() const

{

return m_Length*m_Width*m_Height;

}

The Compare() function definition is:

int CBox::Compare(CBox xBox) const

{

return this->Volume() > xBox.Volume();

}

362

Defining Your Own Data Types

As you can see, the const modifier appears in both definitions. If you leave it out, the code will not compile. A function with a const modifier is a different function from one without, even though the name and parameters are exactly the same. Indeed you can have both const and non-const versions of a function in a class, and sometimes this can be very useful.

With the class declared as shown, the constructor also needs to be defined separately, like this:

CBox::CBox(double lv, double bv, double hv): m_Length(lv), m_Width(bv), m_Height(hv)

{

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

}

Arrays of Objects of a Class

You can declare an array of objects of a class in exactly the same way that you have declared an ordinary array where the elements were one of the built-in types. Each element of an array of class objects causes the default constructor to be called.

Try It Out

Arrays of Class Objects

We can use the class definition of CBox from the last example but modified to include a specific default constructor:

//Ex7_11.cpp

//Using an array of class objects #include <iostream>

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

class CBox

// Class definition at global scope

{

 

public:

 

// Constructor definition

 

CBox(double lv, 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;

 

}

 

CBox()

// Default constructor

{

 

cout << endl

<< “Default constructor called.”; m_Length = m_Width = m_Height = 1.0;

}

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

{

363

Chapter 7

return m_Length*m_Width*m_Height;

}

 

private:

 

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

};

int main()

{

CBox

boxes[5];

//

Array of CBox

objects declared

CBox

cigar(8.0, 5.0, 1.0);

//

Declare cigar

box

cout << endl

<<“Volume of boxes[3] = “ << boxes[3].Volume()

<<endl

<<“Volume of cigar = “ << cigar.Volume();

cout << endl; return 0;

}

The program produces this output:

Default constructor called.

Default constructor called.

Default constructor called.

Default constructor called.

Default constructor called.

Constructor called.

Volume of boxes[3] = 1

Volume of cigar = 40

How It Works

You have modified the constructor accepting arguments so that only two default values are supplied, and you have added a default constructor that initializes the data members to 1 after displaying a message that it was called. You are now able to see which constructor was called when. The constructors now have quite distinct parameter lists, so there’s no possibility of the compiler confusing them.

You can see from the output that the default constructor was called five times, once for each element of the boxes array. The other constructor was called to create the cigar object. It’s clear from the output that the default constructor initialization is working satisfactorily, as the volume of the array element is 1.

Static Members of a Class

Both data members and function members of a class can be declared as static. Because the context is a class definition, there’s a little more to it than the effect of the static keyword outside of a class, so let’s look at static data members.

364

Defining Your Own Data Types

Static Data Members of a Class

When you declare data members of a class to be static, the effect is that the static data members are defined only once and are shared between all objects of the class. Each object gets its own copies of each of the ordinary data members of a class, but only one instance of each static data member exists, regardless of how many class objects have been defined. Figure 7-7 illustrates this.

Class Definition

class CBox

{

public:

static int objectCount;

...

private:

double m_Length; double m_Width; double m_Height;

...

object1

 

 

object2

 

object3

 

 

 

 

 

 

 

 

m_Length

 

 

m_Length

 

m_Length

m_Width

 

 

m_Width

 

m_Width

m_Height

 

 

m_Height

 

m_Height

...

 

 

...

 

 

...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

One copy of each static data member is

 

 

 

shared between all objects of the class type

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

objectCount

 

 

 

Figure 7-7

 

 

 

 

 

 

 

 

 

 

 

 

 

 

One use for a static data member is to count how many objects actually exist. You could add a static data member to the public section of the CBox class by adding the following statement to the previous class definition:

static int objectCount;

// Count of objects in existence

You now have a problem. How do you initialize the static data member?

You can’t initialize the static data member in the class definition — that’s simply a blueprint for an object, and initializing values are not allowed. You don’t want to initialize it in a constructor because you want to increment it every time the constructor is called so the count of the number of objects created is accumulated. You can’t initialize it in another member function because a member function is associated with

365

Chapter 7

an object, and you want it initialized before any object is created. The answer is to write the initialization of the static data member outside of the class definition with this statement:

int CBox::objectCount = 0; // Initialize static member of class CBox

Notice that the static keyword is not included here; however, you do need to qualify the member name by using the class name and the scope resolution operator so that the compiler understands that you are referring to a static member of the class. Otherwise, you would simply create a global variable that was nothing to do with the class.

Try It Out

Counting Instances

Let’s add the static data member and the object counting capability to the last example.

//Ex7_12.cpp

//Using a static data member in a class #include <iostream>

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

class CBox

// Class definition at global scope

{

 

public:

 

static int objectCount;

// Count of objects in existence

// Constructor definition

CBox(double lv, 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;

 

objectCount++;

 

}

 

CBox()

// Default constructor

{

 

cout << endl

<< “Default constructor called.”; m_Length = m_Width = m_Height = 1.0; objectCount++;

}

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

{

return m_Length*m_Width*m_Height;

}

 

private:

 

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

366

 

 

Defining Your Own Data Types

 

};

 

 

 

 

 

int CBox::objectCount = 0;

// Initialize static member of CBox class

 

int main()

 

 

{

 

 

CBox boxes[5];

// Array of CBox objects declared

 

CBox cigar(8.0, 5.0, 1.0);

// Declare cigar box

cout << endl << endl

<<“Number of objects (through class) = “

<<CBox::objectCount;

cout << endl

<<“Number of objects (through object) = “

<<boxes[2].objectCount;

cout << endl; return 0;

}

This example produces the following output:

Default constructor called.

Default constructor called.

Default constructor called.

Default constructor called.

Default constructor called.

Constructor called.

Number of objects (through class) = 6

Number of objects (through object) = 6

How It Works

This code shows that it doesn’t matter how you refer to the static member ObjectCount (whether through the class itself or any of the objects of that class). The value is the same and it is equal to the number of objects of that class that have been created. The six objects are obviously the five elements of the Boxes array, plus the cigar object. It’s interesting to note that static members of a class exist even though there may be no members of the class in existence. This is evidently the case, because you initialized the static member ObjectCount before any class objects were declared.

Static data members are automatically created when your program begins, and they will be initialized with 0 unless you initialize them with some other value. Thus you need only to initialize static data members of a class if you want them to start out with a value other than 0.

Static Function Members of a Class

By declaring a function member as static, you make it independent of any particular object of the class. Referencing members of the class from within a static function must be done using qualified names (as you would do with an ordinary global function accessing a public data member). The static member function has the advantage that it exists, and can be called, even if no objects of the class exist.

367

Chapter 7

In this case, only static data members can be used because they are the only ones that exist. Thus, you can call a static function member of a class to examine static data members, even when you do not know for certain that any objects of the class exist. You could, therefore, use a static member function to determine whether some objects of the class have been created or, indeed, how many have been created.

Of course, after the objects have been defined, a static member function can access private as well as public members of class objects. A static function might have this prototype:

static void Afunction(int n);

A static function can be called in relation to a particular object by a statement such as the following:

aBox.Afunction(10);

where aBox is an object of the class. The same function could also be called without reference to an object. In this case, the statement would take the following form,

CBox::Afunction(10);

where CBox is the class name. Using the class name and the scope resolution operator serves to tell the compiler to which class the function Afunction() belongs.

Pointers and References to Class Objects

Using pointers, and particularly references to class objects, is very important in object-oriented programming and in particular in the specification of function parameters. Class objects can involve considerable amounts of data, so using the pass-by-value mechanism by specifying parameters to a function to be objects can be very time consuming and inefficient because each argument object will be copied. There are also some techniques involving the use of references that are essential to some operations with classes. As you’ll see, you can’t write a copy constructor without using a reference parameter.

Pointers to Class Objects

You declare a pointer to a class object in the same way that you declare other pointers. For example, a pointer to objects of the class CBox is declared in this statement:

CBox* pBox = 0;

// Declare a pointer to CBox

You can now use this to store the address of a CBox object in an assignment in the usual way, using the address operator:

pBox = &cigar;

// Store address of CBox object cigar in pBox

As you saw when you used the this pointer in the definition of the Compare() member function, you can call a function using a pointer to an object. You can call the function Volume() for the pointer pBox in a statement like this:

cout << pBox->Volume();

// Display volume of object pointed to by pBox

368