1. Trang chủ
  2. » Công Nghệ Thông Tin

Ivor Horton’s BeginningVisual C++ 2008 phần 4 pdf

139 338 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 139
Dung lượng 1,31 MB

Nội dung

Value class objects are always copied by just copying fields and the assignment of one value class object to another is done in the same way. Value classes are intended to be used to represent simple objects defined by a limited amount of data, so for objects that don’t fit this specification or where the value class restrictions are problematical you should use ref class types to represent them. Let’s take the Height class for a test drive. Try It Out Defining and Using a Value Class Type Here’s the code to exercise the Height value class: // Ex7_14.cpp : main project file. // Defining and using a value class type #include “stdafx.h” using namespace System; // Class representing a height value class Height { private: // Records the height in feet and inches int feet; int inches; public: // Create a height from inches value Height(int ins) { feet = ins/12; inches = ins%12; } // Create a height from feet and inches Height(int ft, int ins) : feet(ft), inches(ins){} }; int main(array<System::String ^> ^args) { Height myHeight = Height(6,3); Height^ yourHeight = Height(70); Height hisHeight = *yourHeight; Console::WriteLine(L”My height is {0}”, myHeight); Console::WriteLine(L”Your height is {0}”, yourHeight); Console::WriteLine(L”His height is {0}”, hisHeight); return 0; } Executing this program results in the following output: My height is Height Your height is Height His height is Height 383 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 383 How It Works Well, the output is a bit monotonous and perhaps less than we were hoping for, but let’s come back to that a little later. In the main() function, you create three variables with the following statement: Height myHeight = Height(6,3); Height^ yourHeight = Height(70); Height hisHeight = *yourHeight; The first variable is of type Height so the object that represents a height of 6 feet 3 inches is allocated on the stack. The second variable is a handle of type Height^ so the object representing a height of 5 feet 10 inches is created on the CLR heap. The third variable is another stack variable that is a copy of the object referenced by yourHeight. Because yourHeight is a handle, you have to dereference it to assign it to the hisHeight variable and the result is that hisHeight contains a duplicate of the object referenced by yourHeight. Variables of a value class type always contain a unique object so two such variables cannot reference the same object; assigning one variable of a value class type to another always involves copying. Of course, several handles can reference a single object and assigning the value of one handle to another simply copies the address (or nullptr) from one to the other so that both objects reference the same object. The output is produced by the three calls of the Console::WriteLine() function. Unfortunately the out- put is not the values of the value class objects, but simply the class name. So how did this come about? It was optimistic to expect the values to be produced — after all, how is the compiler to know how they should be presented? Height objects contain two values — which one should be presented as the value? The class has to have a way to make the value available in this context. The ToString() Function in a Class Every C++/CLI class that you define has a ToString() function — I’ll explain how this comes about in the next chapter when I discuss class inheritance — that returns a handle to a string that is supposed to represent the class object. The compiler arranges for the ToString() function for an object to be called whenever it recognizes that a string representation of an object is required and you can call it explicitly if necessary. For example, you could write this: double pi = 3.142; Console::WriteLine(pi.ToString()); This outputs the value of pi as a string and it is the ToString() function that is defined in the System::Double class that provides the string. Of course, you would get the same output without explicitly calling the ToString() function. The default version of the ToString() function that you get in the Height class just outputs the class name because there is no way to know ahead of time what value should be returned as a string for an object of your class type. To get an appropriate value output by the Console::WriteLine() function in the previous example, you must add a ToString() function to the Height class that presents the value of an object in the form that you want. Here’s how the class looks with a ToString() function: // Class representing a height value class Height 384 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 384 { private: // Records the height in feet and inches int feet; int inches; public: // Create a height from inches value Height(int ins) { feet = ins/12; inches = ins%12; } // Create a height from feet and inches Height(int ft, int ins) : feet(ft), inches(ins){} // Create a string representation of the object virtual String^ ToString() override { return feet + L” feet “+ inches + L” inches”; } }; The combination of the virtual keyword before the return type for ToString() and the override key- word following the parameter list for the function indicates that this version of the ToString() function overrides the version of the function that is present in the class by default. You’ll hear a lot more about this in Chapter 8. Our new version of the ToString() function now outputs a string expressing a height in feet and inches. If you add this function to the class definition in the previous example, you get the fol- lowing output when you compile and execute the program: My height is 6 feet 3 inches Your height is 5 feet 10 inches His height is 5 feet 10 inches This is more like what you were expecting to get before. You can see from the output that the WriteLine() function quite happily deals with an object on the CLR heap that you reference through the yourHeight handle, as well as the myHeight and hisHeight objects that were created on the stack. Literal Fields The factor 12 that you use to convert from feet to inches and vice versa is a little troubling. It is an example of what is called a “magic number,” where a person reading the code has to guess or deduce its signifi- cance and origin. In this case it’s fairly obvious what the 12 is but there will be many instances where the origin of a numerical constant in a calculation is not so apparent. C++/CLI has a literal field facility for introducing named constants into a class that will solve the problem in this case. Here’s how you can eliminate the magic number from the code in the single-argument constructor in the Height class: value class Height { private: // Records the height in feet and inches int feet; int inches; 385 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 385 literal int inchesPerFoot = 12; 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){} // Create a string representation of the object virtual String^ ToString() override { return feet + L” feet “+ inches + L” inches”; } }; Now the constructor uses the name inchesPerFoot instead of 12, so there is no doubt as to what is going on. You can define the value of a literal field in terms of other literal fields as long as the names of the fields you are using to specify the value are defined first. For example: value class Height { // Other code literal int inchesPerFoot = 12; literal double millimetersPerInch = 25.4; literal double millimetersPerFoot = inchesPerFoot*millimetersPerInch; // Other code }; Here you define the value for the literal field millimetersPerFoot as the product of the other two literal fields. If you were to move the definition of the millimetersPerFoot field so that it precedes either or both of the other two, the code would not compile. A literal field can only be initialized with a value that is a constant integral value, an enum value, or a string; this obviously limits the possible types for a literal field to those you can initialize in this way. Defining Reference Class Types A reference class is comparable to a native C++ class in capabilities and does not have the restrictions that a value class has. Unlike a native C++ class, however, a reference class does not have a default copy constructor or a default assignment operator. If your class needs to support either of these operators, you must explicitly add a function for the capability — you’ll see how in the next chapter. 386 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 386 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 ful- fill 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 { 387 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 387 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 }; 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 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. 388 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 388 The next statement creates a handle to a new Box object. Box^ newBox = gcnew Box(10, 15, 20); 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 referenced generally 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. Defining a Copy Constructor for a Reference Class Type You are unlikely to be doing it very often but if you do pass objects of a reference class type to a func- tion by value, you must implement a public copy constructor; this situation can arise with the Standard Template Library implementation for the CLR, which you will learn about in Chapter 10. The parame- ter to the copy constructor must be a const reference, so you would define the copy constructor for the Box class like this: Box(const Box% box) : Length(box.Length), Width(box.Width), Height(box.Height) {} In general, the form of the copy constructor for a ref class T that allows a ref type object of type T to be passed by value to a function is: T(const T% t) { if(*this != t) { // Code to make the copy } return *this; } 389 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 389 Occasionally you may also need to implement a copy constructor that takes an argument that is a handle. Here’s how you could do that for the Box class: Box(const Box^ box) : Length(box->Length), Width(box->Width), Height(box->Height) {} As you can see, there is little difference between this and the previous version. 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 prop- erty 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 individ- ual 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. Defining Scalar Properties As scalar property has a single value and you define a scalar property in a class using the property key- word. 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; 390 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 390 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() func- tion for the property here too, if there were 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 prop- erty 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: 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 { 391 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 391 // 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; } } property int ounces { int get() { return oz; } void set(int value) { oz = value; } } }; 392 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 392 [...]... implement a peek() function to do this 9 Repeat Ex7 -4 but as a CLR console program using ref classes 40 7 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 40 8 25905c08.qxd:WroxPro 2/21/08 8:53 AM Page 40 9 8 More on Classes In this chapter, you will extend your knowledge of classes by understanding how you can make your class objects work more like the basic types in C++ You will learn: ❑ What a class destructor... members of a C++/ CLI class are referred to as fields ❑ Objects of a class are created and initialized using functions called constructors These are automatically called when an object declaration is encountered Constructors may be overloaded to provide different ways of initializing an object 40 5 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 40 6 Chapter 7: Defining Your Own Data Types ❑ Classes in a C++/ CLI... L”am”) return L”9:00”; else return L” 14: 30”; break; case Day::Sunday: return L”closed”; break; // Opening times // Saturday opening: // morning is 9 am // afternoon is 2:30 pm // Saturday opening: // closed all day 40 1 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 40 2 Chapter 7: Defining Your Own Data Types default: if(AmOrPm == L”am”) return L”9:30”; else return L” 14: 00”; break; // Monday to Friday opening:... dynamically will cause problems So, out of this, you have an absolutely 100 percent, 24 carat golden rule: If you allocate space for a member of a native C++ class dynamically, always implement a copy constructor Sharing Memor y Between Variables As a relic of the days when 64KB was quite a lot of memory, you have a facility in C++ that allows more than one variable to share the same memory (but obviously... 8:51 AM Page 40 7 Chapter 7: Defining Your Own Data Types 3 Create a function which takes a pointer to an object of type Sample as an argument, and which outputs the values of the members of any object Sample that is passed to it Test this function by extending the program that you created for the previous exercise 4 Define a class CRecord with two private data members that store a name up to 14 characters... (‘pushing’) or removing (‘popping’) items only from one end and works on a last-in, first-out principle For example, if the stack contained [10 4 16 20], pop() would return 10, and the stack would then contain [4 16 20]; a subsequent push(13) would leave the stack as [13 4 16 20] You can’t get at an item that is not at the top without first popping the ones above it Your class should implement push() and pop()... initialized at compile time where literal fields could do the job anyway; however, you have another way to initialize static initonly fields at runtime — through a static constructor 40 4 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 40 5 Chapter 7: Defining Your Own Data Types Static Constructors A static constructor is a constructor that you declare using the static keyword and that you use to initialize static... a native C++ class in the free store and how to delete them when they are no longer required ❑ When you must write a copy constructor for a class ❑ What a union is and how it can be used ❑ How to make objects of your class work with C++ operators such as + or * ❑ What class templates are and how you define and use them ❑ How to use the standard string class for string operations in native C++ programs... called Constructor called Constructor called Constructor called Constructor called Volume of cigar is 40 Volume of boxes[2] is 1.21 Destructor called Destructor called Destructor called Destructor called Destructor called Destructor called Destructor called 41 1 25905c08.qxd:WroxPro 2/21/08 8:53 AM Page 41 2 Chapter 8: More on Classes You get one call of the destructor at the end of the program for each... with a little example: // Ex8_02.cpp // Using a destructor to free memory #include // For stream I/O #include // For strlen() and strcpy() using std::cout; 41 3 25905c08.qxd:WroxPro 2/21/08 8:53 AM Page 41 4 Chapter 8: More on Classes using std::endl; // Put the CMessage class definition here (Listing 08_01) // Put the destructor definition here (Listing 08_02) int main() { // Declare . function: // Class representing a height value class Height 3 84 Chapter 7: Defining Your Own Data Types 25905c07.qxd:WroxPro 2/21/08 8:51 AM Page 3 84 { private: // Records the height in feet and inches int. Types A reference class is comparable to a native C++ class in capabilities and does not have the restrictions that a value class has. Unlike a native C++ class, however, a reference class does not. 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

Ngày đăng: 12/08/2014, 19:20