📄 index.html
字号:
<h3> <a name="Heading13">Definition of Structs in a Function Parameter List and
Return Type</a></h3>
<p>In C, a struct can be defined in a function parameter list as well as in its
return type. For example</p>
<pre>
<tt>/*** valid in C but not C++ ***/</tt>
<tt>/* struct definition in return type and parameter list of a function */</tt>
<tt>struct Stat { int code; char msg[10];} </tt>
<tt> logon (struct User { char username[8]; char pwd[8];} u );</tt>
</pre>
<p>In C++, this is illegal.</p>
<h3> <a name="Heading14">Bypassing an Initialization</a></h3>
<p>A <i>jump statement</i> unconditionally transfers control. A jump statement
is one of the following: a <tt>goto</tt> statement, a transfer from the condition
of a <tt>switch</tt> statement to a <tt>case</tt> label, a <tt>break</tt> statement,
a <tt>continue</tt> statement, or a <tt>return</tt> statement. In C, the initialization
of a variable can be skipped by a jump statement, as in the following example:</p>
<pre>
<tt>/*** valid in C but not C++ ***/</tt>
<tt>int main()</tt>
<tt>{</tt>
<tt> int n=1;</tt>
<tt> switch(n)</tt>
<tt> {</tt>
<tt> case 0:</tt>
<tt> int j=0;</tt>
<tt> break;</tt>
<tt> case 1: /* skip initialization of j */</tt>
<tt> j++; /* undefined */</tt>
<tt> break;</tt>
<tt> default:</tt>
<tt> break;</tt>
<tt> }</tt>
<tt> return 0;</tt>
<tt>}</tt>
</pre>
<p>In C++, bypassing an initialization is illegal.</p>
<h2> <a name="Heading15">Quiet Differences Between C and C++</a></h2>
<p>The differences that have been presented thus far are easily diagnosed by a
C++ compiler. There are, however, semantic differences between C and C++ in
the interpretation of certain constructs. These differences might not result
in a compiler diagnostic; therefore, it is important to pay attention to them.</p>
<h3> <a name="Heading16">The Size of an enum Type</a></h3>
<pre>
<tt>In C, the size of an enumeration equals the sizeof(int). In C++, the underlying type for an enumeration is not necessarily an int -- it can be smaller. Furthermore, if an enumerator's value is too large to be represented as an unsigned int, the implementation is allowed to use a larger unit. For example enum { SIZE = 5000000000UL };</tt>
</pre>
<h3> <a name="Heading17">The Size of A Character Constant</a></h3>
<p>In C, the result of applying the operator <tt>sizeof</tt> to a character constant
-- for example, <tt>sizeof('c');</tt> -- equals <tt>sizeof(int)</tt>. In C++,
on the other hand, the expression <tt>sizeof('c');</tt> equals <tt>sizeof(char)</tt>.</p>
<h3> <a name="Heading18">Predefined Macros</a></h3>
<p>C and C++ compilers define the following macros: </p>
<pre>
<tt>__DATE__ /*a literal containing compilation date in the form "Apr 13 1998" */</tt>
<tt>__TIME__ /*a literal containing the compilation time in the form "10:01:07" */</tt>
<tt>__FILE__ /*a literal containing the name of the source file being compiled */</tt>
<tt>__LINE__ /* current line number in the source file */</tt>
</pre>
<p>C++ compilers exclusively define the following macro:</p>
<pre>
<tt>__cpluplus</tt>
</pre>
<p>Standard-compliant C compilers define the following macro symbol:</p>
<pre>
<tt>__STDC__</tt>
</pre>
<p>Whether a C++ compiler also defines the macro symbol <tt>__STDC__</tt> is implementation-dependent.</p>
<h3> <a name="Heading19">Default Value Returned from main()</a></h3>
<p>In C, when control reaches the end of <tt>main()</tt> without encountering
a <tt>return</tt> statement, the effect is that of returning an undefined value
to the environment. In C++, however, <tt>main()</tt> implicitly executes a</p>
<pre>
<tt>return 0;</tt>
</pre>
<p>statement in this case.</p>
<blockquote>
<hr>
<strong>NOTE: </strong> You might have noticed that the code listings throughout
the book contain an explicit <tt>return</tt> statement at the end of <tt>main()</tt>,
even though this is not necessary. There are two reasons for this: First, many
compilers that do not comply with the Standard issue a warning message when
a <tt>return</tt> statement is omitted. Secondly, the explicit <tt>return</tt>
statement is used to return a nonzero value in the event of an error.
<hr>
</blockquote>
<h2> <a name="Heading20">Migrating From C to C++</a></h2>
<p>Resolving the syntactic and semantic differences between C and C++ is the first
step in migrating from C to C++. This process ensures that C code can compile
under a C++ compiler, and that the program behaves as expected. There is another
clear advantage of compiling C code under a C++ compiler: The tighter type checking
that is applied by a C++ compiler can detect potential bugs that a C compiler
does not detect. The list of discrepancies between C and C++ that was previously
presented is mostly a result of loopholes and potential traps in C that were
fixed in C++. An issue that is of concern, however, is performance -- does a
C++ compiler produce object code that is less efficient than the code produced
by a C compiler? This topic is discussed in more detail in Chapter 12, "Optimizing
Your Code." However, it is important to note that a good C++ compiler can <i>outperform</i>
a good C compiler because it can exercise optimization techniques that C compilers
normally do not support, such as function inlining and the <i>named return value</i>
(also discussed in Chapter 12).</p>
<p>Nonetheless, in order to benefit from the robustness of object-oriented programming,
more substantial code modifications are required. Fortunately, the transition
from procedural to object-oriented programming can be performed gradually. The
following section demonstrates a technique of wrapping bare functions with an
additional code layer to minimize the dependency on implementation details.
Following that is a discussion of how to use full-fledged classes that wrap
legacy code in order to gain more of the benefits of object-orientation.</p>
<h3> <a name="Heading21">Function Wrappers</a></h3>
<p>Low-level code such as infrastructure routines and API functions can be used
by different teams for the same project. Normally, this code is developed and
maintained by a third party vendor or a specific team in the project. For example</p>
<pre>
<tt>int retrievePerson (int key, Person* recordToBefilled); /* C function */</tt>
</pre>
<p>A problem can arise when the interface of <tt>()</tt> changes: Every occurrence
of a function call has to be tracked down and modified accordingly. Consider
how such a small change can affect existing programs:</p>
<pre>
<tt>/*</tt>
<tt> function modification: key is now a char * instead of an int</tt>
<tt> every call to this function has to modified accordingly</tt>
<tt>*/</tt>
<tt>int retrievePerson (const char * key, Person* recordToBefilled);</tt>
</pre>
<p>As you saw in Chapter 5, "Object-Oriented Programming and Design," one of the
most noticeable weaknesses of procedural programming is its vulnerability to
such changes; however, even in strict procedural programming you can localize
their impact by using a <i>wrapper function</i>. A wrapper function calls the
vulnerable function and returns its result. Following is an example:</p>
<pre>
<tt>/* A wrapper function */</tt>
<tt>int WrapRetrievePerson(int key, Person* recordToBefilled)</tt>
<tt>{</tt>
<tt> return retrievePerson (key, recordToBefilled);</tt>
<tt>}</tt>
</pre>
<p>A wrapper provides a stable interface to a code fragment that is used extensively
and that is vulnerable to changes. When using a wrapper function, a change in
the interface of an API function is reflected only in the definition of its
corresponding wrapper function. Other parts of the program are not affected
by the change. This is very similar to the way in which a class's accessors
and mutators provide indirect access to its nonpublic members. In the following
example, the function wrapper's body has been modified due to the change in
the type of key from <tt>int</tt> to <tt>char *</tt>. Note, however, that its
interface remains intact:</p>
<pre>
<tt>/*** file DB_API.h ***/</tt>
<tt>int retrievePerson (const char *strkey, Person* precordToBefilled);</tt>
<tt>typedef struct</tt>
<tt>{</tt>
<tt> char first_name[20];</tt>
<tt> char last_name[20];</tt>
<tt> char address [50];</tt>
<tt>} Person;</tt>
<tt>/*** file DB_API.h ***/</tt>
<tt>#include <stdio.h></tt>
<tt>#include " DB_API.h "</tt>
<tt>int WrapRetrievePerson(int key, Person* precordToBefilled) //remains intact</tt>
<tt>{</tt>
<tt> /* wrapper's implementation modified according to API's modification */</tt>
<tt> char strkey[100];</tt>
<tt> sprintf (strkey, "%d", key); /* convert int to a string */</tt>
<tt> return retrievePerson (strkey, precordToBefilled);</tt>
<tt>}</tt>
</pre>
<p>By systematically applying this technique to every function that is maintained
by other teams or vendors, you can ensure a reasonable level of interface stability
even when the underlying implementation changes.</p>
<p>Although the function wrapper technique offers protection from changes in implementation
details, it does not provide other advantages of object-oriented programming,
including encapsulation of related operations in a single class, constructors
and destructors, and inheritance. The next phase in the migration process is
to encapsulate a collection of related functions into a single wrapper class.
This technique, however, requires familiarity with object-oriented concepts
and principles.</p>
<h2> <a name="Heading22">Designing Legacy Code Wrapper Classes</a></h2>
<p>In many frameworks that were originally written in C and then ported to C++,
a common -- but wrong -- practice was to wrap C functions in a single <i>wrapper
class</i>. Such a wrapper class provides as its interface a series of operations
mapped directly to the legacy functions. The following networking functions
provide an example:</p>
<pre>
<tt>/*** file: network.h ***/</tt>
<tt>#ifndef NETWORK_H</tt>
<tt>#define NETWORK_H</tt>
<tt> /* functions related to UDP protocol */</tt>
<tt>int UDP_init();</tt>
<tt>int UDP_bind(int port);</tt>
<tt>int UDP_listen(int timeout);</tt>
<tt>int UDP_send(char * buffer);</tt>
<tt> /* functions related to X.25 protocol */</tt>
<tt>int X25_create_virtual_line();</tt>
<tt>int X25_read_msg_from_queue(char * buffer);</tt>
<tt> /* general utility functions */</tt>
<tt>int hton(unsigned int); //reverse bytes from host to network order</tt>
<tt>int ntoh(unsigned int); //reverse bytes from network to host order</tt>
<tt>#endif</tt>
<tt>/*** network.h ***/</tt>
</pre>
<p>A na[um]ive implementation of a class wrapper might simply embed all these
functions in a single class as follows:</p>
<pre>
<tt>#include "network.h"</tt>
<tt>class Networking</tt>
<tt>{</tt>
<tt>private:</tt>
<tt>//...stuff</tt>
<tt>public:</tt>
<tt> //constructor and destructor</tt>
<tt> Networking();</tt>
<tt> ~Networking();</tt>
<tt> //members</tt>
<tt> int UDP_init();</tt>
<tt> int UDP_bind(int port);</tt>
<tt> int UDP_listen(int timeout);</tt>
<tt> int UDP_send(char * buffer);</tt>
<tt> int X25_create_virtual_line();</tt>
<tt> int X25_read_msg_from_queue(char * buffer);</tt>
<tt> int hton(unsigned int); //reverse bytes from host to network order</tt>
<tt> int ntoh(unsigned int); //reverse bytes from network to host order</tt>
<tt>};</tt>
</pre>
<p>However, this method of implementing a wrapper class is not recommended. X.25
and UDP protocols are used for different purposes and have almost nothing in
common. Bundling the interfaces of these two protocols together can cause maintenance
problems in the long term -- and it undermines the very reason for moving to
an object-oriented design in the first place. Furthermore, due to its amorphous
interface, <tt>Networking</tt> is not an ideal base for other derived classes.The
problem with <tt>Networking</tt> and similar classes is that they do not genuinely
embody an object-oriented policy. They are merely a collection of unrelated
operations. A better design approach is to divide the legacy functions into
meaningful, self-contained units and wrap each unit by a dedicated class. For
example</p>
<pre>
<tt>#include "network.h"</tt>
<tt>class UDP_API</tt>
<tt>{</tt>
<tt>private:</tt>
<tt>//...stuff</tt>
<tt>public:</tt>
<tt> //constructor and destructor</tt>
<tt> UDP_API();</tt>
<tt> ~UDP_API();</tt>
<tt> //members</tt>
<tt> int UDP_init();</tt>
<tt> int UDP_bind(int port);</tt>
<tt> int UDP_listen(int timeout);</tt>
<tt> int UDP_send(char * buffer);</tt>
<tt>};</tt>
<tt>class X25_API</tt>
<tt>{</tt>
<tt>private:</tt>
<tt>//...stuff</tt>
<tt>public:</tt>
<tt> //constructor and destructor</tt>
<tt> X25_API();</tt>
<tt> ~X25_API();</tt>
<tt> //members</tt>
<tt> int X25_create_virtual_line();</tt>
<tt> int X25_read_msg_from_queue(char * buffer);</tt>
<tt>};</tt>
<tt>class Net_utility</tt>
<tt>{</tt>
<tt> private:</tt>
<tt>//...stuff</tt>
<tt>public:</tt>
<tt> //constructor and destructor</tt>
<tt> Net_utility();</tt>
<tt> ~Net_utility();</tt>
<tt> //members</tt>
<tt> int hton(unsigned int); //reverse bytes from host to network order</tt>
<tt> int ntoh(unsigned int); //reverse bytes from network to host order</tt>
<tt>};</tt>
</pre>
<p>Now each class offers a coherent interface. Another advantage is a simpler
usage protocol; users of class <tt>X25_API</tt>, for instance, are not forced
to accept the interface of UDP protocol, and vice versa.</p>
<h2> <a name="Heading23">Multilingual Environments</a></h2>
<blockquote>
<hr>
<strong>NOTE: </strong> In this section, the distinction between C code and
C++ is indicated explicitly by file extensions. The .h extension is used for
C header files, whereas C++ header files are indicated by the .hpp extension.
Similarly, .c and .cpp extensions are used for C and C++ source files, respectively.
In addition, only C-style comments are used in C files.
<hr>
</blockquote>
<p>Thus far, this chapter has concentrated on a unidirectional migration process:
from C to C++. Nevertheless, many systems are not confined to a single programming
language. A typical information system can simultaneously use one programming
language for the graphical interface, another language for accessing data from
a database, and a third language for server applications. Often, these languages
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -