Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 44 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
44
Dung lượng
1,02 MB
Nội dung
28 568523 Ch22.qxd 4/5/04 2:03 PM Page 292 292 Part IV: Inheritance The programmer can divide a single program into separate files known as modules. These individual source files are compiled separately and then com- bined during the build process to generate a single program. Modules can then be allocated to separate groups known as namespaces. The process of combining separately compiled modules into a single exe- cutable is called linking. There are a number of reasons to divide programs into more manageable pieces. First, dividing a program into modules results in a higher level of encapsulation. Classes wall off their internal members in order to provide a certain degree of safety. Programs can wall off functions to do the same thing. Encapsulation is one of the advantages of object-oriented programming. Second, it is easier to comprehend and, therefore, easier to write and debug a program that consists of a number of well-thought-out modules than a single source file full of all of the classes and functions that the program uses. Next comes reuse. I used the reuse argument to help sell object-based pro- gramming. It is extremely difficult to keep track of a single class reused among multiple programs when a separate copy of the class is kept in each program. It is much better if a single class module is automatically shared among programs. Finally, there is the argument of time. A compiler such as Visual C++ or Dev- C++ doesn’t need very long to build the examples contained in this book using a high-speed computer like yours. Commercial programs sometimes consist of millions of source lines of code. Rebuilding a program of that size can take more than 24 hours. A programmer would not tolerate rebuilding a program like that for every single change. However, the majority of the time is spent compiling source files that haven’t changed. It is much faster to recompile just those modules that have changed and then quickly link all modules together. Separate namespaces allow a further level of encapsulation. A namespace should consist of a set of modules that perform a single capability. For example, all of the mathematical functions might be combined into a Math namespace. This lesson builds a simplistic program, called SeparateModules, that con- sists of a Student class, a GraduateStudent subclass, and a main() module to test both. Dividing the program — Student You begin by deciding what the logical divisions of SeparateModules should be. First, you notice that Student is an entity of its own. It does not depend 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 293 Chapter 22: Factoring Classes 293 on any other functions (besides C++ functions). Thus, it would make sense to put Student in a module by itself. Because the class will be used in several places, you break the declaration into a student.h file and a separate imple- mentation file, Student.cpp. By convention, the include file carries the name of the primary class it defines, but in lowercase letters. Ideally, the include file defines only one class. This allows the user program to include just the files that it needs. Historically, all include files carried the extension .h. This was changed in the current C++ standard. System include files such as iostream now have no extension at all. However, many programmers stick with the .h convention for include files they write. This allows such include files to be easily differen- tiated by the reader of the program. The resulting student.h file appears as follows: // Student - basic student #ifndef _STUDENT_ #define _STUDENT_ namespace Schools { class Student { public: Student(char* pszName, int nID); virtual char* display(); protected: // student’s name char* pszName; int nID; }; } #endif The #ifndef is a preprocessor control much like #include. #ifndef _ STUDENT_ says to include only the following lines if the argument _STUDENT_ is defined. The first time that student.h is included, _STUDENT_ is not defined. However, the #define immediately following the #ifndef then defines it. This has the effect that student.h is processed only once, no matter how many times it is included in a given file. Defining a namespace The second feature of the Student class is the creation of the Schools namespace. 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 294 294 Part IV: Inheritance A namespace is a collection of loosely coupled classes that are somehow logi- cally similar. In this case, I intend to throw all classes that I create concerning students, graduate students, classes, course schedules, and so forth into the Schools namespace. The classes that make up the Schools namespace are like members of a family. One class within a namespace may refer to other members of the same namespace directly. However, external classes must specify the name- space. You will see the ways of specifying a class’s namespace in the follow- ing SeparatedMain application. Another reason for dividing modules into namespaces is to avoid “name colli- sion.” For example, the class Grade within the namespace Schools does not interfere with the class Grade in the namespace FoodProduction. Implementing Student I put the implementation of the Student class in the file Student.cpp: // Student - implement the methods of the Student class #include <cstdio> #include <cstdlib> #include <iostream> #include <string> #include “student.h” namespace Schools { Student::Student(char* pszNameArg, int nIDArg) : nID(nIDArg) { pszName = new char[strlen(pszNameArg) + 1]; strcpy(pszName, pszNameArg); } // display - return a description of student char* Student::display() { // copy the student’s name into a block of heap // memory that we can return to the caller char* pReturn = new char[strlen(pszName) + 1]; strcpy(pReturn, pszName); return pReturn; } } The constructor for Student copies off the name and id provided it. The vir- tual display() method returns a string that describes the Student object. 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 295 Chapter 22: Factoring Classes 295 Compiling the Student.cpp file generates an intermediate file. This interme- diate file can be combined quickly with other intermediate files to form a completed executable program. For historical reasons, this intermediate file carries the extension .o (for “object file”) in most C++ environments. Dividing the program — GraduateStudent The next module that seems quasi-independent is GraduateStudent. Logically, one could fold the GraduateStudent class into Student.cpp; however, some programs may want to deal with Student as an abstraction and not worry about students versus graduate students. I made the GraduateStudent class as simple as possible. The include file appears as follows: // GraduateStudent - a special type of Student #ifndef _GRADUATE_STUDENT_ #define _GRADUATE_STUDENT_ #include “student.h” namespace Schools { class GraduateStudent : public Student { public: // trivial constructors GraduateStudent(char* pszName, int nID) : Student(pszName, nID){} // demonstration virtual function virtual char* display(); }; } #endif Notice that the graduateStudent.h file includes student.h. This is because the GraduateStudent class is dependent upon the definition of Student. The resulting source file implements the display() method, the only member function that is yet to be implemented: // GraduateStudent - a special type of Student #include <cstdio> #include <cstdlib> #include <iostream> #include “graduateStudent.h” 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 296 296 Part IV: Inheritance namespace Schools { char* GraduateStudent::display() { // get description of basic student char* pFirst = Student::display(); // we’ll add this text char* pSecond = “-G”; // get a new string and tack second onto first char* pName = new char[strlen(pFirst) + strlen(pSecond) + 1]; strcpy(pName, pFirst); strcat(pName, pSecond); // don’t forget to return the string returned by // Student::display() to the heap before passing // our new string to the caller delete pFirst; return pName; } } The GraduateStudent version of display() concatenates a “-G” onto the end of whatever Student returns. It begins by allocating a new character array that’s large enough to handle the extra information. Never assume that there’s enough room in the original buffer for any extra characters to be tacked onto the end. The program copies the contents of the original string into the newly allo- cated array. It then appends the “- G”. The display() function must return the buffer allocated by Student::display() to the heap before continuing. Forgetting to return buffers to the heap is known as a memory leak. A pro- gram with memory leaks executes properly at first; however, the program slows more and more as the available memory is lost to the leaks. The pro- gram eventually grinds to a halt. Memory leaks are very difficult to find. Implementing an application The two classes, Student and GraduateStudent, have been separated into independent source files and included in the Schools namespace. I wrote the following very simple application to invoke the two classes: 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 297 Chapter 22: Factoring Classes 297 // SeparatedMain - demonstrated an application separated // into two parts - the main() part #include <cstdio> #include <cstdlib> #include <iostream> #include “graduateStudent.h” #include “student.h” using namespace std; //using namespace Schools; using Schools::GraduateStudent; int main(int nArgc, char* pszArgs[]) { Schools::Student s(“Sophie Moore”, 1234); cout << “Student = “ << s.display() << endl; GraduateStudent gs(“Greg U. Waite”, 5678); cout << “Student = “ << gs.display() << endl; // wait until user is ready before terminating program // to allow the user to see the program results system(“PAUSE”); return 0; } The application includes both the student.h and graduateStudent.h include files. This gives the application access to the definition of the two classes. You might notice that including graduatestudent.h automatically includes student.h. However, you shouldn’t take it for granted; include student.h if you access the Student class directly, whether or not you include graduateStudent.h. The #ifndef, which you installed in student.h, will make sure that the contents of student.h are not processed twice by the C++ compiler. SeparatedMain is not a member of the Schools namespace. When main() refers to the Student class, C++ does not know whether the programmer intends to use the Student found in the Schools namespace or a similarly named class in some other namespace. main() can completely specify a class without any possibility of ambiguity because Schools::Student refers specifically to the namespace and class. Alternatively, the programmer can specify her intentions at the beginning of the module: The phrase using Schools::GraduateStudent; tells C++ that any mention to GraduateStudent refers to the Schools namespace. 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 298 298 Part IV: Inheritance The programmer can gain access to all members of the Schools namespace by adding the command using namespace Schools. The following version of main() builds successfully: using namespace Schools; int main(int nArgc, char* pszArgs[]) { Student s(“Sophie Moore”, 1234); cout << “Student = “ << s.display() << endl; GraduateStudent gs(“Greg U. Waite”, 5678); cout << “Student = “ << gs.display() << endl; // wait until user is ready before terminating program // to allow the user to see the program results system(“PAUSE”); return 0; } statement at the beginning of the the std namespace. You began using the using namespace std book. The modules that make up the Standard C++ Library are members of Project file Full of expectation, I open the SeparatedMain.cpp file in the compiler and click Build. The module compiles properly, but an error occurs during the linking process. C++ does not know what a Student is. Somehow you have to tell C++ that the Student.cpp and GraduateStudent.cpp files need to be linked together with SeparatedMain.cpp to create the program. Most C++ environments, including both Dev-C++ and Visual C++.NET, combine multiple modules together via a project file. Dev-C++ and Visual C++ use their own project file formats. The directions for creating a C++ console application project within Visual Studio.NET is pro- vided on the enclosed CD-ROM in Bonus Chapter 2. Creating a project file under Dev-C++ Execute the following steps to create a Dev-C++ project: 1. Choose File➪New➪Project. Select Console Application and type the name SeparateModules. You should see the window in Figure 22-6. 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 299 Chapter 22: Factoring Classes 299 Figure 22-6: The New Project window allows the user to enter the name and type of project. 2. Click OK. Dev-C++ opens a file window. 3. Select the directory into which to store the project. I selected \CPP_Programs\Chap22. Dev-C++ creates a project with a default initial module main.cpp. 4. Remove main.cpp from the project because you already have a main() module. 5. Choose Project➪Remove From Project. 6. Select main.cpp and click OK. 7. Copy the files main.cpp, Student.cpp, GraduateStudent.cpp, student.h, and graduateStudent.h to the Chap22 folder if they aren’t there already. 8. Choose Project➪Add to Project. 9. Select the entire list of source modules and click OK. 10. Choose Execute➪Rebuild All to compile the modules in the project and create an executable program. 11. Click the Classes tab in the left window to see a detailed description of each class in the program, as shown in Figure 22-7. Make sure that the class browser is enabled and configured properly. 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 300 300 Part IV: Inheritance Figure 22-7: The classes tab displays the members of each class. 12. Choose Tools➪Editor options and click the Class browsing tab. 13. Click the Enable Class Browser browser and the options shown in Figure 22-8. Notice how the class browser displays each member. Functions display with their argument types as well as the type of object returned. Notice also that the class browser shows two display() member functions under the GraduateStudent class. Figure 22-8: The class browser options tab determines the type of information available in the class browser. 28 568523 Ch22.qxd 4/5/04 2:03 PM Page 301 Chapter 22: Factoring Classes 301 14. Select the first display() entry in the list, the one with the small dia- mond in front of it. This opens the Student.cpp file and places the cursor on the display() member function. Selecting the second display() entry in the class browser takes the editor to the GraduateStudent::display() member function. The properties of the project are initially set to the default. You can change the settings as follows. 15. Select Project➪Project Options. For example, select the Linker options under the Compiler tab. Now make sure that Generate Debugging Information is set to Yes if you intend to use the Dev-C++ debugger. I encourage you to break your programs into multiple source files. It simpli- fies the editing, modifying, and debugging process. [...]... 67.5 34567 ============================= Trudie, 56x 78 78. 90 Error parsing string ============================= Valerie, 789 01 89 .10 name = Valerie,account = 789 01, balance = 89 .1 Valerie, 89 .1 789 01 ============================= Press any key to continue Reflect a second before continuing Notice how the program was able to resynch itself after the error in the input file Notice, also, the simplicity... Problem No matter what anyone may think of operator overloading, you will need to overload the assignment operator for many classes that you generate C++ provides a default definition for operator=() for all classes This default def inition performs a member-by-member copy This works great for an intrinsic type like an int int i; i = 10; // “member by member” copy This same default definition is applied... Chapter 4 describe the operators that C++ defines for the intrinsic data types C++ enables the programmer to define the operators for classes that the programmer has created in addition to these intrinsic operators This is called operator overloading Normally, operator overloading is optional and not attempted by beginning C++ programmers A lot of experienced C++ programmers (including me) don’t think... void fn() { cout . program. Most C++ environments, including both Dev -C++ and Visual C++. NET, combine multiple modules together via a project file. Dev -C++ and Visual C++ use their own project file formats. The. operator for many classes that you generate. C++ provides a default definition for operator=() for all classes. This default def- inition performs a member-by-member copy. This works great for an. using Schools::GraduateStudent; tells C++ that any mention to GraduateStudent refers to the Schools namespace. 28 5 685 23 Ch22.qxd 4/5/04 2:03 PM Page 2 98 2 98 Part IV: Inheritance The programmer