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

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

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

Defining Your Own Data Types

You define a reference class using the ref class keyword — both words together separated by one or more spaces represent a single keyword. Here’s the CBox class from the Ex7_07 example redefined as a reference class.

ref class Box

{

public:

// No-arg constructor supplying default field values Box(): Length(1.0), Width(1.0), Height(1.0)

{

Console::WriteLine(L”No-arg constructor called.”);

}

// Constructor definition using an initialisation list Box(double lv, double bv, double hv):

Length(lv), Width(bv), Height(hv)

{

Console::WriteLine(L”Constructor called.”);

}

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

{

return Length*Width*Height;

}

 

private:

 

double Length;

// Length of a box in inches

double Width;

// Width of a box in inches

double Height;

// Height of a box in inches

};

Note first that I have removed the C prefix for the class name and the m_ prefix for member names because this notation is not recommended for C++/CLI classes. You cannot specify default values for function and constructor parameters in C++/CLI classes so you have to add a no-arg constructor to the Box class to fulfill this function. The no-arg constructor just initializes the three private fields with 1.0.

Try It Out

Using a Reference Class Type

Here’s an example that uses the Box class that you saw in the previous section.

//Ex7_15.cpp : main project file.

//Using the Box reference class type

#include “stdafx.h”

using namespace System;

ref class Box

{

public:

// No-arg constructor supplying default field values Box(): Length(1.0), Width(1.0), Height(1.0)

{

379

Chapter 7

Console::WriteLine(L”No-arg constructor called.”);

}

// Constructor definition using an initialisation list Box(double lv, double bv, double hv):

Length(lv), Width(bv), Height(hv)

{

Console::WriteLine(L”Constructor called.”);

}

// Function to calculate the volume of a box

double Volume()

{

return Length*Width*Height;

}

 

private:

 

double Length;

// Length of a box in inches

double Width;

// Width of a box in inches

double Height;

// Height of a box in inches

};

 

int main(array<System::String ^> ^args)

 

{

 

Box^ aBox;

// Handle of type Box^

Box^ newBox = gcnew Box(10, 15, 20);

 

aBox = gcnew Box;

// Initialize with default Box

Console::WriteLine(L”Default box volume is {0}”, aBox->Volume());

Console::WriteLine(L”New box volume is {0}”, newBox->Volume());

return 0;

 

}

 

The output from this example is:

 

Constructor called.

 

No-arg constructor called.

 

Default box volume is 1

 

New box volume is 3000

 

Press any key to continue . . .

 

How It Works

The first statement in main() creates a handle to a Box object.

Box^ aBox; // Handle of type Box^

No object is created by this statement, just the tracking handle, aBox. The aBox variable is initialized by default with nullptr so it does not point to anything yet. In contrast, a variable of a value class type always contains an object.

The next statement creates a handle to a new Box object.

Box^ newBox = gcnew Box(10, 15, 20);

380

Defining Your Own Data Types

The constructor accepting three arguments is called to create the Box object on the heap and the address is stored in the handle, newBox. As you know, objects of ref class types are always created on the CLR heap and are always referenced using a handle.

You create a Box object by calling the no-arg constructor and store its address in aBox.

aBox = gcnew Box;

// Initialize with default Box

This object has the Length, Width, and Height fields set to 1.0.

Finally you output the volumes of the two Box objects you have created.

Console::WriteLine(L”Default box volume is {0}”, aBox->Volume());

Console::WriteLine(L”New box volume is {0}”, newBox->Volume());

Because aBox and newBox are handles, you use the -> operator to call the Volume() function for the objects to which they refer.

Class Properties

A property is a member of either a value class or a reference class that you access as though it were a field, but it really isn’t a field. The primary difference between a property and a field is that the name of a field refers to a storage location whereas the name of a property does not — it calls a function. A property has get() and set() accessor functions to retrieve and set its value respectively so when you use a property name to obtain its value, behind the scenes you are calling the get() function for the property and when you use the name of a property on the right of an assignment statement you are calling its set() function. If a property only provides a definition for the get() function, it is called a read-only property because the set() function is not available to set the property value. A property may just have the set() function defined for it, in which case it is described as a write-only property.

