Core C++ A Software Engineering Approach phần 5 pptx

120 320 0
Core C++ A Software Engineering Approach phần 5 pptx

Đ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

file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm The picture also shows that when the client code needs the values of cylinder fields (e.g., for computing cylinder volume, scaling, printing, or setting the field values), the client code uses member functions getVolume(), scaleCylinder(), and so on rather than accessing the values of fields radius and height. This is what the dashed line means. It shows that the direct access to data is ruled out. There are two motivations for barring access to data members. The first objective is to limit the extent of changes to the program when data design changes. If the interfaces of member functions stay the same (and usually it is not difficult to keep them the same when the data design changes), then it is member function implementations that have to change, not the client code. This is important for maintenance. The set of functions that have to change is well defined¡Xthey are all listed in the class definition, and there is no need to inspect the rest of the program for possible implications. The second reason for barring direct client access to data members is that the client code expressed in terms of calls to member functions is easier to understand than is the code expressed in terms of detailed computations over field values (provided that the responsibility is pushed to the member functions and they do the work for the client, not just retrieve and set the values of the fields, as is the case with the getHeight() and setHeight() functions). To achieve these advantages, everything inside the class should be private to the class, not accessible from outside the class, leaving only function interfaces public, accessible from the outside of the class. This would prevent the client code from creating dependencies on server class file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (481 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm data. Remember that the word dependency is the dirtiest word in programming. Dependencies between different parts of program code denote: ϒΠ the need for cooperation among programmers during program development ϒΠ the need to study and change more code during program maintenance than is necessary ϒΠ difficulties in reusing code in the same or similar project Meanwhile, the class design in Listing 9.2 does not enforce any protection against access to data. The client code can access the fields of the Cylinder object instances, developing dependencies on the Cylinder data design, and foregoing essential advantages of using classes. Cylinder c1, c2; // define program data c1.setCylinder(10,30); c2.setCylinder(20,30); // use access function c1.radius = 10; c1.height = 20; . . . // this is still ok! C++ allows the class designer to use fine control over access rights to class components. You can indicate access rights to each class component (data or function) by using the keywords public, private, and protected. Here is another version of class Cylinder. struct Cylinder { // start of class scope private: double radius, height; // data is private public: // operations are public void setCylinder(double r, double h); double getVolume(); // compute volume void scaleCylinder(double factor); void printCylinder(); // print object state } ; // end of class scope The keywords divide the class scope into segments. All data members or function members following the keyword, for example, private, have the same private access mode. In our example, data members radius and height are private, and all member functions are public. There might be any number of public, protected, and private segments in any order you want. In this example, I define the radius data member as private, then two member functions as public, then the height data member as private, then two more member functions as public. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (482 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm struct Cylinder { // start of class scope private: double radius; // data is private public: // operations are public void setCylinder(double r, double h); double getVolume(); private: double height; // data is private public: // operations are public void scaleCylinder(double factor); void printCylinder(); // print object state } ; // end of class scope This is a nice element of flexibility, but usually programmers group all class components with the same access rights in the same segment. In general, class members (either data members or member functions) in public segments are available to the rest of the program as in the previous examples. Class members (again, both data and functions) in private segments are available to the class member functions only (and to functions with access rights of a friend; I will discuss friends later, in Chapter 10, "Operator Functions: Another Good Idea." ). Using the name of a private class member outside of the class (or friend) scope is a syntax error. Notice that these rules do not prevent you from making data private and making functions public. However, in traditional C++ class design, data members are made private, and member functions are made public. Class members in protected segments are available to the class member functions and to member functions of classes that inherit from this one (directly or indirectly). Discussing inheritance now will take us too far from the topic of class syntax; I will do that later. Client functions (global functions or member functions of other classes) can access private class members only through the functions (if any) in the public part. Cylinder c1, c2; // define program data c1.setCylinder(10,30); c2.setCylinder(20,30); // use access function // c1.radius = 10; c1.height = 20; // this is now a syntax error if (c1.getVolume() < c2.getVolume()) // another access function c1.scaleCylinder(1.2); // scale it up file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (483 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm It is the duty of the class designer to provide necessary access to its data to support class clients and to avoid excessive access. If the client code uses the class feature it does not have to use, it develops extra dependencies. Should this feature change, the client code is affected as well. Also, the more features of the class that are made public, the more knowledge the client programmer and maintainer have to acquire to use the class instances productively. With the use of private access to class data members, the implementation details of the class Cylinder are now hidden; if the names or types of Cylinder fields change, the client code is not affected as long as the Cylinder class interface remains the same. The client code is prevented from developing dependencies on class Cylinder data design. The client programmer (and maintainer) is excused from the need to learn class Cylinder data design. Usually, it is the data part that is likely to evolve. This is why, in a typical class, data members are private and member functions are public. This enhances modifiability of the program and reusability of class design. Notice that class member functions (whether public or private) can access any data member of the same class, whether public or private. This is why any group of functions that accesses the same set of data should be bound together as class member functions, and calls to these functions should be used as messages to class instances in the client code. This enhances reusability. The class is isolated from other parts of the program. Its private parts are outside the reach of other code (similar to local variables in a function or a block). This property decreases the amount of coordination among design team members and reduces the likelihood of human miscommunication. This enhances program quality. In all previous examples, I used the keyword struct to define a C++ class. C++ also allows you to use the keyword class for that purpose. Here is an example of class Cylinder that uses the keyword class rather than struct. class Cylinder { // change from 'struct' to 'class' keyword private: double radius, height; // data is still private public: // operations are public void setCylinder(double r, double h); double getVolume(); void scaleCylinder(double factor); void printCylinder(); } ; // end of class scope file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (484 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm What is the difference between this class definition and the previous class definition? There is none. This class specification defines exactly the same class. The objects of these classes are exactly the same¡Xthere is no difference at all. There are only two differences between the keywords struct and class in C++. One difference is that the keyword struct has only one meaning in C++: It is used for one purpose only (to introduce a programmer-defined type into the program the way I did in the previous examples). Another difference between the keywords struct and class in C++ is in default access rights. In struct (and in union,) default access is public. In class, default access is private. That is all. Using default access rights allows you to structure the sequence of data fields and member functions differently. In the next version I am responding to the criticism of some programmers who say that class examples that describe data rather than functions first (as I did in previous examples) are hypocritical. The purpose of the class construct is to hide data design from the client code, and it is not a good idea to open the class specification with the description of the so-called "hidden" data. The client code uses public member functions; hence, it is appropriate if they are listed first in the class specification. struct Cylinder { // some prefer to list public members first void setCylinder(double r, double h); // operations are public double getVolume(); void scaleCylinder(double factor); void printCylinder(); private: double radius, height; // data is private } ; // end of class scope Others feel that understanding data is important for understanding what member functions do. Hence, there is nothing wrong with describing data first. After all, data "hiding" is not about military-type classified information or KGB-like secrecy, where information should be prevented from being known. In programming, information hiding and encapsulation is about preventing the client code from using the information in the client design, not about knowing this information. In this case, if you want to use default access rights, the class keyword is better than struct. class Cylinder { // some prefer to list data first double radius, height; // data is still private public: // operations are public void setCylinder(double r, double h); file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (485 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm double getVolume(); void scaleCylinder(double factor); void printCylinder(); } ; // end of class scope Some programmers say that the keyword struct is inferior to the keyword class, because if you define the class using the default access rights, data will not be protected against use by the client code, and that will defeat encapsulation. struct Cylinder { // default access rights are used double radius, height; // data is not protected from client access void setCylinder(double r, double h); // methods are public double getVolume(); void scaleCylinder(double factor); void printCylinder(); } ; // end of class scope Yes, this class design does defeat encapsulation. But hey, this does not prove that the keyword struct is inferior to the keyword class. If you replace struct with class in this design, the result will be even worse than with the keyword struct. Do you see why? class Cylinder { // default access rights are used double radius, height; // data is protected from client access void setCylinder(double r, double h); // methods are not accessible double getVolume(); void scaleCylinder(double factor); void printCylinder(); } ; // end of class scope This class is not usable at all. Yes, the data fields are now private (and this is fine), but so are member functions, and the client code cannot access them. This is not a very good design. It is probably better not to rely on defaults and instead specify access rights explicitly. Let us call a spade a spade. Initialization of Object Instances file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (486 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm When the compiler processes a definition of a variable, it uses its type definition to allocate the required amount of memory, either from the heap (for static and extern variables or for dynamic variables) or from the stack (for local automatic variables). This is true for simple variables, arrays, structures, and classes with member functions. If the code later assigns a value to a variable, the variable does not need initialization at its definition. If the algorithm uses the variable as an rvalue, it needs initial values for its data members. Cylinder c1; // data members are not initialized double vol = c1.getVolume(); // no, this is no good This coding pattern, however, might be appropriate if some default values could be used in computations. However, C++ initializes only static and global variables. (Default values are zeros of a suitable type.) Dynamic variables and automatic variables are left without initial values. Sometimes you would like to specify default initial values. It would be nice to initialize data members at their definition, similar to regular variables, but in C++, a data member definition cannot contain an initializer. class Cylinder { double radius = 100, height = 0; ¡K // no, this is illegal in C++ The class could provide a member function for the client code to call to specify the object's initial state. class Cylinder { double radius, height; public: void setCylinder(double r, double h); ¡K } ; Using this function, client code would send the setCylinder() message to Cylinder objects. Cylinder c1; c1.SetCylinder(100.0,0.0); // set radius to 100, height to zero file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (487 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm This is, of course, overkill. This code allows you to specify any initial values rather than specified default values. This is where constructors become useful. Constructors as Member Functions Class objects can be initialized implicitly, using a constructor. A constructor is a class member function, but its syntax is more rigid than it is for other member functions. It cannot have an arbitrary name; you should give the constructor function the same name as the class. The constructor interface cannot specify a return type, not even void. It cannot return values even if it contains a return statement. class Cylinder { double radius, height; public: Cylinder () // same name as class, no return type { radius=1.0; height=0.0; } // no return statement ¡K } ; When client code creates an object, the default constructor is called. Cylinder c1; // default constructor: no parameters It is called a default constructor because it has no parameters. I know, this is a strange reason, but that's the way it is. A constructor cannot be called explicitly at will, as can any other member function. c1.Cylinder(); // syntax error: no explicit calls to constructors The constructor can be called only when an object is created, not later. The compiler generates code that implicitly calls the constructor immediately after the instance of the object is created. This is why constructors are usually placed in the public section of the class specification. Otherwise, an attempt to create a class instance would generate an error as does any access to a private class member. file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (488 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm In general, an instance of the object can be created in the following ways: ϒΠ at the beginning of the program (extern and static objects) ϒΠ at entry into the scope with the object definition (automatic objects) ϒΠ when a variable is passed to a function (or returned from a function) by value ϒΠ when a variable is created dynamically using the operator new (but not malloc) Now you see why constructors have no return values: Constructors are called implicitly by the code generated by the compiler, and there is nobody around to use this return value. As member functions, constructors can have parameters; hence, constructors can be overloaded. If necessary, constructor parameters can have default values. When a class has more than one constructor, each one could be called when an object is created. Which constructor is called depends on the context, that is, on a set of arguments supplied by the client code at the time of object creation (the number and type of arguments). Supplying constructors in the class means providing services to class clients: Client programmers do not have to call initializing functions explicitly anymore. However, they should worry about supplying the arguments for the constructor. Here is an example of a constructor with two parameters. class Cylinder { double radius, height; // initialized in constructors public: Cylinder(double r, double h); // member function prototype void setCylinder(double r, double h); . . . . . } ; Cylinder::Cylinder(double r, double h) // scope operator { radius = r; height = h; } Notice the name of the constructor implemented outside the class boundaries. The first Cylinder denotes the class to which the member function belongs. The second Cylinder denotes the name of the member function (the same as the name of the class). Constructors that have fewer than two parameters have special names. (You will see them shortly.) Constructors with two or more parameters do not have special names¡Xthey are just general constructors. This constructor with two parameters does the same thing as setCylinder(): It sets the values of file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (489 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm data members to the values of the arguments supplied by the client. The difference is that setCylinder() can be called many times for the same object instance in the client code. The constructor is called only once¡Xwhen the object is created. Here are a few examples of constructor invocations in client code. These different syntactic forms call the same general constructor with two parameters. Notice that an assignment operator in the second statement does not mean that an assignment operation is performed. Despite the appearance of using an assignment, this is not what you think (quite a common situation in C++. Remember that story about the crocodile?): There is no assignment there. It is just a different syntactic form for a constructor call. Cylinder c1(3.0,5.0); // a constructor call for a named object Cylinder c2 = Cylinder(3,5); // it is still a constructor call Cylinder *r = new Cylinder(3.0,5.0); // unnamed object Notice the syntax for a variable with arguments. This is a new syntax. One of the implicit ambitions of C++ language design is the uniform treatment of variables of built-in and programmer-defined types. For built-in types, we used the assignment operator for initialization. With the advent of programmer-defined types, you can use the syntax with arguments for variables of built-in types as well as class objects. int x1(20); // same as int x1=20 When an object is allocated with a call to malloc(), no constructor is called. Hence, the client code has to initialize class objects explicitly. Cylinder *p = (Cylinder*)malloc(sizeof(Cylinder)); // no constructor call p->setCylinder(3,5); // object fields are assigned values A call to malloc() is the only way in C++ to create an object without a constructor call. Creation of all other objects, named objects and dynamic objects, is followed by a call to a constructor. Without any fanfare, we crossed a point of no return. From now on, there will be no situation where you just create an object instance and give it a chunk of memory. Any creation of an object will be accompanied by a function call¡Xa call to a constructor. Again, this requires a change in thinking. Every time you see an object instance created (remember that story about the brick?), you should file://///Administrator/General%20English%20Learning/it2002-7-6/core.htm (490 of 1187) [8/17/2002 2:57:55 PM] Simpo PDF Merge and Split Unregistered Version - http://www.simpopdf.com [...]... is called, a copy of the actual argument is made¡Xthe space for a Cylinder variable is allocated and is initialized by the values of the fields of the actual argument But wait a minute! There is no such thing as creation of an object in C++ without a constructor call! "The space for a Cylinder variable is allocated and is initialized by the values of the fields of the actual argument" means that the... private data that should not be accessible to client code But hey, who says that information hiding is about not changing private data? A call to setPoint() does change private data, and it does not break information hiding Neither does a call to getRef() Encapsulation and information hiding are about avoiding dependencies between classes, not about avoiding changes to private data members From a software. .. sequences as memory allocation and deallocation, file opening and closing, and so on Let us consider an example of a class where the destructor could be useful Class Name accommodates a string of characters that contains a person's name The constructor initializes an array of characters (It is a conversion constructor since it has one parameter of a type that is different from Name.) For simplicity's sake,... constructor is called If Cylinder is a simple C structure, it is a syntax error: You are told that you made a mistake and you have a chance to think about it and decide what you want to do If Cylinder is a C++ class without a conversion constructor, it is a syntax error: Again, you do not have to run the program and analyze the program output to know about that If Cylinder is a C++ class with a conversion... global variable // public members are visible // public members are visible // Cglobal, y, foo() are visible here This is legal C++, but it is not a good programming style Since a local variable can be defined anywhere in a block (a function block or an unnamed block), it is not accessible in the code in this block that precedes this variable If a local name is the same as a global name, the local name... global scope operator :: (for global names) and by the class scope operator (for the class scope name) file://///Administrator/General%20English%20Learning/it2002-7-6 /core. htm (50 6 of 1187) [8/17/2002 2 :57 :56 PM] file://///Administrator/General%20English%20Learning/it2002-7-6 /core. htm In the next example, the name radius is used for a global variable, for the Cylinder data member, Simpo and as a localPDF... their initial values Hence, you are able to do that for object variables int x; Cylinder c1; // noninitialized variables The syntax is the same, but the meaning is different The definition of a variable of a built-in type just allocates memory for this variable The definition of a variable of a programmer-defined class allocates memory for this variable and then calls the default constructor If class does... line of main() Dynamic variables are allocated and deallocated explicitly Usually, the calls to operators new and delete or to functions malloc() and free() do not happen in the same function (scope) Often, a dynamic variable is allocated in one function, attached to a dynamic structure (stack, queue, linked list, etc.), and then deallocated in another function (These functions should probably belong... global name If a name defined in the class scope is the same as a local name defined in one of its member functions, then the local name is used within that member function, and the name defined in the class scope is used in other member functions In short, a local scope name can hide both class scope names and global names; a class scope name can hide a global name These rules of name hiding can be... could initialize to default values Similarly, one might want to initialize a nonclass variable of a built-in type with another variable of the same type C++ supports a similar syntax that allows the client code to initialize one class object with the values of another object of the same class int x(20); Cylinder c1 (50 ,70); int y=x; Cylinder c2=c1; // objects are created, initialized // initialization from . are available to the rest of the program as in the previous examples. Class members (again, both data and functions) in private segments are available to the class member functions only (and. stack (for local automatic variables). This is true for simple variables, arrays, structures, and classes with member functions. If the code later assigns a value to a variable, the variable does. programmer (and maintainer) is excused from the need to learn class Cylinder data design. Usually, it is the data part that is likely to evolve. This is why, in a typical class, data members are

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

Mục lục

  • Part I: Introduction to Programming with C++

    • Chapter 1. Object-oriented approach: What's So Good About It?

    • Chapter 2. Getting Started Quickly: A Brief Overview of C++

    • Chapter 3. Working with C++ Data and Expressions

    • Chapter 5. Aggregation with Programmer-Defined Data Types

    • Chapter 6. Memory Management: the Stack and the Heap

    • Part II: Object-oriented programing with C++

      • Chapter 7. Programming With C++ Functions

      • Chapter 8. Object-Oriented Programming with Functions

      • Chapter 9. C++ Class as a Unit of Modularization

      • Chapter 10. Operator Functions: Another Good idea

      • Chapter 11. Constructors and Destructors: Potential Trouble

      • Part III: Object-Oriented Programming with Aggregation and Inheritance

        • Chapter 12. Composite Classes: Pitfalls and Advantages

        • Chapter 13. Similar Classes: How to Treat Them

        • Chapter 14. Choosing between Inheritance and Composition

        • Part IV: Advanced uses of C++

          • Chapter 15. Virtual Functions and other Advanced Uses of Inheritance

          • Chapter 16. Advanced Uses of Operator Overloading

          • Chapter 17. Templates: Yet Another Design Tool

          • Chapter 19. What We Have Learned

Tài liệu cùng người dùng

Tài liệu liên quan