📄 ch05.htm
字号:
a pointer. A pointer is a variable that holds the address of some object in memory.</p><blockquote> <hr> <p><strong> </strong> <b>pointer</b>--A variable that holds the address of an object.</p> <hr></blockquote><h3> <a name="Heading8">Memory Addresses</a></h3><p>You can imagine that each memory location is ordered sequentially, as an offset from some arbitrary starting point. Picture, if you will, a series of cubbyholes, all aligned and numbered, perhaps as shown in Figure 5.2.</p><p><b>Figure 5.2</b><tt><b> </b></tt><i>Memory as cubbyholes.</i></p><p>Every integer, character, or object you create is stored in these addresses. Because each cubby holds one byte, a 4-byte integer such as <tt>correct</tt> takes up four such locations. </p><p><tt>correct</tt>'s address is just the first byte of that storage location. Because the compiler knows that an integer is 4 bytes, if its address is <tt>0x001</tt>, the compiler knows it occupies <tt>0x001</tt>, <tt>0x002</tt>, <tt>0x003</tt>, and <tt>0x004</tt>, and therefore puts the next integer at <tt>0x005</tt>-<tt>0x008</tt>.</p><blockquote> <hr> <p><strong>NOTE: </strong> These addresses are in hexadecimal, which is a base-16 numbering system. If you are curious about this, please see Appendix A, "Binary and Hexadecimal."</p> <hr></blockquote><p>There are two perspectives on what is stored in these locations. One is the bit perspective, which is pretty close to how the compiler "thinks" about memory (see Figure 5.3).</p><p><b>Figure 5.3</b><tt><b> </b></tt><i>How the compiler thinks about data in memory.</i></p><p>From this perspective, the four bytes are filled with binary digits. How these values are interpreted is irrelevant to the compiler. The bits might represent an integer, a character, or an address somewhere else in memory. We'll return to that idea in a moment.</p><p>The point is that the compiler doesn't know or care how to interpret the bits--it just knows what is stored at a given location. To the programmer, however, this memory is conceived somewhat differently (as shown in Figure 5.4).</p><p><b>Figure 5.4</b><tt><b> </b></tt><i>How programmers think about data in memory.</i></p><p>To the programmer, the value <tt>5</tt> is stored at this location like a letter in a mailbox. The programmer doesn't much care how the bits are configured, he just knows that the value is stashed away at a particular location.</p><p>Let's return to the idea that you can store a memory address. This is a powerful idea. We show here that the value <tt>5</tt> is stored at memory location <tt>0x001</tt>. What if you take that address, <tt>0x001</tt> (which in binary is <tt>00000000 00000000 00000000 00000001</tt>), and you stash that pattern at another address in memory: <tt>0x1101</tt> (see Figure 5.5).</p><p><b>Figure 5.5</b><tt><b> </b></tt><i>Storing the address.</i></p><blockquote> <hr> <p><strong>NOTE: </strong> There are some simplifying assumptions here that do not distort the point of this discussion. For example, these are not real memory locations, and values are often stored in memory in a slightly different order than is shown. In addition, compilers often store values at even boundaries in memory.</p> <hr></blockquote><p>Here you see that at location <tt>1101</tt>, you have stored the value <tt>0x001</tt>: the memory location at which you stored the value <tt>5</tt>. </p><p>At that <i>pointed to</i> address, you hold the value <tt>5</tt> as illustrated in Figure 5.4. You can now assign this address to a variable that holds an address--a pointer. You declare a pointer by indicating the type of object it points to (in this case, <tt>int</tt>), followed by the pointer operator (<tt>*</tt>), followed by the name of the variable: <a name="WhereWasI"></a></p><pre><tt>int * p<tt>Correct</tt>;</tt></pre><p>This declares <tt>pCorrect</tt> to be a pointer to an integer. You can then assign the address of any integer, in this case <tt>correct</tt>, to that pointer:</p><pre><tt>p<tt>Correct</tt> = &<tt>correct</tt>;</tt></pre><p>Thus, <tt>pCorrect</tt> now contains the address of the score, as shown in Figure 5.6.</p><p><b>Figure 5.6 </b><tt><i>pCorrect</i></tt><i> points to <tt>correct</tt>.</i></p><p><tt>pCorrect</tt> is a pointer to an integer. The integer itself, <tt>correct</tt>, is stored at <tt>0x001</tt>, and <tt>pCorrect</tt> stores the address of that integer. </p><p>The pointer does not have to be in the same method as the variable. In fact, by passing the address into a method and manipulating it with a pointer, you can achieve the same pass by reference effect you achieved using references. Listing 5.5 illustrates this point by rewriting Listing 5.4 using pointers.</p><h4> Listing 5.5 Using Pointers</h4><pre><tt>0: #include <iostream></tt><tt>1: using namespace std;</tt><tt>2: </tt><tt>3: class Game</tt><tt>4: {</tt><tt>5: public:</tt><tt>6: Game(){}</tt><tt>7: ~Game(){}</tt><tt>8: void Play();</tt><tt>9: void Score(int * correct, int * position);</tt><tt>10: </tt><tt>11: private:</tt><tt>12: int howManyLetters;</tt><tt>13: int howManyPositions;</tt><tt>14: };</tt><tt>15: </tt><tt>16: void Game::Score(int * pCorrect, int * pPosition)</tt><tt>17: {</tt><tt>18: cout << "\nBeginning score. Correct: " << * pCorrect 18a: << " Position: " << * pPosition << endl;</tt><tt>19: * pCorrect = 5;</tt><tt>20: * pPosition = 7;</tt><tt>21: cout << "Departing score. Correct: " << * pCorrect</tt><tt>21a: << " Position: " << * pPosition << endl;</tt><tt>22: }</tt><tt>23: </tt><tt>24: void Game::Play()</tt><tt>25: {</tt><tt>26: int correct = 0;</tt><tt>27: int position = 0;</tt><tt>28: </tt><tt>29: cout << "Beginning Play. Correct: " << correct</tt><tt>29a: << " Position: " << position << endl;</tt><tt>30: correct = 2;</tt><tt>31: position = 4;</tt><tt>32: cout << "Play updated values. Correct: " << correct</tt><tt>32a: << " Position: " << position << endl;</tt><tt>33: cout << "\nCalling score..." << endl;</tt><tt>34: Score(&correct, &position);</tt><tt>35: cout << "\nBack from Score() in Play. Correct: " << correct</tt><tt>35a: << " Position: " << position << endl;</tt><tt>36: }</tt><tt>37: </tt><tt>38: int main()</tt><tt>39: {</tt><tt>40: </tt><tt>41: Game theGame;</tt><tt>42: theGame.Play();</tt><tt>43: return 0;</tt><tt>44: }</tt><tt>45: Beginning Play. <tt>Correct</tt>: 0 <tt>Position</tt>: 0</tt><tt>46: Play updated values. <tt>Correct</tt>: 2 <tt>Position</tt>: 4</tt><tt>47:</tt><tt>48: Calling score...</tt><tt>49:</tt><tt>50: Beginning score. <tt>Correct</tt>: 2 <tt>Position</tt>: 4</tt><tt>51: Departing score. <tt>Correct</tt>: 5 <tt>Position</tt>: 7</tt><tt>52:</tt><tt>53: Back from <tt>Score() </tt>in Play. <tt>Correct</tt>: 5 <tt>Position</tt>: 7</tt></pre><p>The signature to <tt>Score() </tt>has changed again, as shown on lines 9 and 16. This time, <tt>pCorrect</tt> and <tt>p</tt><tt>Position</tt> are declared to be <i>pointers to </i><tt>int</tt>: They hold the address of an integer.</p><p>On line 34, <tt>Play()</tt> calls <tt>Score() </tt>and passes in the addresses of <tt>correct</tt> and <tt>position</tt> using the address-of operator (<tt>&</tt>). There is no reason to declare a pointer here. All you need is the address, and you can get that using the address-of operator.</p><p>The compiler puts this address into the pointers that are declared to be the parameters to <tt>Score()</tt>. Thus, on line 34, the variables <tt>pCorrect</tt> and <tt>pPosition</tt> are filled with the addresses of <tt>correct</tt> and <tt>position</tt>, respectively. <a name="_Toc444312808"></a></p><h3> <a name="Heading9">Dereferencing</a></h3><p>On line 18 you want to print the values of <tt>correct</tt> and <tt>position</tt>. You don't want the values of <tt>p</tt><tt>Correct</tt> and <tt>p</tt><tt>Position</tt> because these are addresses. Rather, you want to print the values at the variables whose addresses these pointers hold. </p><p>Similarly, on line 19 and 20 you want to set a new value into the variable whose address is stored in <tt>p</tt><tt>Correct</tt>. You do <i>not</i> want to write</p><pre><tt>p<tt>Correct</tt> = 5;</tt></pre><p>because that assigns <tt>5</tt> to <tt>p</tt><tt>Correct</tt>, and <tt>p</tt><tt>Correct</tt> needs an address, not a simple integer value.</p><blockquote> <hr> <p><strong>NOTE: </strong> This will compile, but it stores the address <tt>5</tt> to this pointer, which is a disaster waiting to happen.</p> <hr></blockquote><p>The <i>dereference operator</i> (<tt>*</tt>) is used. Again, this is the pointer operator, but its meaning is understood in context. </p><blockquote> <hr> <p><strong> </strong> <b>dereference</b> <b>operator</b>--The dereference operator is used to access the object to which the pointer points.</p> <hr></blockquote><p>The dereference operator returns the object whose address is stored in the pointer. Thus</p><pre><tt>*p<tt>Correct</tt></tt></pre><p>returns the variable <tt>correct</tt>. By writing</p><pre><tt>*p<tt>Correct</tt> = 5;</tt></pre><p>we store the value <tt>5</tt> in <tt>correct</tt>. </p><blockquote> <hr> <p><strong>NOTE: </strong> I read the statement</p> <hr></blockquote><pre><tt>*pCorrect = 5</tt></pre><blockquote> <hr> <p><strong>NOTE: </strong> as "set the value at <tt>pCorrect</tt> to <tt>5</tt>." That is, assign <tt>5</tt> to the variable whose address is stored in <tt>pCorrect</tt>.</p> <hr></blockquote><h3> <a name="Heading10">Getting the Operators Straight</a></h3><p>There are two hurdles for a novice programmer--syntax and semantics--and pointers challenge you on both. The syntax is different because the same symbols (<tt>&</tt> and <tt>*</tt>) are used for many different purposes. The asterisk is used for multiplication, for the declaration of a pointer, and for dereferencing:</p><pre><tt>z = x * y; // z equals x multiplied by y</tt><tt>int * ptr; // declare a pointer</tt></pre><p>*ptr = 7; // assign 7 to the dereferenced pointerSimilarly, the ampersand is used for references, for the address-of operator, and for logical AND:</p><pre><tt>if ( x && y ) // if x and also y</tt><tt>ptr = &x; // address-of operator</tt></pre><p>int & x = y; // initialize a referenceMore important than the confusing syntax is the difficulty with semantics. When I assign the value <tt>5</tt> on line 19, realize that I'm assigning <tt>5</tt> to <tt>correct</tt> in <tt>Play()</tt> indirectly through the pointer that is passed into <tt>Score</tt><tt>()</tt>. <a name="_Toc444312810"></a></p><h2> <a name="Heading11">Arrays</a></h2><p>Listing 5.6 reproduces the excerpt of <tt>Play()</tt> that we were examining in Listing 5.2 when we went off on the discussion of pointers. Remember that on line 10 we call <tt>Score() </tt>with three parameters, the first of which is our array of characters--<tt>guess</tt>. We've considered the two other parameters, <tt>correct</tt> and <tt>position</tt>, which are passed by reference using references. How is <tt>guess </tt>passed?</p><h4> Listing 5.6 Play</h4><pre><tt> 0: void Game::Play()</tt><tt>1: {</tt><tt>2: char guess[80];</tt><tt>3: int correct = 0;</tt><tt>4: int position = 0;</tt><tt>5: </tt><tt>6: //...</tt><tt>7: cout << "\nYour guess: ";</tt><tt>8: Display(guess);</tt><tt>9: </tt><tt>10: Score(guess,correct,position);</tt><tt>11: cout << "\t\t" << correct << " correct, " << position</tt><tt>11a: << " in position." << endl;</tt><tt>12: }</tt></pre><p>You must always pass arrays by reference; this is fairly easy to accomplish because C++ supports a close symmetry between arrays and pointers.</p><h2> <a name="Heading12">Excursion: Pointers and Constants</a></h2><p>Every nonstatic member method has, as a hidden parameter, a pointer called the <tt>this</tt> pointer. The <tt>this</tt> pointer has the address of the object itself. When you write</p><pre><tt>const char * Game::GetSolution() </tt><tt>{ </tt>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -