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

📄 chapter01.html

📁 《C++编程思想》中文版。。。。。。。。。。。。。
💻 HTML
📖 第 1 页 / 共 5 页
字号:
to tell a generic shape to draw itself, or a generic vehicle to steer, or a
generic bird to move, the compiler cannot know at compile-time precisely what
piece of code will be executed. That’s the whole point – when the
message is sent, the programmer doesn&#8217;t <I>want</I> to know what piece of
code will be executed; the draw function can be applied equally to a circle, a
square, or a triangle, and the object will execute the proper code depending on
its specific type. If you don&#8217;t have to know what piece of code will be
executed, then when you add a new subtype, the code it executes can be different
without requiring changes to the function call. Therefore, the compiler cannot
know precisely what piece of code is executed, so what does it do? For example,
in the following diagram the <B>BirdController</B> object just works with
generic <B>Bird</B> objects, and does not know what exact type they are. This is
convenient from <B>BirdController</B>&#8217;s perspective, because it
doesn&#8217;t have to write special code to determine the exact type of
<B>Bird</B> it&#8217;s working with, or that <B>Bird</B>&#8217;s behavior. So
how does it happen that, when <B>move(&#160;)</B> is called while ignoring the
specific type of <B>Bird</B>, the right behavior will occur (a <B>Goose
</B>runs, flies, or swims, and a <B>Penguin</B> runs or swims)?</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo10.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The answer is the primary twist in
object-oriented programming: The compiler cannot make a function call in the
traditional sense. The function call generated by a non-OOP compiler causes what
is called <A NAME="Index131"></A><A NAME="Index132"></A><I>early binding</I>, a
term you may not have heard before because you&#8217;ve never thought about it
any other way. It means the compiler generates a call to a specific function
name, and the linker resolves this call to the absolute address of the code to
be executed. In OOP, the program cannot determine the address of the code until
runtime, so some other scheme is necessary when a message is sent to a generic
object.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To solve the problem, object-oriented
languages use the concept of
<A NAME="Index133"></A><A NAME="Index134"></A><I>late binding</I>. When you send
a message to an object, the code being called isn&#8217;t determined until
runtime. The compiler does ensure that the function exists and performs type
checking on the arguments and return value (a language in which this isn&#8217;t
true is called <A NAME="Index135"></A><A NAME="Index136"></A><I>weakly
typed</I>), but it doesn&#8217;t know the exact code to
execute.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">To perform late binding, the C++ compiler
inserts a special bit of code in lieu of the absolute call. This code calculates
the address of the function body, using information stored in the object (this
process is covered in great detail in Chapter 15). Thus, each object can behave
differently according to the contents of that special bit of code. When you send
a message to an object, the object actually does figure out what to do with that
message.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">You state that you want a function to
have the flexibility of late-binding properties using the
<A NAME="Index137"></A><A NAME="Index138"></A>keyword <B>virtual</B>. You
don&#8217;t need to understand the mechanics of <B>virtual</B> to use it, but
without it you can&#8217;t do object-oriented programming in C++. In C++, you
must remember to add the <B>virtual</B> keyword because, by default, member
functions are <I>not</I> dynamically bound. Virtual functions allow you to
express the differences in behavior of classes in the same family. Those
differences are what cause polymorphic behavior.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Consider the shape example. The family of
classes (all based on the same uniform interface) was diagrammed earlier in the
chapter. To demonstrate polymorphism, we want to write a single piece of code
that ignores the specific details of type and talks only to the base class. That
code is <A NAME="Index139"></A><I>decoupled</I> from type-specific information,
and thus is simpler to write and easier to understand. And, if a new type
&#8211; a <B>Hexagon</B>, for example &#8211;<B> </B>is added through
inheritance, the code you write will work just as well for the new type of
<B>Shape</B> as it did on the existing types. Thus, the program is
<I>extensible</I>.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">If you write a function in C++ (as you
will soon learn how to do):</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE><font color=#0000ff>void</font> doStuff(Shape&amp; s) {
  s.erase();
  <font color=#009900>// ...</font>
  s.draw();
} </PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This function speaks to any <B>Shape</B>,
so it is independent of the specific type of object that it&#8217;s drawing and
erasing (the &#8216;<B>&amp;</B>&#8217; means &#8220;Take the address of the
object that&#8217;s passed to <B>doStuff(&#160;)</B>,&#8221; but it&#8217;s not
important that you understand the details of that right now). If in some other
part of the program we use the <B>doStuff(&#160;)</B> function:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>Circle c;
Triangle t;
Line l;
doStuff(c);
doStuff(t);
doStuff(l); </PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The calls to <B>doStuff(&#160;)
</B>automatically work right, regardless of the exact type of the object.
</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">This is actually a pretty amazing trick.
Consider the line:</FONT><BR></P></DIV>
<BLOCKQUOTE><FONT SIZE = "+1"><PRE>doStuff(c);</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">What&#8217;s happening here is that a
<B>Circle</B> is being passed into a function that&#8217;s expecting a
<B>Shape</B>. Since a <B>Circle</B> <I>is</I> a <B>Shape</B> it can be treated
as one by <B>doStuff(&#160;)</B>. That is, any message that
<B>doStuff(&#160;)</B> can send to a <B>Shape</B>, a <B>Circle</B> can accept.
So it is a completely safe and logical thing to do.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">We call this process of treating a
derived type as though it were its base type
<A NAME="Index140"></A><I>upcasting</I>. The name <A NAME="Index141"></A><I>cast
</I>is used in the sense of casting into a mold and the <I>up</I> comes from the
way the <A NAME="Index142"></A><A NAME="Index143"></A>inheritance diagram is
typically arranged, with the base type at the top and the derived classes
fanning out downward. Thus, casting to a base type is moving up the inheritance
diagram: &#8220;upcasting.&#8221;</FONT><BR></P></DIV>
<DIV ALIGN="CENTER"><FONT FACE="Georgia"><IMG SRC="TIC2Vo11.gif"></FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">An object-oriented program contains some
upcasting somewhere, because that&#8217;s how you decouple yourself from knowing
about the exact type you&#8217;re working with. Look at the code in
<B>doStuff(&#160;)</B>:</FONT><BR></P></DIV>

<BLOCKQUOTE><FONT SIZE = "+1"><PRE>  s.erase();
  <font color=#009900>// ...</font>
  s.draw();</PRE></FONT></BLOCKQUOTE>

<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Notice that it doesn&#8217;t say
&#8220;If you&#8217;re a <B>Circle</B>, do this, if you&#8217;re a
<B>Square</B>, do that, etc.&#8221; If you write that kind of code, which checks
for all the possible types that a <B>Shape</B> can actually be, it&#8217;s messy
and you need to change it every time you add a new kind of <B>Shape</B>. Here,
you just say &#8220;You&#8217;re a shape, I know you can <B>erase(&#160;)
</B>and <B>draw(&#160;) </B>yourself, do it, and take care of the details
correctly.&#8221; </FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">What&#8217;s impressive about the code in
<B>doStuff(&#160;)</B> is that, somehow, the right thing happens. Calling
<B>draw(&#160;)</B> for <B>Circle</B> causes different code to be executed than
when calling <B>draw(&#160;) </B>for a <B>Square</B> or a <B>Line</B>, but when
the <B>draw(&#160;)</B> message is sent to an anonymous <B>Shape</B>, the
correct behavior occurs based on the actual type of the <B>Shape</B>. This is
amazing because, as mentioned earlier, when the C++ compiler is compiling the
code for <B>doStuff(&#160;)</B>, it cannot know exactly what types it is dealing
with. So ordinarily, you&#8217;d expect it to end up calling the version of
<B>erase(&#160;)</B> and <B>draw(&#160;) </B>for <B>Shape</B>, and not for the
specific <B>Circle</B>, <B>Square</B>, or <B>Line</B>. And yet the right thing
happens because of polymorphism. The compiler and runtime system handle the
details; all you need to know is that it happens and more importantly how to
design with it. If a member function is <B>virtual</B>, then<B> </B>when you
send a message to an object, the object will do the right thing, even when
upcasting is involved.</FONT><A NAME="_Toc472654689"></A><BR></P></DIV>
<A NAME="Heading28"></A><FONT FACE = "Verdana"><H2 ALIGN="LEFT">
Creating and destroying objects</H2></FONT>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Technically, the domain of OOP is
abstract data typing, inheritance, and polymorphism, but other issues can be at
least as important. This section gives an overview of these
issues.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Especially important is the way objects
are created and destroyed. Where is the data for an object and how is the
lifetime of that object controlled? Different programming languages use
different philosophies here. C++ takes the approach that control of efficiency
is the most important issue, so it gives the programmer a choice. For maximum
runtime speed, the storage and lifetime can be determined while the program is
being written, by placing the objects on the <A NAME="Index144"></A>stack or in
<A NAME="Index145"></A><A NAME="Index146"></A>static storage. The stack is an
area in memory that is used directly by the microprocessor to store data during
program execution. Variables on the stack are sometimes called
<A NAME="Index147"></A><A NAME="Index148"></A><I>automatic </I>or
<A NAME="Index149"></A><A NAME="Index150"></A><I>scoped</I> variables. The
static storage area is simply a fixed patch of memory that is allocated before
the program begins to run. Using the stack or static storage area places a
priority on the speed of storage allocation and release, which can be valuable
in some situations. However, you sacrifice flexibility because you must know the
exact quantity, lifetime, and type of objects <I>while</I> you&#8217;re writing
the program. If you are trying to solve a more general problem, such as
computer-aided design, warehouse management, or air-traffic control, this is too
restrictive.</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">The second approach is to create objects
<A NAME="Index151"></A><A NAME="Index152"></A>dynamically in a pool of memory
called the <A NAME="Index153"></A><I>heap</I>. In this approach you don&#8217;t
know until runtime how many objects you need, what their lifetime is, or what
their exact type is. Those decisions are made at the spur of the moment while
the program is running. If you need a new object, you simply make it on the heap
when you need it, using the <A NAME="Index154"></A><A NAME="Index155"></A><B>new
</B>keyword. When you&#8217;re finished with the storage, you must release it
using the <A NAME="Index156"></A><A NAME="Index157"></A><B>delete </B>keyword.
</FONT><BR></P></DIV>
<DIV ALIGN="LEFT"><P><FONT FACE="Georgia">Because the storage is managed
dynamically at runtime, the amount of time required to allocate storage on the
heap is significantly longer than the time to create storage on the stack.
(Creating storage on the stack is often a single microprocessor instruction to
move the stack pointer down, and another to move it back up.) The dynamic
approach makes the generally logical assumption that objects tend to be
complicated, so the extra overhead of finding storage and releasing that storage

⌨️ 快捷键说明

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