A class can contain two different kinds of properties: scalar properties and indexed properties. Scalar properties are a single value accessed using the property name whereas indexed properties are a set of values that you access using an index between square brackets following the property name. The String class has the scalar property, Length, that provides you with the number of characters in a string, and for a String object str you access the Length property using the expression str->Length because str is a handle. Of course, to access a property with the name MyProp for a value class object store in the variable val, you would use the expression val.MyProp, just like accessing a field. The Length property for a string is an example of a read-only property because no set() function is defined for it — you cannot set the length of a string as a String object is immutable. The String class also gives you access to individual characters in the string as indexed properties. For a string handle, str, you can access the third indexed property with the expression str[2], which corresponds to the third character in the string.

Properties can be associated with, and specific to, a particular object, in which case the properties are described as instance properties. The Length property for a String object is an example of an instance property. You can also specify a property as static, in which case the property is associated with the class and the property value will be the same for all objects of the class type. Let’s explore properties in a little more depth.

381

Chapter 7

Defining Scalar Properties

As scalar property has a single value and you define a scalar property in a class using the property keyword. The get() function for a scalar property must have a return type that is the same as the property type and the set() function must have a parameter of the same type as the property. Here’s an example of a property in the Height value class that you saw earlier.

value class Height

{

private:

// Records the height in feet and inches int feet;

int inches;

literal int inchesPerFoot = 12;

literal double inchesToMeters = 2.54/100;

public:

//Create a height from inches value Height(int ins)

{

feet = ins / inchesPerFoot; inches = ins % inchesPerFoot;

}

//Create a height from feet and inches Height(int ft, int ins) : feet(ft), inches(ins){}

//The height in meters as a property

property double meters

{

//Returns the property value double get()

{

return inchesToMeters*(feet*inchesPerFoot+inches);

}

//You would define the set() function for the property here...

}

// Create a string representation of the object virtual String^ ToString() override

{

return feet + L” feet “+ inches + L” inches”;

}

};

The Height class now contains a property with the name meters. The definition of the get function for the property appears between the braces following the property name. You would put the set() function for the property here too, if there was one. Note that there is no semicolon following the braces that enclose the get() and set() function definitions for a property. The get() function for the meters property makes use of a new literal class member, inchesToMeters, to convert the height in inches to meters. Accessing the meters property for an object of type Height makes the height available in meters. Here’s how you could do that:

382

Defining Your Own Data Types

Height ht = Height(6, 8);

// Height of 6 feet 8 inches

Console::WriteLine(L”The height is {0} meters”, ht->meters);

The second statement outputs the value of ht in meters using the expression ht->meters.

You are not obliged to define the get() and set() functions for a property inline; you can define them outside the class definition in a .cpp file. For example, you could specify the meters property in the Height class like this:

value class Height

{

// Code as before...

public:

//Code as before...

//The height in meters property double meters

{

double get();

// Returns the property value

// You would define the set() function for the property here...

}

// Code as before...

};

The get() function for the meters property is now declared but not defined in the Height class, so a definition must be supplied outside the class definition. In the get() function definition in the

Height.cpp file the function name must be qualified by the class name and the property name so the definition looks like this:

Height::meters::get()

{

return inchesToMeters*(feet*inchesPerFoot+inches);

}

The Height qualifier indicates that this function definition belongs to the Height class and the meters qualifier indicates the function is for the meters property in the class.

Of course, you can define properties for a reference class. Here’s an example:

ref class Weight

{

private: int lbs; int oz;

public:

property int pounds

{

int get() { return lbs; }

void set(int value) { lbs = value; }

383

Chapter 7

}

property int ounces

{

int get() { return oz; }

void set(int value) { oz = value; }

}

};

