📄 chapter11.html
字号:
<HTML><HEAD> <TITLE>Chapter 11</TITLE> <LINK REL="STYLESHEET" HREF="downey.css" tppabs="http://rocky.wellesley.edu/downey/ost/thinkCS/c++_html/downey.css"></HEAD><BODY><H2>Chapter 11</H2><H1>Member functions</H1><BR><BR><H3>11.1 Objects and functions</H3><P>C++ is generally considered an object-oriented programming language, which means that it provides features that support object-oriented programming.</P><P>It's not easy to define object-oriented programming, but we have already seen some features of it:</P><OL> <LI>Programs are made up of a collection of structure definitions and function definitions, where most of the functions operate on specific kinds of structures (or objecs).</LI><BR><BR> <LI>Each structure definition corresponds to some object or concept in the real world, and the functions that operate on that structure correspond to the ways real-world objects interact.</LI></OL><P>For example, the <TT>Time</TT> structure we defined in Chapter 9 obviously corresponds to the way people record the time of day, and the operations we defined correspond to the sorts of things people do with recorded times. Similarly, the <TT>Point</TT> and <TT>Rectangle</TT> structures correspond to the mathematical concept of a point and a rectangle.</P><P>So far, though, we have not taken advantage of the features C++ provides to support object-oriented programming. Strictly speaking, these features are not necessary. For the most part they provide an alternate syntax for doing things we have already done, but in many cases the alternate syntax is more concise and more accurately conveys the structure of the program.</P><P>For example, in the <TT>Time</TT> program, there is no obvious connection between the structure definition and the function definitions that follow. Withsome examination, it is apparent that every function takes at least one <TT>Time</TT> structure as a parameter.</P><P>This observation is the motivation for <B>member functions</B>. Member function differ from the other functions we have written in two ways:</P><OL> <LI>When we call the function, we <B>invoke</B> it on an object, rather than just call it. People sometimes describe this process as ``performing an operation on an object,'' or ``sending a message to an object.''</LI><BR><BR> <LI>The function is <I>declared</I> inside the <TT>struct</TT> definition, in order to make the relationship between the structure and the function explicit.</LI></OL><P>In the next few sections, we will take the functions from Chapter 9 and transform them into member functions. One thing you should realize is that thistransformation is purely mechanical; in other words, you can do it just by following a sequence of steps.</P><P>As I said, anything that can be done with a member function can also be donewith a nonmember function (sometimes called a <B>free-standing</B> function).But sometimes there is an advantage to one over the other. If you are comfortable converting from one form to another, you will be able to choose thebest form for whatever you are doing.</P><BR><BR><H3>11.2 <TT>print</TT></H3><P>In Chapter 9 we defined a structure named <TT>Time</TT> and wrote a functionnamed <TT>printTime</TT><P> <PRE>struct Time { int hour, minute; double second;};void printTime (const Time& time) { cout << time.hour << ":" << time.minute << ":" << time.second << endl;}</PRE><P>To call this function, we had to pass a <TT>Time</TT> object as a parameter.</P><PRE> Time currentTime = { 9, 14, 30.0 }; printTime (currentTime);</PRE><P>To make <TT>printTime</TT> into a member function, the first step is to change the name of the function from <TT>printTime</TT> to <TT>Time::print</TT>.The <TT>::</TT> operator separates the name of the structure from the name of the function; together they indicate that this is a function named <TT>print</TT> that can be invoked on a <TT>Time</TT> structure.</P><P>The next step is to eliminate the parameter. Instead of passing an object asan argument, we are going to invoke the function on an object.</P><P>As a result, inside the function, we no longer have a parameter named <TT>time</TT>. Instead, we have a <B>current object</TT>, which is the object the function is invoked on. We can refer to the current object using the C++ keyword <TT>this</TT>.</P><P>One thing that makes life a little difficult is that <TT>this</TT> is actually a <B>pointer</B> to a structure, rather than a structure itself. A pointer is similar to a reference, but I don't want to go into the details of using pointers yet. The only pointer operation we need for now is the <TT>*</TT> operator, which converts a structure pointer into a structure. In the following function, we use it to assign the value of <TT>this</TT> to a local variable named <TT>time</TT>:</P><PRE>void Time::print () { Time time = *this; cout << time.hour << ":" << time.minute << ":" << time.second << endl;}</PRE><P>The first two lines of this function changed quite a bit as we transformed it into a member function, but notice that the output statement itself did not change at all.</P><P>In order to invoke the new version of <TT>print</TT>, we have to invoke it on a <TT>Time</TT> object:</P><PRE> Time currentTime = { 9, 14, 30.0 }; currentTime.print ();</PRE><P>The last step of the transformation process is that we have to declare the new function inside the structure definition:</P><PRE>struct Time { int hour, minute; double second; void Time::print ();};</PRE><P>A <B>function declaration</B> looks just like the first line of the functiondefinition, except that it has a semi-colon at the end. The declaration describes the <B>interface</B> of the function; that is, the number and types of the arguments, and the type of the return value.</P><P>When you declare a function, you are making a promise to the compiler that you will, at some point later on in the program, provide a definition for the function. This definition is sometimes called the <B>implementation</B> of the function, since it contains the details of how the function works. If you omit the definition, or provide a definition that has an interface different from what you promised, the compiler will complain.</P><BR><BR><H3>11.3 Implicit variable access</H3><P>Actually, the new version of <TT>Time::print</TT> is more complicated than it needs to be. We don't really need to create a local variable in order to refer to the instance variables of the current object.</P><P>If the function refers to <TT>hour</TT>, <TT>minute</TT>, or <TT>second</TT>,all by themselves with no dot notation, C++ knows that it must be referring to the current object. So we could have written:</P><PRE>void Time::print (){ cout << hour << ":" << minute << ":" << second << endl;}</PRE><P>This kind of variable access is called ``implicit'' because the name of the object does not appear explicitly. Features like this are one reason member functions are often more concise than nonmember functions.</P><BR><BR><H3>11.4 Another example</H3><P>Let's convert <TT>increment</TT> to a member function. Again, we are going to transform one of the parameters into the implicit parameter called <TT>this</TT>. Then we can go through the function and make all the variable accesses implicit.</P><PRE>void Time::increment (double secs) { second += secs; while (second >= 60.0) { second -= 60.0; minute += 1; } while (minute >= 60) { minute -= 60.0; hour += 1; }}</PRE><P>By the way, remember that this is not the most efficient implementation of this function. If you didn't do it back in Chapter 9, you should write a more efficient version now.</P><P>To declare the function, we can just copy the first line into the structure definition:</P><PRE>struct Time { int hour, minute; double second; void Time::print (); void Time::increment (double secs);};</PRE><P>And again, to call it, we have to invoke it on a <TT>Time</TT> object:</P><PRE> Time currentTime = { 9, 14, 30.0 }; currentTime.increment (500.0); currentTime.print ();</PRE><P>The output of this program is <TT>9:22:50</TT>.</P><BR><BR><H3>11.5 Yet another example</H3><P>The original version of <TT>convertToSeconds</TT> looked like this:</P><PRE>double convertToSeconds (const Time& time) { int minutes = time.hour * 60 + time.minute; double seconds = minutes * 60 + time.second; return seconds;}</PRE><P>It is straightforward to convert this to a member function:</P><PRE>double Time::convertToSeconds () const { int minutes = hour * 60 + minute; double seconds = minutes * 60 + second; return seconds;}</PRE><P>The interesting thing here is that the implicit parameter should be declared<TT>const</TT>, since we don't modify it in this function. But it is not obvious where we should put information about a parameter that doesn't exist. The answer, as you can see in the example, is after the parameter list (which is empty in this case).</P><P>The <TT>print</TT> function in the previous section should also declare thatthe implicit parameter is <TT>const</TT>.</P><BR><BR><H3>11.6 A more complicated example</H3><P>Although the process of transforming functions into member functions is mechanical, there are some oddities. For example, <TT>after</TT> operates on two <TT>Time</TT> structures, not just one, and we can't make both of them implicit. Instead, we have to invoke the function on one of them and pass the other as an argument.</P><P>Inside the function, we can refer to one of the them implicitly, but to access the instance variables of the other we continue to use dot notation.</P><PRE>bool Time::after (const Time& time2) const { if (hour > time2.hour) return true; if (hour < time2.hour) return false; if (minute > time2.minute) return true; if (minute < time2.minute) return false; if (second > time2.second) return true; return false;}</PRE><P>To invoke this function:</P><PRE> if (doneTime.after (currentTime)) { cout << "The bread will be done after it starts." << endl; }</PRE><P>You can almost read the invocation like English: ``If the done-time is afterthe current-time, then...''</P><BR><BR><H3>11.7 Constructors</H3><P>Another function we wrote in Chapter 9 was <TT>makeTime</TT>:</P><PRE>Time makeTime (double secs) { Time time; time.hour = int (secs / 3600.0); secs -= time.hour * 3600.0; time.minute = int (secs / 60.0); secs -= time.minute * 60.0; time.second = secs; return time;}</PRE><P>Of course, for every new type, we need to be able to create new objects. In fact, functions like <TT>makeTime</TT> are so common that there is a special function syntax for them. These functions are called <B>constructors</B> and the syntax looks like this:</P><PRE>Time::Time (double secs) { hour = int (secs / 3600.0); secs -= hour * 3600.0; minute = int (secs / 60.0); secs -= minute * 60.0; second = secs;}</PRE><P>First, notice that the constructor has the same name as the class, and no return type. The arguments haven't changed, though.</P><P>Second, notice that we don't have to create a new time object, and we don't have to return anything. Both of these steps are handled automatically. We can refer to the new object---the one we are constructing---using the keyword <TT>this</TT>, or implicitly as shown here. When we write values to
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -