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

📄 ch05.htm

📁 c++语言操作手册
💻 HTM
📖 第 1 页 / 共 5 页
字号:
  members into a displayable string. In order to enhance performance, a design   modification is made: Instead of the three integers, a single <tt>string</tt>   now holds the date representation. Had class <tt>DateTime</tt> relied on the   internal implementation of <tt>Date</tt>, it would have had to be modified as   well. But because it can access <tt>Date</tt>'s data members only through access   methods, all that is required is a small change in the <tt>Date::Day()</tt>   member function. Please note that accessor methods are usually inlined anyway,   so their use does not incur additional runtime overhead.</p><h3> <a name="Heading12">Declaring Virtual Base Class Destructors</a></h3><p>A base class needs to have its destructor declared <tt>virtual</tt>. In doing   so, you ensure that the correct destructor is always called, even in the following   case:</p><pre><tt>class Base</tt><tt>{</tt><tt>private:</tt><tt>  char *p;</tt><tt>public:</tt><tt>  Base() { p = new char [200]; }</tt><tt>  ~ Base () {delete [] p; } //non virtual destructor, bad</tt><tt>};</tt><tt>class Derived : public Base</tt><tt>{</tt><tt>private:</tt><tt>  char *q;</tt><tt>public:</tt><tt>  Derived() { q = new char[300]; }</tt><tt>  ~Derived() { delete [] q; }</tt><tt>  //...</tt><tt>};</tt><tt>void destroy (Base &amp; b)  </tt><tt>{ </tt><tt>  delete &amp;b; </tt><tt>}</tt><tt>int main()</tt><tt>{</tt><tt>  Base *pb = new Derived(); //200 + 300 bytes allocated</tt><tt>  //... meddle with pb</tt><tt>  destroy (*pb);  //OOPS! only the destructor of Base is called</tt><tt>  //were Base's destructor virtual, the correct destructor would be called</tt><tt>  return 0;</tt><tt>}</tt></pre><h3> <a name="Heading13">Virtual Member Functions</a></h3><p>Virtual member functions enable subclasses to extend or override the behavior   of a base class. Deciding which members in a class can be overridden by a derived   class is not a trivial issue. A class that overrides a <tt>virtual</tt> member   function is only committed to adhere to the prototype of the overridden member   function -- not to its implementation. A common mistake is to declare all member   functions as <tt>virtual</tt> "just in case". In this respect, C++ makes a clear-cut   distinction between abstract classes that provide pure interfaces as opposed   to base classes that provide implementation as well as an interface.</p><h4> Extending A Virtual Function in A Derived Class</h4><p>There are cases in which you want a derived class to extend a virtual function   defined in its base class rather than override it altogether. It can be done   quite easily in the following way:</p><pre><tt>class shape</tt><tt>{</tt><tt>  //...</tt><tt>public:</tt><tt>  virtual void draw();</tt><tt>  virtual void resize(int x, int y) { clearscr(); /*...*/ }};</tt><tt>class rectangle: public shape</tt><tt>{</tt><tt>  //...</tt><tt>public:  </tt><tt>  virtual void resize (int x, int y)</tt><tt>  {</tt><tt>    shape::resize(x, y);  //explicit call to the base's virtual function</tt><tt>    //add functionality</tt><tt>    int size = x*y;</tt><tt>    //...</tt><tt>  }</tt><tt>};</tt></pre><p>The overriding function in a derived class should invoke an overridden function   of its base class using its fully-qualified name.</p><h4> Changing Access Specification of A Virtual Function</h4><p>The access specification of a <tt>virtual</tt> member function that is defined   in a base class can be changed in a derived class. For example</p><pre><tt>class Base</tt><tt>{</tt><tt>public:</tt><tt>  virtual void Say() { cout&lt;&lt;"Base";}</tt><tt>};</tt><tt>class Derived : public Base</tt><tt>{</tt><tt>private: //access specifier changed; legal but not a good idea</tt><tt>  void Say() {cout &lt;&lt;"Derived";} // overriding Base::Say()</tt><tt>};</tt></pre><p>Although this is legal, it does not work as expected when pointers or references   are used; a pointer or reference to <tt>Base</tt> can also be assigned to any   object that is publicly derived from <tt>Base</tt>:</p><pre><tt>Derived d;</tt><tt>Base *p = &amp;d;</tt><tt>p-&gt;Say(); //OK, invokes Derived::Say()</tt></pre><p>Because the actual binding of a virtual member function is postponed to runtime,   the compiler cannot detect that a nonpublic member function will be called;   it assumes that <tt>p</tt> points to an object of type <tt>Base</tt>, in which   <tt>Say()</tt> is a public member. As a rule, do not change the access specification   of a virtual member function in a derived class.</p><h4> Virtual Member Functions Should Not Be Private</h4><p></p><p>As you saw previously, it is customary to extend virtual functions in a derived   class by first invoking the base class's version of that function; then extend   it with additional functionality. This can't be done when a virtual function   is declared <tt>private</tt>. </p><h3> <a name="Heading14">Abstract Classes and Interfaces</a></h3><p>An abstract class is one that has at least one <i>pure virtual member function,   </i>that is, a non-implemented placeholder that must be implemented by its derived   class. Instances of an abstract class cannot be created because it is intended   to serve as a design skeleton for concrete classes that are derived from it,   and not as an independent object. See the following example:</p><pre><tt>class File  //abstract class; serves as interface</tt><tt>{</tt><tt>public:</tt><tt>  int virtual open() = 0;  //pure virtual</tt><tt>  int virtual close() = 0; //pure virtual	</tt><tt>};</tt><tt>class diskFile: public File</tt><tt>{</tt><tt>private:</tt><tt>  string filename;</tt><tt>  //...</tt><tt>public:</tt><tt>  int open() {/*...*/}</tt><tt>  int close () {/*...*/}</tt><tt>};</tt></pre><h3> <a name="Heading15">Use Derivation Instead of Type-Fields</a></h3><p>Suppose that you have to implement an internationalization helper class that   manages the necessary parameters of every natural language that is currently   supported by a word processor. A naive implementation might rely on type-fields   to indicate the specific language that is currently being used (for example,   the interface language in which menus are displayed).</p><pre><tt>class Fonts {/*...*/};</tt><tt>class Internationalization</tt><tt>{</tt><tt>private:</tt><tt>  Lang lg; //type field</tt><tt>  FontResthisce fonts</tt><tt>public:</tt><tt>  enum Lang {English, Hebrew, Danish}</tt><tt>  Internationalization(Lang lang) : lg(lang) {};</tt><tt>  Loadfonts(Lang lang);</tt><tt>};</tt></pre><p>Every modification in <tt>Internationalization</tt> affects all its users,   even when they are not supposed to be affected. When adding support for a new   language, the users of the already-supported languages have to recompile (or   download, which is worse) the new version of the class. Moreover, as time goes   by and support for new languages is added, the class becomes bigger and more   difficult to maintain, and it tends to contain more bugs. A much better design   approach is to use derivation instead of type-fields. For example</p><pre><tt>class Internationalization //now a base class</tt><tt>{</tt><tt>private:</tt><tt>  FontResthisce fonts</tt><tt>public:</tt><tt>  Internationalization ();</tt><tt>  virtual int Loadfonts();</tt><tt>  virtual void SetDirectionality();</tt><tt>};</tt><tt>class English : public Internationalization</tt><tt>{</tt><tt>public:</tt><tt>  English();</tt><tt>  Loadfonts() { fonts = TimesNewRoman; }</tt><tt>  SetDirectionality(){}//do nothing; default: left to right</tt><tt>};</tt><tt>class Hebrew : public Internationalization</tt><tt>{</tt><tt>public:</tt><tt>  Hebrew();</tt><tt>  Loadfonts() { fonts = David; }</tt><tt>  SetDirectionality() { directionality = right_to_left;}</tt><tt>};</tt></pre><p>Derivation simplifies class structure and localizes the changes that are associated   with a specific language to its corresponding class without affecting others. </p><h3> <a name="Heading16">Overloading A Member Function Across Class Boundaries</a></h3><p>A class is a namespace. The scope for overloading a member function is confined   to a class but not to its derived classes. Sometimes the need arises to overload   the same function in its class as well as in a class that is derived from it.   However, using an identical name in a derived class merely hides the base class's   function, rather than overloading it. Consider the following:</p><pre><tt>class B</tt><tt>{</tt><tt>public:</tt><tt>  void func();</tt><tt>};</tt><tt>class D : public B</tt><tt>{</tt><tt>public:</tt><tt>  void func(int n); //now hiding B::f, not overloading it</tt><tt>};</tt><tt>D d;</tt><tt>d.func();//compilation error. B::f is invisible in d;</tt><tt>d.func(1); //OK, D::func takes an argument of type int</tt></pre><p>In order to overload -- rather than hide -- a function of a base class, the   function name of the base class has to be injected explicitly into the namespace   of the derived class by a <i>using declaration</i>. For example</p><pre><tt>class D : public B</tt><tt>{</tt><tt>using B::func; // inject the name of a base member into the scope of D</tt><tt>public:</tt><tt>  void func(int n); // D now has two overloaded versions of func()</tt><tt>};</tt><tt>D d;</tt><tt>d.func ( ); // OK</tt><tt>d.func ( 10 ); // OK</tt></pre><h3> <a name="Heading17">Deciding Between Inheritance and Containment</a></h3><p>When designing a class hierarchy, you often face a decision between inheritance,   or <i>is-a</i>, and containment, or <i>has-a</i>, relation. The choice is not   always immediately apparent. Assume that you are designing a <tt>Radio</tt>   class, and you already have the following classes implemented for you in some   library: <tt>Dial</tt> and <tt>ElectricAppliance</tt>. It is obvious that <tt>Radio</tt>   is derived from <tt>ElectricAppliance</tt>. However, it is not so obvious that   <tt>Radio</tt> is also derived from <tt>Dial</tt>. In such cases, check whether   there is always a 1:1 relationship between the two. Do all radios have one and   only one dial? They don't. A radio can have no dials at all -- a transmitter/receiver   adjusted to a fixed frequency, for example. Furthermore, it might have more   than one dial -- FM and AM dials. Hence, your <tt>Radio</tt> class needs to   be designed to have Dial(s) rather than being<i> </i>derived from <tt>Dial</tt>.   Note that the relationship between <tt>Radio</tt> and <tt>ElectricAppliance</tt>   is 1:1 and corroborates the decision to derive <tt>Radio</tt> from <tt>ElectricAppliance</tt>.</p><h3> <a name="Heading18">The Holds-a Relation</a></h3><p>Ownership defines the responsibility for the creation and the destruction of   an object. An object is an owner of some other resource if and only if it has   the responsibility for both constructing and destroying it. In this respect,   an object that contains another object also owns it because its constructor   is responsible for the invocation of the embedded object's constructor. Likewise,   its destructor is responsible for invoking the embedded object's destructor.   This is the well-known has-a relationship. A similar relationship is <i>holds-a</i>.   It is distinguished from has-a by one factor: ownership. A class that indirectly   contains -- by means of a reference or a pointer -- another object that is constructed   and destroyed independently is said to hold that object. Here's an example:</p><pre><tt>class Phone {/*...*/};</tt><tt>class Dialer {/*...*/};</tt>

⌨️ 快捷键说明

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