Here the pounds and ounces properties are used to provide access to the values of the private fields, lbs and oz. You can set values the properties of a Weight object and access them subsequently like this:

Weight^ wt = gcnew Weight; wt->pounds = 162;

wt->ounces = 12;

Console::WriteLine(L”Weight is {0} lbs {1} oz.”, wt->pounds, wt->ounces);

A variable accessing a ref class object is always a handle so you must use the -> operator to access properties of an object of a reference class type.

Trivial Scalar Properties

You can define a scalar property for a class without providing definitions for the get() and set() functions, in which case it is called a trivial scalar property. To specify a trivial scalar property you just omit the braces containing the get() and set() function definitions and end the property declaration with a semi-colon. Here’s an example of a value class with trivial scalar properties:

value class Point

 

{

 

public:

 

property int x;

// Trivial property

property int y;

// Trivial property

virtual String^ ToString() override

 

{

 

return L”(“ + x + L”,” + y + L”)”;

// Returns “(x,y)”

}

 

};

 

 

 

Default get() and set() function definitions are supplied automatically for each trivial scalar property and these return the property value and set the property value to the argument of the type specified for the property. Private space is allocated to accommodate the property value behind the scenes.

Let’s see some scalar properties working.

Try It Out

Using Scalar Properties

This example uses three classes, two value classes, and a ref class:

//Ex7_16.cpp : main project file.

//Using scalar properties

384

Defining Your Own Data Types

#include “stdafx.h”

using namespace System;

// Class defining a person’s height value class Height

{

private:

// Records the height in feet and inches int feet;

int inches;

literal int inchesPerFoot = 12;

literal double inchesToMeters = 2.54/100;

public:

//Create a height from inches value Height(int ins)

{

feet = ins / inchesPerFoot; inches = ins % inchesPerFoot;

}

//Create a height from feet and inches Height(int ft, int ins) : feet(ft), inches(ins){}

//The height in meters

property double meters

// Scalar property

{

 

//Returns the property value double get()

{

return inchesToMeters*(feet*inchesPerFoot+inches);

}

//You would define the set() function for the property here...

}

// Create a string representation of the object virtual String^ ToString() override

{

return feet + L” feet “+ inches + L” inches”;

}

};

// Class defining a person’s weight value class Weight

{

private:

int lbs; int oz;

literal int ouncesPerPound = 16;

385

Chapter 7

literal double lbsToKg = 1.0/2.2;

 

public:

 

 

Weight(int pounds, int ounces)

 

{

 

 

lbs = pounds;

 

 

oz = ounces;

 

 

}

 

 

property int pounds

 

// Scalar property

{

 

 

int get() { return lbs;

}

 

void set(int value) {

lbs = value;

}

}

 

 

property int ounces

 

// Scalar property

{

 

 

int get() { return oz;

}

 

void set(int value) {

oz = value;

}

}

 

 

property double kilograms

 

// Scalar property

{

 

 

double get() { return lbsToKg*(lbs + oz/ouncesPerPound); }

}

virtual String^ ToString() override

{ return lbs + L” pounds “ + oz + L” ounces”; }

};

// Class

defining a person

 

ref class Person

 

{

 

 

private:

 

 

Height ht;

 

Weight wt;

 

public:

 

 

property String^ Name;

// Trivial scalar property

Person(String^ name, Height h, Weight w) : ht(h), wt(w)

{

Name = name;

}

Height getHeight(){ return ht; } Weight getWeight(){ return wt; }

};

int main(array<System::String ^> ^args)

{

Weight hisWeight = Weight(185, 7);

Height hisHeight = Height(6, 3);

386

Defining Your Own Data Types

Person^ him = gcnew Person(L”Fred”, hisHeight, hisWeight);

Weight herWeight = Weight(105, 3);

Height herHeight = Height(5, 2);

Person^ her = gcnew Person(L”Freda”, herHeight, herWeight);

Console::WriteLine(L”She is {0}”, her->Name); Console::WriteLine(L”Her weight is {0:F2} kilograms.”,

her->getWeight().kilograms);

Console::WriteLine(L”Her height is {0} which is {1:F2} meters.”, her->getHeight(),her->getHeight().meters);

Console::WriteLine(L”He is {0}”, him->Name); Console::WriteLine(L”His weight is {0}.”, him->getWeight()); Console::WriteLine(L”His height is {0} which is {1:F2} meters.”,

him->getHeight(),him->getHeight().meters);

return 0;

}

This example produces the following output:

She is Freda

Her weight is 47.73 kilograms.

Her height is 5 feet 2 inches which is 1.57 meters.

He is Fred

His weight is 185 pounds 7 ounces.

His height is 6 feet 3 inches which is 1.91 meters.

Press any key to continue . . .

How It Works

The two value classes, Height and Weight, define the height and weight of a person. The Person class has fields of type Height and Weight to store the height and weight of a person and the name of a person is stored in the Name property, which is a trivial property because no explicit get() and set() functions have been defined for it; it therefore has the default get() and set() functions.

The first two statements in main() define Height and Weight objects and these are then used to define him:

Weight hisWeight = Weight(185, 7);

Height hisHeight = Height(6, 3);

Person^ him = gcnew Person(L”Fred”, hisHeight, hisWeight);

Height and Weight are value classes so the variables of these types store the values directly. Person is a reference class so him is a handle. The first argument to the Person class constructor is a string literal so the compiler arranges for a String object to be created from this and the handle to this object is passed as the argument. The second and third arguments are the value class objects that you create in the first two statements. Of course, copies of these are passed as arguments because of the pass-by-value mechanism for function arguments. Within the Person class constructor, the assignment statement sets the value of the Name parameter and the values of the two fields, ht and wt, are set in the initialization list. The only way to set a property is through an implicit call of its set() function; a property cannot be initialized in the initializer list of a constructor.

387

Chapter 7

There is a similar sequence of three statements to those for him that define her. With two Person objects created on the heap you first output information about her with these statements:

Console::WriteLine(L”She is {0}”, her->Name); Console::WriteLine(L”Her weight is {0:F2} kilograms.”,

her->getWeight().kilograms); Console::WriteLine(L”Her height is {0} which is {1:F2} meters.”,

her->getHeight(),her->getHeight().meters);

In the first statement, you access the Name property for the object referenced by the handle, her, with the expression her->Name; the result of executing this is a handle to the string that the property’s get() value returns, so it is of type String^.

In the second statement, you access the kilograms property of the wt field for the object referenced by her with the expression her->getWeight().kilograms. The her->getWeight() part of this expression returns a copy of the wt field, and this is used to access the kilograms property; thus the value returned by the get() function for the kilograms property becomes the value of the second argument to the WriteLine() function.

In the third output statement, the second argument is the result of the expression, her->getHeight(), which returns a copy of ht. To produce the value of this in a form suitable for output the compiler arranges for the ToString() function for the object to be called, so the expression is equivalent to her->getHeight().ToString(), and in fact you could write it like this if you want. The third argument to the WriteLine() function is the meters property for the Height object that is returned by the getHeight() function the the Person object, her.

The last three output statements output information about the him object in a similar fashion to the her object. In this case the weight is produced by an implicit call of the ToString() function for the wt field in the him object.

Defining Indexed Properties

Indexed properties are a set of property values in a class that you access using an index between square brackets, just like accessing an array element. You have already made use of indexed properties for strings because the String class makes characters from the string available as indexed properties. As you have seen, if str is the handle to a String object then the expression str[4] accesses the fifth indexed property value, which corresponds to the fifth character in the string. A property that you access by placing an index in square brackets following the name of the variable referencing the object, this is an example of a default indexed property. An indexed property that has a property name is described as a named indexed property.

Here’s a class containing a default indexed property:

ref class Name

 

{

 

private:

 

array<String^>^ Names;

// Stores names as array elements

public:

Name(...array<String^>^ names) Names(names) {}

// Indexed property to return any name

388