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

📄 ec4.htm

📁 一个非常适合初学者入门的有关c++的文档
💻 HTM
📖 第 1 页 / 共 5 页
字号:
neither of these statements would compile: result = oneHalf * 2;             // error!result = 2 * oneHalf;             // error!That would hardly qualify as support for mixed-mode arithmetic, but at least the behavior of the two statements would be consistent.The Rational class we've been examining, however, is designed to allow implicit conversions from built-in types to Rationals that's why Rational's constructor isn't declared explicit. That being the case, compilers will perform the implicit conversion necessary to allow result's first assignment to compile. In fact, your handy-dandy compilers will perform this kind of implicit type conversion, if it's needed, on every parameter of every function call. But they will do it only for parameters listed in the parameter list, never for the object on which a member function is invoked, i.e., the object corresponding to *this inside a member function. That's why this call works, result = oneHalf.operator*(2);      // converts int -> Rationaland this one does not: result = 2.operator*(oneHalf);      // doesn't convert                                    // int -> RationalThe first case involves a parameter listed in the function declaration, but the second one does not.Nonetheless, you'd still like to support mixed-mode arithmetic, and the way to do it is by now perhaps clear: make operator* a non-member function, thus allowing compilers to perform implicit type conversions on all arguments: class Rational {  ...                               // contains no operator*};// declare this globally or within a namespace; see// Item M20 for why it's written as it isconst Rational operator*(const Rational& lhs,                         const Rational& rhs){  return Rational(lhs.numerator() * rhs.numerator(),                  lhs.denominator() * rhs.denominator());}Rational oneFourth(1, 4);Rational result;result = oneFourth * 2;           // fineresult = 2 * oneFourth;           // hooray, it works!This is certainly a happy ending to the tale, but there is a nagging worry. Should operator* be made a friend of the Rational class?In this case, the answer is no, because operator* can be implemented entirely in terms of the class's public interface. The code above shows one way to do it. Whenever you can avoid friend functions, you should, because, much as in real life, friends are often more trouble than they're worth.However, it's not uncommon for functions that are not members, yet are still conceptually part of a class interface, to need access to the non-public members of the class.As an example, let's fall back on a workhorse of this book, the String class. If you try to overload operator>> and operator<< for reading and writing String objects, you'll quickly discover that they shouldn't be member functions. If they were, you'd have to put the String object on the left when you called the functions: // a class that incorrectly declares operator>> and// operator<< as member functionsclass String {public:  String(const char *value);  ...  istream& operator>>(istream& input);  ostream& operator<<(ostream& output);private:  char *data;};String s;s >> cin;                   // legal, but contrary                            // to conventions << cout;                  // dittoThat would confuse everyone. As a result, these functions shouldn't be member functions. Notice that this is a different case from the one we discussed above. Here the goal is a natural calling syntax; earlier we were concerned about implicit type conversions.If you were designing these functions, you'd come up with something like this: istream& operator>>(istream& input, String& string){  delete [] string.data;  read from input into some memory, and make string.data  point to it  return input;}ostream& operator<<(ostream& output,                    const String& string){  return output << string.data;}Notice that both functions need access to the data field of the String class, a field that's private. However, you already know that you have to make these functions non-members. You're boxed into a corner and have no choice: non-member functions with a need for access to non-public members of a class must be made friends of that class.The lessons of this Item are summarized below, in which it is assumed that f is the function you're trying to declare properly and C is the class to which it is conceptually related: Virtual functions must be members. If f needs to be virtual, make it a member function of C. operator>> and operator<< are never members. If f is operator>> or operator<<, make f a non-member function. If, in addition, f needs access to non-public members of C, make f a friend of C. Only non-member functions get type conversions on their left-most argument. If f needs type conversions on its left-most argument, make f a non-member function. If, in addition, f needs access to non-public members of C, make f a friend of C. Everything else should be a member function. If none of the other cases apply, make f a member function of C. Back to Item 19: Differentiate among member functions, non-member functions, and friend functions.Continue to Item 21: Use const whenever possible.Item 20: Avoid data members in the public interface.First, let's look at this issue from the point of view of consistency. If everything in the public interface is a function, clients of your class won't have to scratch their heads trying to remember whether to use parentheses when they want to access a member of your class. They'll just do it, because everything is a function. Over the course of a lifetime, that can save a lot of head scratching.You don't buy the consistency argument? How about the fact that using functions gives you much more precise control over the accessibility of data members? If you make a data member public, everybody has read/write access to it, but if you use functions to get and set its value, you can implement no access, read-only access, and read-write access. Heck, you can even implement write-only access if you want to: class AccessLevels {public:  int getReadOnly() const{ return readOnly; }  void setReadWrite(int value) { readWrite = value; }  int getReadWrite() const { return readWrite; }  void setWriteOnly(int value) { writeOnly = value; }private:  int noAccess;                    // no access to this   int  int readOnly;                    // read-only access to                                   // this int  int readWrite;                   // read-write access to                                   // this int  int writeOnly;                   // write-only access to                                   // this int};Still not convinced? Then it's time to bring out the big gun: functional abstraction. If you implement access to a data member through a function, you can later replace the data member with a computation, and nobody using your class will be any the wiser.For example, suppose you are writing an application in which some automated equipment is monitoring the speed of passing cars. As each car passes, its speed is computed, and the value is added to a collection of all the speed data collected so far: class SpeedDataCollection {public:  void addValue(int speed);       // add a new data value  double averageSoFar() const;    // return average speed};Now consider the implementation of the member function averageSoFar (see also Item M18). One way to implement it is to have a data member in the class that is a running average of all the speed data so far collected. Whenever averageSoFar is called, it just returns the value of that data member. A different approach is to have averageSoFar compute its value anew each time it's called, something it could do by examining each data value in the collection. (For a more general discussion of these two approaches, see Items M17 and M18.)The first approach keeping a running average makes each SpeedDataCollection object bigger, because you have to allocate space for the data member holding the running average. However, averageSoFar can be implemented very efficiently; it's just an inline function (see Item 33) that returns the value of the data member. Conversely, computing the average whenever it's requested will make averageSoFar run slower, but each SpeedDataCollection object will be smaller.Who's to say which is best? On a machine where memory is tight, and in an application where averages are needed only infrequently, computing the average each time is a better solution. In an application where averages are needed frequently, speed is of the essence, and memory is not an issue, keeping a running average is preferable. The important point is that by accessing the average through a member function, you can use either implementation, a valuable source of flexibility that you wouldn't have if you made a decision to include the running average data member in the public interface.The upshot of all this is that you're just asking for trouble by putting data members in the public interface, so play it safe by hiding all your data members behind a wall of functional abstraction. If you do it now, we'll throw in consistency and fine-grained access control at no extra cost! Back to Item 20: Differentiate among member functions, non-member functions, and friend functions.Continue to Item 22: Prefer pass-by-reference to pass-by-value.Item 21: Use const whenever possible.The wonderful thing about const is that it allows you to specify a certain semantic constraint a particular object should not be modified and compilers will enforce that constraint. It allows you to communicate to both compilers and other programmers that a value should remain invariant. Whenever that is true, you should be sure to say so explicitly, because that way you enlist your compilers' aid in making sure the constraint isn't violated.The const keyword is remarkably versatile. Outside of classes, you can use it for global or namespace constants (see Items 1 and 47) and for static objects (local to a file or a block). Inside classes, you can use it for both static and nonstatic data members (see also Item 12).For pointers, you can specify whether the pointer itself is const, the data it points to is const, both, or neither: char *p              = "Hello";          // non-const pointer,                                         // non-const data4const char *p        = "Hello";          // non-const pointer,                                         // const datachar * const p       = "Hello";          // const pointer,                                         // non-const dataconstchar*constp = "Hello";          // const pointer,                                         // const dataThis syntax isn't quite as capricious as it looks. Basically, you mentally draw a vertical line through the asterisk of a pointer declaration, and if the word const appears to the left of the line, what's pointed to is constant; if the word const appears to the right of the line, the pointer itself is constant; if const appears on both sides of the line, both are constant.When what's pointed to is constant, some programmers list const before the type name. Others list it after the type name but before the asterisk. As a result, the following functions take the same parameter type: class Widget { ... };void f1(const Widget *pw);      // f1 takes a pointer to a                                // constant Widget objectvoid f2(Widget const *pw);      // so does f2

⌨️ 快捷键说明

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