📄 chapter14.html
字号:
<HTML><HEAD> <TITLE>Chapter 14</TITLE> <LINK REL="STYLESHEET" HREF="downey.css" tppabs="http://rocky.wellesley.edu/downey/ost/thinkCS/c++_html/downey.css"></HEAD><BODY><H2>Chapter 14</H2><H1>Classes and invariants</H1><BR><BR><H3>14.1 Private data and classes</H3><P>I have used the word ``encapsulation'' in this book to refer to the process of wrapping up a sequence of instructions in a function, in order to separate the function's interface (how to use it) from its implementation (how it does what it does).</P><P>This kind of encapsulation might be called ``functional encapsulation,'' to distinguish it from ``data encapsulation,'' which is the topic of this chapter.Data encapsulation is based on the idea that each structure definition should provide a set of functions that apply to the structure, and prevent unrestricted access to the internal representation.</P><P>One use of data encapsulation is to hide implementation details from users or programmers that don't need to know them.</P><P>For example, there are many possible representations for a <TT>Card</TT>,including two integers, two strings and two enumerated types. The programmer who writes the <TT>Card</TT> member functions needs to know which implementation to use, but someone using the <TT>Card</TT> structure should nothave to know anything about its internal structure.</P><P>As another example, we have been using <TT>apstring</TT> and <TT>apvector</TT> objects without ever discussing their implementations. There are many possibilities, but as ``clients'' of these libraries, we don't need toknow.</P> <P>In C++, the most common way to enforce data encapsulation is to prevent client programs from accessing the instance variables of an object. The keyword<TT>private</TT> is used to protect parts of a structure definition. For example, we could have written the <TT>Card</TT> definition:</P><PRE>struct Card{private: int suit, rank;public: Card (); Card (int s, int r); int getRank () const { return rank; } int getSuit () const { return suit; } void setRank (int r) { rank = r; } void setSuit (int s) { suit = s; }};</PRE><P>There are two sections of this definition, a private part and a public part.The functions are public, which means that they can be invoked by client programs. The instance variables are private, which means that they can be readand written only by <TT>Card</TT> member functions.</P><P>It is still possible for client programs to read and write the instance variables using the <B>accessor functions</B> (the ones beginning with <TT>get</TT> and <TT>set</TT>). On the other hand, it is now easy to control which operations clients can perform on which instance variables. For example, it might be a good idea to make cards ``read only'' so that after they are constructed, they cannot be changed. To do that, all we have to do is remove the <TT>set</TT> functions.</P> <P>Another advantage of using accessor functions is that we can change the internal representations of cards without having to change any client programs.</P><BR><BR><H3>14.2 What is a class?</H3><P>In most object-oriented programming languages, a <B>class</B> is a user-defined type that includes a set of functions. As we have seen, structuresin C++ meet the general definition of a class.</P><P>But there is another feature in C++ that also meets this definition; confusingly, it is called a <TT>class</TT>. In C++, a class is just a structure whose instance variables are private by default. For example, I couldhave written the <TT>Card</TT> definition:</P><PRE>class Card{ int suit, rank;public: Card (); Card (int s, int r); int getRank () const { return rank; } int getSuit () const { return suit; } int setRank (int r) { rank = r; } int setSuit (int s) { suit = s; }};</PRE><P>I replaced the word <TT>struct</TT> with the word <TT>class</TT> and removedthe <TT>private:</TT> label. This result of the two definitions is exactly the same.</P><P>In fact, anything that can be written as a <TT>struct</TT> can also be written as a <TT>class</TT>, just by adding or removing labels. There is no real reason to choose one over the other, except that as a stylistic choice, most C++ programmers use <TT>class</TT>.</P><P>Also, it is common to refer to all user-defined types in C++ as ``classes,''regardless of whether they are defined as a <TT>struct</TT> or a <TT>class</TT>.</P><BR><BR><H3>14.3 Complex numbers</H3><P>As a running example for the rest of this chapter we will consider a class definition for complex numbers. Complex numbers are useful for many branches ofmathematics and engineering, and many computations are performed using complex arithmetic. A complex number is the sum of a real part and an imaginary part, and is usually written in the form <TT>x + yi</TT>, where $x$ is the real part,<TT>y</TT> is the imaginary part, and <TT>i</TT> represents the square root of -1.</P><P>The following is a class definition for a user-defined type called <TT>Complex</TT>:</P><PRE>class Complex{ double real, imag;public: Complex () { } Complex (double r, double i) { real = r; imag = i; }};</PRE><P>Because this is a <TT>class</TT> definition, the instance variables <TT>real</TT> and <TT>imag</TT> are private, and we have to include the label <TT>public:</TT> to allow client code to invoke the constructors.</P><P>As usual, there are two constructors: one takes no parameters and does nothing; the other takes two parameters and uses them to initialize the instance variables.</P><P>So far there is no real advantage to making the instance variables private.Let's make things a little more complicated; then the point might be clearer.</P><P>There is another common representation for complex numbers that is sometimescalled ``polar form'' because it is based on polar coordinates. Instead of specifying the real part and the imaginary part of a point in the complex plane, polar coordinates specify the direction (or angle) of the point relativeto the origin, and the distance (or magnitude) of the point.</P><P>The following figure shows the two coordinate systems graphically.</P><P CLASS=1><IMG SRC="images/coordinates.png" tppabs="http://rocky.wellesley.edu/downey/ost/thinkCS/c++_html/images/coordinates.png"></P><P>Complex numbers in polar coordinates are written <TT>re<SUP>it</SUP></TT>,where </TT>r</TT> is the magnitude (radius), and </TT>t</TT> is the angle inradians.</P><P>Fortunately, it is easy to convert from one form to another. To go from Cartesian to polar,</P> <PRE> r = sqrt(x<SUP>2</SUP> + y<SUP>2</SUP>) t = arctan(y / x)</PRE><P>To go from polar to Cartesian,</P><PRE> x = r cos t y = r sin t</PRE><P>So which representation should we use? Well, the whole reason there are multiple representations is that some operations are easier to perform in Cartesian coordinates (like addition), and others are easier in polar coordinates (like multiplication). One option is that we can write a class definition that uses <I>both</I> representations, and that converts between them automatically, as needed.</P><PRE>class Complex{ double real, imag; double mag, theta; bool cartesian, polar;public: Complex () { cartesian = false; polar = false; } Complex (double r, double i) { real = r; imag = i; cartesian = true; polar = false; }};</PRE><P>There are now six instance variables, which means that this representation will take up more space than either of the others, but we will see that it is very versatile.</P><P>Four of the instance variables are self-explanatory. They contain the real part, the imaginary part, the angle and the magnitude of the complex number. The other two variables, <TT>cartesian</TT> and <TT>polar</TT> are flags thatindicate whether the corresponding values are currently valid.</P><P>For example, the do-nothing constructor sets both flags to false to indicatethat this object does not contain a valid complex number (yet), in either representation.</P><P>The second constructor uses the parameters to initialize the real and imaginary parts, but it does not calculate the magnitude or angle. Setting the <TT>polar</TT> flag to false warns other functions not to access <TT>mag</TT> or <TT>theta</TT> until they have been set.</P><P>Now it should be clearer why we need to keep the instance variables private.If client programs were allowed unrestricted access, it would be easy for them to make errors by reading uninitialized values. In the next few sections, we will develop accessor functions that will make those kinds of mistakes impossible.</P><BR><BR><H3>14.4 Accessor functions</H3><P>By convention, accessor functions have names that begin with <TT>get</TT> and end with the name of the instance variable they fetch. The return type, naturally, is the type of the corresponding instance variable.</P><P>In this case, the accessor functions give us an opportunity to make sure that the value of the variable is valid before we return it. Here's what <TT>getReal</TT> looks like:</P><PRE>double Complex::getReal (){ if (cartesian == false) calculateCartesian (); return real;}</PRE><P>If the <TT>cartesian</TT> flag is true then <TT>real</TT> contains valid data, and we can just return it. Otherwise, we have to call <TT>calculateCartesian</TT> to convert from polar coordinates to Cartesian coordinates:</P><PRE>void Complex::calculateCartesian (){ real = mag * cos (theta); imag = mag * sin (theta); cartesian = true;}</PRE><P>Assuming that the polar coordinates are valid, we can calculate the Cartesian coordinates using the formulas from the previous section. Then we setthe <TT>cartesian</TT> flag, indicating that <TT>real</TT> and <TT>imag</TT> now contain valid data.</P><P>As an exercise, write a corresponding function called <TT>calculatePolar</TT>and then write <TT>getMag</TT> and <TT>getTheta</TT>. One unusual thing about these accessor functions is that they are not <TT>const</TT>, because invoking them might modify the instance variables.</P><BR><BR><H3>14.5 Output</H3><P>As usual when we define a new class, we want to be able to output objects in a human-readable form. For <TT>Complex</TT> objects, we could use two functions:</P><PRE>void Complex::printCartesian (){ cout << getReal() << " + " << getImag() << "i" << endl;}void Complex::printPolar (){ cout << getMag() << " e^ " << getTheta() << "i" << endl;}</PRE><P>The nice thing here is that we can output any <TT>Complex</TT> object in either format without having to worry about the representation. Since the output functions use the accessor functions, the program will compute automatically any values that are needed.</P><P>The following code creates a <TT>Complex</TT> object using the second constructor. Initially, it is in Cartesian format only. When we invoke <TT>printCartesian</TT> it accesses <TT>real</TT> and <TT>imag</TT> without having to do any conversions.</P><PRE> Complex c1 (2.0, 3.0); c1.printCartesian(); c1.printPolar();</PRE><P>When we invoke <TT>printPolar</TT>, and <TT>printPolar</TT> invokes <TT>getMag</TT>, the program is forced to convert to polar coordinates and store the results in the instance variables. The good news is that we only haveto do the conversion once. When <TT>printPolar</TT> invokes <TT>getTheta</TT>, it will see that the polar coordinates are valid and return <TT>theta</TT>immediately.</P><P>The output of this code is:</P><PRE>2 + 3i3.60555 e^ 0.982794i</PRE><BR><BR><H3>14.6 A function on <TT>Complex</TT> numbers</H3><P>A natural operation we might want to perform on complex numbers is addition.If the numbers are in Cartesian coordinates, addition is easy: you just add thereal parts together and the imaginary parts together. If the numbers are in polar coordinates, it is easiest to convert them to Cartesian coordinates and then add them.</P><P>Again, it is easy to deal with these cases if we use the accessor functions:</P><PRE>Complex add (Complex& a, Complex& b){ double real = a.getReal() + b.getReal(); double imag = a.getImag() + b.getImag(); Complex sum (real, imag); return sum;}</PRE><P>Notice that the arguments to <TT>add</TT> are not <TT>const</TT> because they might be modified when we invoke the accessors. To invoke this function, we would pass both operands as arguments:</P>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -