📄 ch04.htm
字号:
it? You want to generate letters at random, and you don't want the user to be able to guess the solution. The C++ library provides a method, <tt>rand()</tt>, which generates a pseudo-random number. It is pseudo-random in that it always generates numbers in the same predictable order, depending on where it starts--but they appear to be random.</p><p>You can increase the apparent randomness of the numbers that are generated if you start the random number generator with a different <i>starting</i> number (which we call a seed number) each time you run the program. </p><p>You provide <tt>rand()</tt> with a seed number by first calling <tt>srand()</tt> and passing in a value. <tt>srand</tt> (<i>s</i>eed <i>random</i>) gives the random number generate a starting point to work from. The seed determines the first random number that will be generated. </p><p>If you don't call <tt>srand()</tt> first, <tt>rand()</tt> behaves as if you have called <tt>srand()</tt><tt> </tt>with the seed value <tt>1</tt>. </p><p>You want to change the seed value each time you run the program so that you'll invoke one more library function: <tt>time()</tt>. The function <tt>time</tt> returns the system time, expressed as a large integer. </p><blockquote> <hr> <p><strong>NOTE: </strong> Interestingly, it actually provides you with the number of seconds that have elapsed since midnight, January 1, 1970, according to the system clock. This date, 1/1/1970, is known as the <i>epoch,</i> the moment in time from which all other computer dates are calculated. </p> <hr></blockquote><p>The <tt>time()</tt> function takes a parameter of type <tt>time_t</tt>, but we don't care about this because it is happy taking the <tt>NULL</tt> value instead:</p><pre><tt> srand( (unsigned)time( NULL ) );</tt></pre><p>The sequence, then, is to call <tt>time()</tt>, pass in <tt>NULL</tt>, cast the returned value to unsigned <tt>int</tt>, and pass that result to <tt>srand()</tt>. This provides a reasonably random value to <tt>srand()</tt>, causing it<tt> </tt>to initialize <tt>rand() </tt>to a nearly-random starting point.</p><blockquote> <hr> <p><strong>NOTE: </strong> Let's talk about casting a value. When you cast a value to unsigned you say to the compiler, "I know you don't think this is an unsigned integer, but I know better, so just treat it like one." In this case, <tt>time()</tt> returns the value of type <tt>time_t</tt>, but you know from the documentation that this can be treated as an unsigned integer--and an unsigned integer is what <tt>srand()</tt> expects. Casting is also called "hitting it with the big hammer." It works great, but you've disconnected the sprinklers and disabled the alarms, so be sure you know what you're doing.</p> <hr></blockquote><p>Now that you have a random number, you need to convert it into a letter in the range you need. To do this, you'll use an array of 26 characters, the letters <i>a</i>-<i>z</i>. By creating such an array, you can convert the value <tt>0</tt> to <tt>a</tt>, the value <tt>1</tt> to <tt>b</tt><i>,</i> and so on. </p><p>Quick! What is the value of <i>z</i>? If you said <tt>25</tt>, pat yourself on the back for not making the fence post error of thinking it would be <tt>26</tt>.</p><p>We'll call the character array <tt>alpha</tt>. You want this array to be available from just about anywhere in your program. Earlier we talked about local variables, variables whose scope is limited to a particular method. We also talked about class member variables, which are variables that are scoped to a particular object of a class. A third alternative is a <i>global variable</i>.</p><blockquote> <hr> <p><strong> </strong> <b>global variable</b>--A variable with no limitation in its scope--visible from anywhere in your program</p> <hr></blockquote><p>The advantage of global variables is that they are visible and accessible from anywhere in your program. That is also the bad news--and C++ programmers avoid global variables like the plague. The problem is that they can be changed from any part of the program, and it is not uncommon for global variables to create tricky bugs that are terribly difficult to find. </p><p>Here's the problem: You're going along in your program and everything is behaving as expected. Suddenly, a global variable has a new and unexpected value. How'd that happen? With global variables, it is difficult to tell because they can be changed from just about anywhere.</p><p>In this particular case, although you want <tt>alpha</tt> to be visible throughout the program, you don't want it changed at all. You want to create it once and then leave it around. That is just what constants are for. Instead of creating a global variable, which can be problematic, you'll create a <i>global constant</i>. Global constants are just fine:</p><pre><tt>const char alpha[] = "abcdefghijklmnopqrstuvwxyz";</tt></pre><blockquote> <hr> <p><b>global constant</b>--A constant with no limitation in its scope--visible from anywhere in your program.</p> <hr></blockquote><p>This creates a constant named <tt>alpha</tt> that holds 27 characters (the characters <i>a</i>-<i>z</i> and the terminating <tt>NULL</tt>). With this in place,</p><pre><tt>alpha[0]</tt></pre><p>evaluates to <i>a</i>, and</p><pre><tt>alpha[25]</tt></pre><p>evaluates to <i>z</i>.</p><blockquote> <hr> <p><strong>NOTE: </strong> We'll include the declaration of <tt>alpha</tt> in a new file called definedValues.h, and we'll <tt>#include</tt> that file in any file that needs to access <tt>alpha</tt>. This way, we create one place for all our global constants (all our defined values), and we can change any or all of them by going to that one file.</p> <hr></blockquote><h4> Listing 4.5 Adding Characters to the Array</h4><pre><tt>0: for ( i = 0; i < howManyPositions; )</tt><tt>1: {</tt><tt>2: int nextValue = rand() % (howManyLetters);</tt><tt>3: char c = alpha[nextValue];</tt><tt>4: if ( ! duplicatesAllowed && i > 0 )</tt><tt>5: {</tt><tt>6: int count = howMany(solution, c);</tt><tt>7: if ( count > 0 )</tt><tt>8: continue;</tt><tt>9: }</tt><tt>10: // add to the array</tt><tt>11: solution[i] = c;</tt><tt>12: i++;</tt><tt>13: }</tt><tt>14: solution[i] = '\0';</tt><tt>15: </tt><tt>16: }</tt></pre><p> On line 0 you create a <tt>for</tt> loop to run once for each position. Thus, if the user has asked for a code with five positions, you'll create five letters.</p><p>On line 2 you call <tt>rand()</tt>, which generates a random value. You use the modulus operator (<tt>%</tt>) to turn that value into one in the range <tt>0</tt> to <tt>howManyLetters</tt><tt>-1</tt>. Thus, if <tt>howManyLetters</tt> is <tt>7</tt>, this forces the value to be <tt>0</tt>, <tt>1</tt>, <tt>2</tt>, <tt>3</tt>, <tt>4</tt>, <tt>5</tt>, or <tt>6</tt>. </p><p>Let's assume for the purpose of this discussion that <tt>rand()</tt> first generates the value <tt>12</tt>, and that <tt>howManyLetters</tt> is <tt>7</tt>. How is the value <tt>12</tt> turned into a value in the range <tt>0</tt> through <tt>6</tt>? To understand this, you must start by examining <i>integer division</i>.</p><p>Integer division is somewhat different from everyday division. In fact, it is exactly like the division you originally learned in fourth grade. "Class, how much is 12 divided by seven?" The answer, to a fourth grader, is "One, remainder five." That is, seven goes into 12 exactly once, with five "left over."</p><blockquote> <hr> <p><strong> </strong> <b>integer division</b>--When the compiler divides two integers, it returns the whole number value and loses the "remainder."</p> <hr></blockquote><p>When an adult divides 12 by 7, the result is a real number (1.714285714286). Integers, however, don't have fractions or decimal parts, so when you ask a programming language to divide two integers, it responds like a fourth grader, giving you the whole number value without the remainder. Thus, in integer math, 12/7 returns the value <tt>1</tt>.</p><p>Just as you can ask the fourth grader to tell you the remainder, you can use the modulus operator (<tt>%</tt>) to ask your programming language for the remainder in integer division. To get the remainder, you take 12 modulus 7 (<tt>12 % 7</tt>), and the result is 5. The modulus operator tells you the remainder after an integer division.</p><p>This result of a modulus operator is always in the range zero through the operand minus one. In this case, zero through seven minus one (or zero through six). If an array contains seven letters, the offsets are <tt>0</tt>-<tt>6</tt>, so the modulus operator does exactly what you want: It returns a valid offset into the array of letters.</p><p>On line 3 you can use the value that is returned from the modulus operator as an offset into <tt>alpha,</tt> thus returning the appropriate letter. If you set <tt>howManyLetters</tt> to <tt>7</tt>, the result will be that you'll always get a number between zero and six, and, therefore, a letter in the range <i>a</i> through <i>g</i>--exactly what you want!</p><p>Next, on line 4 you check to see whether you're allowing duplicates in this game. If not, enter the body of the <tt>if</tt> statement.</p><p>Remember, the bang symbol (<tt>!</tt>) indicates <i>not</i>, so </p><pre><tt>if ( ! duplicatesAllowed )</tt></pre><p>evaluates <tt>true</tt> if <tt>duplicatesAllowed</tt> evaluates <tt>false</tt>. Thus, if not, <tt>duplicatesAllowed</tt> means "if we're not allowing duplicates." The second half of the and statement is that <i>i</i> is greater than zero. There is no point in worrying about duplicates if this is the first letter you're adding to the array.</p><p>On line 6 you assign to the integer variable <tt>count</tt> the result of the member method <tt>howMany()</tt>. This method takes two parameters--a character array and a character--and returns the number of times the character appears in the array. If that value is greater than zero, this character is already in the array and the <tt>continue</tt> statement causes processing to jump immediately to the top of the <tt>for</tt> loop, on line 0. This tests <i>i</i>, which is unchanged, so proceed with the body of the <tt>for</tt> loop on line 2, where you'll generate a new value to try out.</p><p>If <tt>howMany()</tt> returns zero, processing continues on line 11, where the character is added to <tt>solution</tt> at offset <tt>i</tt>. The net result of this is that only unique values are added to the solution if you're not allowing duplicates. Next, <tt>i</tt> is incremented (<tt>i++</tt>) and processing returns to line 0, where <tt>i</tt> is tested against <tt>howManyPositions</tt>. When <tt>i</tt> is equal to <tt>howManyPositions</tt>, the <tt>for</tt> loop is completed.</p><p>Finally, on line 14 you add a <tt>NULL</tt> to the end of the array to indicate the end of the character array. This enables you to pass this array to <tt>cout</tt>, which prints every character up to the <tt>NULL</tt>.</p><blockquote> <hr> <p><strong>NOTE: </strong> To designate a <tt>NULL in a character array</tt>, use the special character <tt>'\0'</tt>. To designate <tt>NULL</tt> otherwise, use the value <tt>0</tt> or the constant <tt>NULL</tt>. <a name="_Toc444149722"></a><a name="_Toc450554056"></a></p> <hr></blockquote><h2> <a name="Heading28">Examining the Defined Values File</a></h2><p>Take a look at Listing 4.6, in which we declare our constant array of characters <tt>alpha</tt><i>.</i></p><h4> Listing 4.6 definedValues.h</h4><pre><tt>0: #ifndef DEFINED_VALUES</tt><tt>1: #define DEFINED_VALUES</tt><tt>2: </tt><tt>3: #include <iostream></tt><tt>4: using namespace std;</tt><tt>5: </tt><tt>6: const char alpha[] = "abcdefghijklmnopqrstuvwxyz";</tt><tt>7: </tt><tt>8: const int minPos = 2;</tt><tt>9: const int maxPos = 10;</tt><tt>10: const int minLetters = 2;</tt><tt>11: const int maxLetters = 26;</tt><tt>12: </tt><tt>13: #endif</tt></pre><p>This listing introduces several new elements.On line 0 you see the precompiler directive <tt>#ifndef</tt>. This is read "if not defined," and it checks to see whether you've already defined whatever follows (in this case, the string <tt>DEFINED_VALUES</tt>). </p><p>If this test fails (if the value <tt>DEFINED_VALUES</tt> is already defined), nothing is processed until the next <tt>#endif</tt> statement, on line 13. Thus, the entire body of this file is skipped if <tt>DEFINED_VALUES</tt> is already defined.</p><p>If this is the first time the precompiler reads this file, that value will not yet be defined; processing will continue on line 2, at which point it will be defined. Thus, the net effect is that this file is processed exactly once. </p><p>The <tt>#ifndef/#define</tt> combination is called an <i>inclusion guard</i>, and it guards against multiple inclusions of the same header file throughout your program. Every header file needs to be guarded in this way.</p><blockquote> <hr> <p><strong>NOTE: </strong> <i>Inclusion guards</i> are added to header files to ensure that they are included in the program only once.</p> <hr></blockquote><p>We intend to include the definedValues.h header file into all our other files so that it constitutes a global set of definitions and declarations. By including, for example, iostream.h here, we don't need to include it elsewhere in the program.</p><p>On line 6 you declare the constant character array that was discussed earlier. On lines 8-11 you declare a number of other constant values that will be available throughout the program. </p><CENTER><P><HR> <A HREF="../index.htm"><IMG SRC="../button/contents.gif" WIDTH="128"HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR> <BR><p></P><P>© <A HREF="../copy.htm">Copyright 1999</A>, Macmillan Computer Publishing. Allrights reserved.</p></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -