📄 ch13.htm
字号:
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 have to share data and code with one another. This section focuses on how to combine C and C++ code in a bilingual system that uses both these languages
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -