⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 m.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
Each pointer in the array can then be made to point to a different EquipmentPiece object: for (int i = 0; i < 10; ++i)  bestPieces[i] = new EquipmentPiece( ID Number );There are two disadvantages to this approach. First, you have to remember to delete all the objects pointed to by the array. If you forget, you have a resource leak. Second, the total amount of memory you need increases, because you need the space for the pointers as well as the space for the EquipmentPiece objects.You can avoid the space penalty if you allocate the raw memory for the array, then use "placement new" (see Item 8) to construct the EquipmentPiece objects in the memory: // allocate enough raw memory for an array of 10// EquipmentPiece objects; see Item 8 for details on// the operator new[] functionvoid *rawMemory =  operator new[](10*sizeof(EquipmentPiece));// make bestPieces point to it so it can be treated as an// EquipmentPiece arrayEquipmentPiece *bestPieces =  static_cast<EquipmentPiece*>(rawMemory);// construct the EquipmentPiece objects in the memory// using "placement new" (see Item 8)for (int i = 0; i < 10; ++i)  new (&bestPieces[i]) EquipmentPiece( ID Number );Notice that you still have to provide a constructor argument for each EquipmentPiece object. This technique (as well as the array-of-pointers idea) allows you to create arrays of objects when a class lacks a default constructor; it doesn't show you how to bypass required constructor arguments. There is no way to do that. If there were, it would defeat the purpose of constructors, which is to guarantee that objects are initialized.The downside to using placement new, aside from the fact that most programmers are unfamiliar with it (which will make maintenance more difficult), is that you must manually call destructors on the objects in the array when you want them to go out of existence, then you must manually deallocate the raw memory by calling operator delete[] (again, see Item 8): // destruct the objects in bestPieces in the inverse// order in which they were constructedfor (int i = 9; i >= 0; --i)  bestPieces[i].~EquipmentPiece();// deallocate the raw memoryoperator delete[](rawMemory);If you forget this requirement and use the normal array-deletion syntax, your program will behave unpredictably. That's because the result of deleting a pointer that didn't come from the new operator is undefined: delete [] bestPieces;                    // undefined! bestPieces                                         // didn't come from the new                                         // operatorFor more information on the new operator, placement new and how they interact with constructors and destructors, see Item 8.The second problem with classes lacking default constructors is that they are ineligible for use with many template-based container classes. That's because it's a common requirement for such templates that the type used to instantiate the template provide a default constructor. This requirement almost always grows out of the fact that inside the template, an array of the template parameter type is being created. For example, a template for an Array class might look something like this: template<class T>class Array {public:  Array(int size);  ...private:  T *data;};template<class T>Array<T>::Array(int size){  data = new T[size];                    // calls T::T() for each  ...                                    // element of the array}In most cases, careful template design can eliminate the need for a default constructor. For example, the standard vector template (which generates classes that act like extensible arrays) has no requirement that its type parameter have a default constructor. Unfortunately, many templates are designed in a manner that is anything but careful. That being the case, classes without default constructors will be incompatible with many templates. As C++ programmers learn more about template design, this problem should recede in significance. How long it will take for that to happen, however, is anyone's guess.The final consideration in the to-provide-a-default-constructor-or-not-to-provide-a-default-constructor dilemma has to do with virtual base classes (see Item E43). Virtual base classes lacking default constructors are a pain to work with. That's because the arguments for virtual base class constructors must be provided by the most derived class of the object being constructed. As a result, a virtual base class lacking a default constructor requires that all classes derived from that class no matter how far removed must know about, understand the meaning of, and provide for the virtual base class's constructors' arguments. Authors of derived classes neither expect nor appreciate this requirement.Because of the restrictions imposed on classes lacking default constructors, some people believe all classes should have them, even if a default constructor doesn't have enough information to fully initialize objects of that class. For example, adherents to this philosophy might modify EquipmentPiece as follows: class EquipmentPiece {public:  EquipmentPiece(  int IDNumber = UNSPECIFIED);  ...private:  static const int   UNSPECIFIED;        // magic ID number value                                         // meaning no ID was};                                       // specifiedThis allows EquipmentPiece objects to be created like this: EquipmentPiece e;                         // now okaySuch a transformation almost always complicates the other member functions of the class, because there is no longer any guarantee that the fields of an EquipmentPiece object have been meaningfully initialized. Assuming it makes no sense to have an EquipmentPiece without an ID field, most member functions must check to see if the ID is present. If it's not, they'll have to figure out how to stumble on anyway. Often it's not clear how to do that, and many implementations choose a solution that offers nothing but expediency: they throw an exception or they call a function that terminates the program. When that happens, it's difficult to argue that the overall quality of the software has been improved by including a default constructor in a class where none was warranted.Inclusion of meaningless default constructors affects the efficiency of classes, too. If member functions have to test to see if fields have truly been initialized, clients of those functions have to pay for the time those tests take. Furthermore, they have to pay for the code that goes into those tests, because that makes executables and libraries bigger. They also have to pay for the code that handles the cases where the tests fail. All those costs are avoided if a class's constructors ensure that all fields of an object are correctly initialized. Often default constructors can't offer that kind of assurance, so it's best to avoid them in classes where they make no sense. That places some limits on how such classes can be used, yes, but it also guarantees that when you do use such classes, you can expect that the objects they generate are fully initialized and are efficiently implemented. Back to Item 4: Avoid gratuitous default constructorsContinue to Item 5: Be wary of user-defined conversion functionsOperatorsOverloadable operators you gotta love 'em! They allow you to give your types the same syntax as C++'s built-in types, yet they let you put a measure of power into the functions behind the operators that's unheard of for the built-ins. Of course, the fact that you can make symbols like "+" and "==" do anything you want also means you can use overloaded operators to produce programs best described as impenetrable. Adept C++ programmers know how to harness the power of operator overloading without descending into the incomprehensible.Regrettably, it is easy to make the descent. Single-argument constructors and implicit type conversion operators are particularly troublesome, because they can be invoked without there being any source code showing the calls. This can lead to program behavior that is difficult to understand. A different problem arises when you overload operators like && and ||, because the shift from built-in operator to user-defined function yields a subtle change in semantics that's easy to overlook. Finally, many operators are related to one another in standard ways, but the ability to overload operators makes it possible to violate the accepted relationships.In the items that follow, I focus on explaining when and how overloaded operators are called, how they behave, how they should relate to one another, and how you can seize control of these aspects of overloaded operators. With the information in this chapter under your belt, you'll be overloading (or not overloading) operators like a pro. Back to OperatorsContinue to Item 6: Distinguish between prefix and postfix forms of increment and decrement operatorsItem 5: Be wary of user-defined conversion functions.C++ allows compilers to perform implicit conversions between types. In honor of its C heritage, for example, the language allows silent conversions from char to int and from short to double. This is why you can pass a short to a function that expects a double and still have the call succeed. The more frightening conversions in C those that may lose information are also present in C++, including conversion of int to short and double to (of all things) char.You can't do anything about such conversions, because they're hard-coded into the language. When you add your own types, however, you have more control, because you can choose whether to provide the functions compilers are allowed to use for implicit type conversions.Two kinds of functions allow compilers to perform such conversions: single-argument constructors and implicit type conversion operators. A single-argument constructor is a constructor that may be called with only one argument. Such a constructor may declare a single parameter or it may declare multiple parameters, with each parameter after the first having a default value. Here are two examples: class Name {                                 // for names of thingspublic:  Name(const string& s);                     // converts string to                                             // Name  ...  };class Rational {                             // for rational numberspublic:  Rational(int numerator = 0,                // converts int to           int denominator = 1);             // Rational  ...};An implicit type conversion operator is simply a member function with a strange-looking name: the word operator followed by a type specification. You aren't allowed to specify a type for the function's return value, because the type of the return value is basically just the name of the function. For example, to allow Rational objects to be implicitly converted to doubles (which might be useful for mixed-mode arithmetic involving Rational objects), you might define class Rational like this: class Rational {public:  ...  operator double() const;                   // converts Rational to};                                           // doubleThis function would be automatically invoked in contexts like this: Rational r(1, 2);         

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -