Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 122 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
122
Dung lượng
1,76 MB
Nội dung
Try It Out Using structs Now use another console application example to exercise a little further how referencing the members of a struct works. Suppose you want to write a program to deal with some of the things you might find in a yard, such as those illustrated in the professionally landscaped yard in Figure 7-1. Figure 7-1 I have arbitrarily assigned the coordinates 0,0 to the top-left corner of the yard. The bottom-right corner has the coordinates 100,120. Thus, the first coordinate value is a measure of the horizontal position rela- tive to the top-left corner, with values increasing from left to right, and the second coordinate is a mea- sure of the vertical position from the same reference point, with values increasing from top to bottom. House Pool Position 0,0 Position 100,120 Hut 70 30 10 25 25 70 90 110 40 80 10 30 Hut 326 Chapter 7 10_571974 ch07.qxp 1/20/06 11:44 PM Page 326 Figure 7-1 also shows the position of the pool and that of the two huts relative to the top-left corner of the yard. Because the yard, huts, and pool are all rectangular, you could define a struct type to repre- sent any of these objects: struct RECTANGLE { int Left; // Top left point int Top; // coordinate pair int Right; // Bottom right point int Bottom; // coordinate pair }; The first two members of the RECTANGLE structure type correspond to the coordinates of the top-left point of a rectangle, and the next two to the coordinates of the bottom-right point. You can use this in an elementary example dealing with the objects in the yard as follows: // Ex7_01.cpp // Exercising structures in the yard #include <iostream> using std::cout; using std::endl; // Definition of a struct to represent rectangles struct RECTANGLE { int Left; // Top left point int Top; // coordinate pair int Right; // Bottom right point int Bottom; // coordinate pair }; // Prototype of function to calculate the area of a rectangle long Area(RECTANGLE& aRect); // Prototype of a function to move a rectangle void MoveRect(RECTANGLE& aRect, int x, int y); int main(void) { RECTANGLE Yard = { 0, 0, 100, 120 }; RECTANGLE Pool = { 30, 40, 70, 80 }; RECTANGLE Hut1, Hut2; Hut1.Left = 70; Hut1.Top = 10; Hut1.Right = Hut1.Left + 25; Hut1.Bottom = 30; Hut2 = Hut1; // Define Hut2 the same as Hut1 MoveRect(Hut2, 10, 90); // Now move it to the right position cout << endl 327 Defining Your Own Data Types 10_571974 ch07.qxp 1/20/06 11:44 PM Page 327 << “Coordinates of Hut2 are “ << Hut2.Left << “,” << Hut2.Top << “ and “ << Hut2.Right << “,” << Hut2.Bottom; cout << endl << “The area of the yard is “ << Area(Yard); cout << endl << “The area of the pool is “ << Area(Pool) << endl; return 0; } // Function to calculate the area of a rectangle long Area(RECTANGLE& aRect) { return (aRect.Right - aRect.Left)*(aRect.Bottom - aRect.Top); } // Function to Move a Rectangle void MoveRect(RECTANGLE& aRect, int x, int y) { int length = aRect.Right - aRect.Left; // Get length of rectangle int width = aRect.Bottom - aRect.Top; // Get width of rectangle aRect.Left = x; // Set top left point aRect.Top = y; // to new position aRect.Right = x + length; // Get bottom right point as aRect.Bottom = y + width; // increment from new position return; } The output from this example is: Coordinates of Hut2 are 10,90 and 35,110 The area of the yard is 12000 The area of the pool is 1600 How It Works Note that the struct definition appears at global scope in this example. You’ll be able to see it in the Class View tab for the project. Putting the definition of the struct at global scope allows you to declare a variable of type RECTANGLE anywhere in the .cpp file. In a program with a more significant amount of code, such definitions would normally be stored in a .h file and then added to each .cpp file where nec- essary by using a #include directive. You have defined two functions to process RECTANGLE objects. The function Area() calculates the area of the RECTANGLE object that you pass as a reference argument as the product of the length and the width, where the length is the difference between the horizontal positions of the defining points, and the 328 Chapter 7 10_571974 ch07.qxp 1/20/06 11:44 PM Page 328 width is the difference between the vertical positions of the defining points. By passing a reference, the code runs a little faster because the argument is not copied. The MoveRect() function modifies the defining points of a RECTANGLE object to position it at the coordinates x, y which are passed as argu- ments. The position of a RECTANGLE object is assumed to be the position of the Left, Top point. Because the RECTANGLE object is passed as a reference, the function is able to modify the members of the RECT- ANGLE object directly. After calculating the length and width of the RECTANGLE object passed, the Left and Top members are set to x and y respectively, and the new Right and Bottom members are calcu- lated by incrementing x and y by the length and width of the original RECTANGLE object. In the main() function, you initialize the Yard and Pool RECTANGLE variables with their coordinate positions as shown in Figure 7-1. The variable Hut1 represents the hut at the top-right in the illustration and its members are set to the appropriate values using assignment statements. The variable Hut2, cor- responding to the hut at the bottom-left of the yard, is first set to be the same as Hut1 in the assignment statement Hut2 = Hut1; // Define Hut2 the same as Hut1 This statement results in the values of the members of Hut1 being copied to the corresponding members of Hut2. You can only assign a struct of a given type to another of the same type. You can’t increment a struct directly or use a struct in an arithmetic expression. To alter the position of Hut2 to its place at the bottom-left of the yard, you call the MoveRect() function with the coordinates of the required position as arguments. This roundabout way of getting the coordi- nates of Hut2 is totally unnecessary and serves only to show how you can use a struct as an argument to a function. Intellisense Assistance with Structures You’ve probably noticed that the editor in Visual C++ 2005 is quite intelligent — it knows the types of variables, for instance. If you hover the mouse cursor over a variable name in the editor window, it pop ups a little box showing its definition. It also can help a lot with structures (and classes, as you will see) because not only does it know the types of ordinary variables, it also knows the members that belong to a variable of a particular structure type. If your computer is reasonably fast, as you type the member selection operator following a structure variable name, the editor pops a window showing the list of members. If you click one of the members, it shows the comment that appeared in the original definition of the structure, so you know what it is. This is shown in Figure 7-2 using a fragment of the previous example. Figure 7-2 329 Defining Your Own Data Types 10_571974 ch07.qxp 1/20/06 11:44 PM Page 329 Now there’s a real incentive to add comments, and to keep them short and to the point. If you double- click on a member in the list or press the Enter key when the item is highlighted, it is automatically inserted after the member selection operator, thus eliminating one source of typos in your code. Great, isn’t it? You can turn any or all of the Intellisense features off if you want to via the Tools > Options menu item, but I guess the only reason you would want to is if your machine is too slow to make them useful. You can turn the statement-completion features on or off on the C/C++ editor page that you select in the right options pane. If you turn them off, you can still call them up when you want too, either through the Edit menu or through the keyboard. Pressing Ctrl+J, for example, pops up the members for an object under the cursor. The editor also shows the parameter list for a function when you are typing the code to call it —it pops up as soon as you enter the left parenthesis for the argument list. This is particu- larly helpful with library functions as its tough to remember the parameter list for all of them. Of course, the #include directive for the header file must already be there in the source code for this to work. Without it the editor has no idea what the library function is. You will see more things that the editor can help with as you learn more about classes. After that interesting little diversion, let’s get back to structures. The struct RECT Rectangles are used a great deal in Windows programs. For this reason, there is a RECT structure prede- fined in the header file windows.h. Its definition is essentially the same as the structure that you defined in the last example: struct RECT { int left; // Top left point int top; // coordinate pair int right; // Bottom right point int bottom; // coordinate pair }; This struct is usually used to define rectangular areas on your display for a variety of purposes. Because RECT is used so extensively, windows.h also contains prototypes for a number of functions to manipulate and modify rectangles. For example, windows.h provides the function InflateRect() to increase the size of a rectangle and the function EqualRect() to compare two rectangles. MFC also defines a class called CRect, which is the equivalent of a RECT structure. After you understand classes, you will be using this in preference to the RECT structure. The CRect class provides a very exten- sive range of functions for manipulating rectangles, and you will be using a number of these when you are writing Windows programs using MFC. Using Pointers with a struct As you might expect, you can create a pointer to a variable of a structure type. In fact, many of the func- tions declared in windows.h that work with RECT objects require pointers to a RECT as arguments 330 Chapter 7 10_571974 ch07.qxp 1/20/06 11:44 PM Page 330 because this avoids the copying of the whole structure when a RECT argument is passed to a function. To define a pointer to a RECT object for example, the declaration is what you might expect: RECT* pRect = NULL; // Define a pointer to RECT Assuming that you have defined a RECT object, aRect, you can set the pointer to the address of this vari- able in the normal way, using the address-of operator: pRect = &aRect; // Set pointer to the address of aRect As you saw when the idea of a struct was introduced, a struct can’t contain a member of the same type as the struct being defined, but it can contain a pointer to a struct, including a pointer to a struct of the same type. For example, you could define a structure like this: struct ListElement { RECT aRect; // RECT member of structure ListElement* pNext; // Pointer to a list element }; The first element of the ListElement structure is of type RECT, and the second element is a pointer to a structure of type ListElement —the same type as that being defined. (Remember that this element isn’t of type ListElement, it’s of type ‘pointer to ListElement’.) This allows object of type ListElement to be daisy-chained together, where each ListElement can contain the address of the next ListElement object in a chain, the last in the chain having the pointer as zero. This is illustrated in Figure 7-3. Figure 7-3 Each box in the diagram represents an object of type ListElement and the pNext member of each object stores the address of the next object in the chain, except for the last object where pNext is 0. This kind of arrangement is usually referred to as a linked list. It has the advantage that as long as you know the first element in the list, you can find all the others. This is particularly important when variables are created dynamically, since a linked list can be used to keep track of them all. Every time a new one is cre- ated, it’s simply added to the end of the list by storing its address in the pNext member of the last object in the chain. LE1 members: aRect pnext = &LE2 LE4 members: aRect pnext = &LE5 LE2 members: aRect pnext = &LE3 LE3 members: aRect pnext = &LE4 LE5 No next element members: aRect pnext = 0 331 Defining Your Own Data Types 10_571974 ch07.qxp 1/20/06 11:44 PM Page 331 Accessing Structure Members through a Pointer Consider the following statements: RECT aRect = { 0, 0, 100, 100 }; RECT* pRect = &aRect; The first declares and defines the aRect object to be of type RECT with the first pair of members initial- ized to (0, 0) and the second pair to (100, 100). The second statement declares pRect as a pointer to type RECT and initializes it with the address of aRect. You can now access the members of aRect through the pointer with a statement such as this: (*pRect).Top += 10; // Increment the Top member by 10 The parentheses to de-reference the pointer here are essential because the member access operator takes precedence over the de-referencing operator. Without the parentheses, you would be attempting to treat the pointer as a struct and to de-reference the member, so the statement would not compile. After exe- cuting this statement, the Top member will have the value 10 and, of course, the remaining members will be unchanged. The method that you used here to access the member of a struct through a pointer looks rather clumsy. Because this kind of operation crops up very frequently in C++, the language includes a special operator to enable you to express the same thing in a much more readable and intuitive form, so let’s look at that next. The Indirect Member Selection Operator The indirect member selection operator, ->, is specifically for accessing members of a struct through a pointer; this operator is also referred to as the indirect member access operator. The operator looks like a little arrow (->)and is formed from a minus sign (-) followed by the symbol for greater than (>) You could use it to rewrite the statement to access the Top member of aRect through the pointer pRect, as follows: pRect->Top += 10; // Increment the Top member by 10 This is much more expressive of what is going on, isn’t it? The indirect member selection operator is also used with classes, and you’ll be seeing a lot more of it throughout the rest of the book. Data Types, Objects, Classes and Instances Before I get into the language, syntax, and programming techniques of classes, I’ll start by considering how your existing knowledge relates to the concept of classes. So far, you’ve learned that native C++ lets you create variables that can be any of a range of fundamental data types: int, long, double and so on. You have also seen how you can use the struct keyword to define a structure that you could then use as the type for a variable representing a composite of several other variables. 332 Chapter 7 10_571974 ch07.qxp 1/20/06 11:44 PM Page 332 The variables of the fundamental types don’t allow you to model real-world objects (or even imaginary objects) adequately. It’s hard to model a box in terms of an int, for example; however, you can use the members of a struct to define a set of attributes for such an object. You could define variables, length, width, and height to represent the dimensions of the box and bind them together as members of a Box structure, as follows: struct Box { double length; double width; double height; }; With this definition of a new data type called Box, you define variables of this type just as you did with variables of the basic types. You can then create, manipulate, and destroy as many Box objects as you need to in your program. This means that you can model objects using structs and write your pro- grams around them. So—that’s object-oriented programming all wrapped up then? Well, not quite. You see, object-oriented programming (OOP) is based on a number of foundations (famously encapsulation, polymorphism, and inheritance) and what you have seen so far doesn’t quite fit the bill. Don’t worry about what these terms mean for the moment —you’ll be exploring that in the rest of this chapter and throughout the book. The notion of a struct in C++ goes far beyond the original concept of struct in C —it incorporates the object-oriented notion of a class. This idea of classes, from which you can create your own data types and use them just like the native types, is fundamental to C++, and the new keyword class was intro- duced into the language to describe this concept. The keywords struct and class are almost identical in C++, except for the access control to the members, which you will find out more about later in this chapter. The keyword struct is maintained for backwards compatibility with C, but everything that you can do with a struct and more, you can achieve with a class. Take a look at how you might define a class representing boxes: class CBox { public: double m_Length; double m_Width; double m_Height; }; When you define CBox as a class you are essentially defining a new data type, similar to when you defined the Box structure. The only differences here are the use of the keyword class instead of struct, and the use of the keyword public followed by a colon that precedes the definition of the members of the class. The variables that you define as part of the class are called data members of the class, because they are variables that store data. You have also called the class CBox instead of Box. You could have called the class Box, but the MFC adopts the convention of using the prefix C for all class names, so you might as well get used to it. MFC also prefixes data members of classes with m_ to distinguish them from other variables, so I’ll use this 333 Defining Your Own Data Types 10_571974 ch07.qxp 1/20/06 11:44 PM Page 333 convention too. Remember though that in other contexts where you might use C++ and in C++/CLI in particular, this will not be the case; in some instances the convention for naming classes and their mem- bers may be different, and in others there may be no particular convention adopted for naming entities. The public keyword is a clue as to the difference between a structure and a class. It just defines the members of the class as being generally accessible, in the same way as the members of a structure are. The members of a struct, however, are public by default. As you’ll see a little later in the chapter, though, it’s also possible to place restrictions on the accessibility of the class members. You can declare a variable, bigBox say, that represents an instance of the CBox class type like this: CBox bigBox; This is exactly the same as declaring a variable for a struct, or indeed for any other variable type. After you have defined the class CBox, declarations for variables of this type are quite standard. First Class The notion of class was invented by an Englishman to keep the general population happy. It derives from the theory that people who knew their place and function in society would be much more secure and comfortable in life than those who did not. The famous Dane, Bjarne Stroustrup, who invented C++, undoubtedly acquired a deep knowledge of class concepts while at Cambridge University in England and appropriated the idea very successfully for use in his new language. Class in C++ is similar to the English concept, in that each class usually has a very precise role and a per- mitted set of actions. However, it differs from the English idea because class in C++ has largely socialist overtones, concentrating on the importance of working classes. Indeed, in some ways it is the reverse of the English ideal, because, as you will see, working classes in C++ often live on the backs of classes that do nothing at all. Operations on Classes In C++ you can create new data types as classes to represent whatever kinds of objects you like. As you’ll come to see, classes (and structures) aren’t limited to just holding data; you can also define mem- ber functions or even operations that act between objects of your classes using the standard C++ opera- tors. You can define the class CBox, for example, so that the following statements work and have the meanings you want them to have: CBox box1; CBox box2; if(box1 > box2) // Fill the larger box box1.fill(); else box2.fill(); You could also implement operations as part of the CBox class for adding, subtracting or even multiply- ing boxes —in fact, almost any operation to which you could ascribe a sensible meaning in the context of boxes. 334 Chapter 7 10_571974 ch07.qxp 1/20/06 11:44 PM Page 334 I’m talking about incredibly powerful medicine here and it constitutes a major change in the approach that you can take to programming. Instead of breaking down a problem in terms of what are essentially computer-related data types (integer numbers, floating point numbers and so on) and then writing a program, you’re going to be programming in terms of problem-related data types, in other words classes. These classes might be named CEmployee, or CCowboy, or CCheese, or CChutney, each defined specifically for the kind of problem that you want to solve, complete with the functions and operators that are necessary to manipulate instances of your new types. Program design now starts with deciding what new application-specific data types you need to solve the problem in hand and writing the program in terms of operations on the specifics that the problem is con- cerned with, be it CCoffins or CCowpokes. Terminology I’ll first summarize some of the terminology that I will be using when discussing classes in C++: ❑ A class is a user-defined data type. ❑ Object-oriented programming (OOP) is the programming style based on the idea of defining your own data types as classes. ❑ Declaring an object of a class type is sometimes referred to as instantiation because you are cre- ating an instance of a class. ❑ Instances of a class are referred to as objects. ❑ The idea of an object containing the data implicit in its definition, together with the functions that operate on that data, is referred to as encapsulation. When I get into the detail of object-oriented programming, it may seem a little complicated in places, but getting back to the basics of what you’re doing can often help to make things clearer, so always keep in mind what objects are really about. They are about writing programs in terms of the objects that are spe- cific to the domain of your problem. All the facilities around classes in C++ are there to make this as comprehensive and flexible as possible. Let’s get down to the business of understanding classes. Understanding Classes A class is specification of a data type that you define. It can contain data elements that can either be vari- ables of the basic types in C++, or of other user-defined types. The data elements of a class may be single data elements, arrays, pointers, arrays of pointers of almost any kind, or objects of other classes, so you have a lot of flexibility in what you can include in your data type. A class also can contain functions that operate on objects of the class by accessing the data elements that they include. So, a class combines both the definition of the elementary data that makes up an object and the means of manipulating the data that belongs to individual objects of the class. The data and functions within a class are called members of the class. Humorously enough, the mem- bers of a class that are data items are called data members and the members that are functions are called function members or member functions. The member functions of a class are also sometimes referred to as methods; I will not use this term in this book but keep it in mind as you may see it used elsewhere. 335 Defining Your Own Data Types 10_571974 ch07.qxp 1/20/06 11:44 PM Page 335 [...]... 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... 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... 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 = 60 84 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... 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... Let’s extend our CBox class to incorporate a constructor // Ex7_ 04. cpp // Using a constructor #include using std::cout; using std::endl; class CBox { public: double m_Length; double m_Width; double m_Height; // Class definition at global scope // Length of a box in inches // Width of a box in inches // Height of a box in inches 343 Chapter 7 // Constructor definition CBox(double lv, double... Defining Your Own Data Types Both of the objects box1 and box2 will, of course, have their own data members This is illustrated in Figure 7 -4 box1 box2 m_Length m_Width m_Height m_Length m_Width m_Height 8 bytes 8 bytes 8 bytes 8 bytes 8 bytes 8 bytes Figure 7 -4 The object name box1 embodies the whole object, including its three data members They are not initialized to anything, however — the data... 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); CBox box2; CBox cigarBox(8.0, 5.0, 1.0); double boxVolume = 0.0; // Stores the volume of a box boxVolume = box1.Volume(); cout . of type CBox, called cigarBox, which also has initializing values. 344 Chapter 7 10_5719 74 ch07.qxp 1/20/06 11 :44 PM Page 344 The volume of box1 is calculated using the member function Volume(). which you could ascribe a sensible meaning in the context of boxes. 3 34 Chapter 7 10_5719 74 ch07.qxp 1/20/06 11 :44 PM Page 3 34 I’m talking about incredibly powerful medicine here and it constitutes. CBox 336 Chapter 7 10_5719 74 ch07.qxp 1/20/06 11 :44 PM Page 336 Both of the objects box1 and box2 will, of course, have their own data members. This is illustrated in Figure 7 -4. Figure 7 -4 The object name