C++ Demystified(c) A Self-Teaching Guide

You may legitimately be wondering why I am devoting an entire section of this chapter to reading a character. After all, reading a character usually is relatively simple. You just use the cin object and the stream insertion operator (>>) as in the following code fragment:

char grade; cout << "Enter a grade: "; cin >> grade;

However, in programming, as in life, matters rarely are as simple as they first appear to be, and this is no exception. The seemingly minor detail of the ENTER key being pressed to end input gives rise to several interesting, and fortunately quite solvable, problems.

The Press Any Key to Continue Problem

The preceding code fragment had the user enter a character which was then assigned to a character variable. However, the purpose of a user inputting a character is not always to assign that input to a variable.

For example, programs often prompt the user to press any key to continue. Indeed, a standard technical support joke concerns a user who complains that their keyboard does not have an any key. Of course, any key means any key on the keyboard, including the ENTER key.

While this joke may be entertaining, implementing the press any key to continue functionality to include the ENTER key is more complicated than is first apparent.

Let s examine the following program:

#include <iostream> using namespace std; int main(void) { char ch; do { cout << "Press Q or q to quit, any other key to continue: "; cin >> ch; if (ch != 'Q' && ch != 'q') cout << "You want to continue?\n"; else cout << "You quit"; } while (ch != 'Q' && ch != 'q'); return 0; }

The program works fine if you press Q or q to quit. The program also works fine if you press any other printable character to continue, such as a letter other than Q or q, a digit, or a punctuation mark.

However, what if you press the ENTER key to continue? The answer is: nothing happens; cin is still waiting for you to enter something. You have to enter a printable character to continue. The reason is that the stream extraction operator (>>) ignores all leading whitespace characters , such as the newline character caused by pressing the ENTER key.

The cin. get Function

In Chapter 10, we discussed the getline function of the cin object. The getline function is called a member function. A member function is a function that is not called by itself, as is, for example, the pow function we used in Chapter 4 to raise a number to a certain power. Instead, a member function is called from an object. Here, getline is a member function of cin. It is called from cin, and separated by a dot, as in cin.getline( name , 80).

Here we will use another member function of cin, get. The get member function was briefly explained in Chapter 10. There, the get function, like the getline function, could be called with two or three arguments, the first argument being a character array.

In addition to the two and three argument versions, the get member function also may be called with no arguments or with one argument. Unlike the two and three argument versions, the zero and one argument versions of the get member function are used to read a single character rather than a character array. The one-argument version will be discussed in this section. The no-argument version will be discussed in the next section, titled The cin.ignore Function.

The data type of the one argument is a character, and the value of this argument changes to whichever keyboard key the user pressed. This is true even if the keyboard key is the ENTER key. Thus, the get member function, unlike the cin object with the stream insertion operator (<<), may be used to assign to a character variable the newline character resulting from pressing the ENTER key.

We will make one change to the previous program. We will change the statement cin >> ch to cin.get(ch), so the program now reads as follows :

#include <iostream> using namespace std; int main(void) { char ch; do { cout << "Press Q or q to quit, any other key to continue: "; cin.get(ch); if (ch != 'Q' && ch != 'q') cout << "You want to continue?\n"; else cout << "You quit"; } while (ch != 'Q' && ch != 'q'); return 0; }

Now, as the following input and output show, the program works if you press the ENTER key to continue:

Press Q or q to quit, any other key to continue: You want to continue? Press Q or q to quit, any other key to continue: q You quit

However, as shown by the following input, if you press a printable character to continue, you are not able to input at the next prompt, which is seemingly skipped :

Press Q or q to quit, any other key to continue: x You want to continue? Press Q or q to quit, any other key to continue: You want to continue? Press Q or q to quit, any other key to continue:

As this output reflects, by curing the problem of a whitespace character not being recognized, we have introduced a new problem when a printable character is inputted.

A description of why this new problem occurred first requires a brief explanation of the term input buffer. The input buffer is an area of memory that stores input, such as from the keyboard, until that input is assigned, such as by cin and the stream extraction operator (>>) or by the get or getline member functions of the cin object.

When the loop begins, the input buffer is empty. Accordingly, execution of the loop halts at the statement cin.get(ch) until you enter some input.

As shown in Figure 12-1, if you press the ENTER key, the only character in the input buffer is the newline character. That character, being the first (and only) one in the input buffer, is removed from the input buffer to be assigned to the variable ch. Thus, at the next iteration of the loop, the input buffer again is empty.

Figure 12-1: The input buffer when only the enter key is pressed

By contrast, if you type the letter x and then press the ENTER key to end input, then, as depicted in Figure 12-2, the input buffer contains not one but two characters, x and the ENTER key, shown by the newline character.

Figure 12-2: The input buffer after typing the letter x and pressing the enter key

The get member function of the cin object removes the first character, x, from the input buffer and assigns that value to the variable ch. As shown in Figure 12-2, the newline character still remains in the input buffer.

Since the newline character remains in the input buffer, at the next iteration of the loop, you do not have the opportunity to enter input. Instead, the get member function, which reads whitespace as well as printable characters, removes the newline character from the input buffer and assigns that newline character to the variable ch. Now the input buffer is empty, so at the next loop iteration you will have the opportunity to enter input.

The cin.ignore Function

The solution is to clear the newline character out of the input buffer before calling the getline function. You do this by using the ignore member function of the cin object.

The ignore member function, like the get and getline member functions, also is overloaded. It can be called with no arguments, one argument, or two arguments.

Calling the ignore function with no arguments will cause the next character in the input buffer to be read, and then discarded ”that is, it won t be assigned to anything. This is exactly what we want. We don t need to assign the newline character left over in the input buffer into some variable. Rather, we just need to get rid of it.

Note  

The one- and two-argument versions of the ignore member function are used with character arrays instead of with an individual character. In the one argument version of the ignore member function, the one argument is the maximum number of characters to be removed from the input buffer. For example, the statement cin.ignore(80) removes up to the next 80 characters from the input buffer. In the two-argument version, the second argument is a character which, if encountered before the number of characters specified in the first argument, causes the removal from the input buffer to stop. Thus, the statement cin.ignore(80, '\n') skips the next 80 characters or until a newline is encountered, whichever comes first.

You also could use the get member function with no arguments to the same effect as the ignore member function with no arguments. The following two statements do the same thing:

cin.ignore(); cin.get();

This section will use the no-argument version of the ignore member function, but you could substitute the no argument version of the get member function to the same effect.

Accordingly, the following program modifies the previous one by following the call of the get member function with a call to the ignore member function:

#include <iostream> using namespace std; int main(void) { char ch; do { cout << "Press Q or q to quit, any other key to continue: "; cin.get(ch); cin.ignore(); if (ch != 'Q' && ch != 'q') cout << "You want to continue?\n"; else cout << "You quit"; } while (ch != 'Q' && ch != 'q'); return 0; }

Now, as the following input and output show, the program works if you press a printable character to continue:

Press Q or q to quit, any other key to continue: x You want to continue? Press Q or q to quit, any other key to continue: q You quit

The reason this works is that the no-argument ignore member function removes the next character from the input buffer. As Figure 12-3 shows, this removes the leftover newline character from the input buffer.

Figure 12-3: The input buffer after cin.ignore()

However, as the following input and output show, if you press the ENTER key to continue, you have to do so twice since the first attempt seems to be skipped:

Press Q or q to quit, any other key to continue: You want to continue? Press Q or q to quit, any other key to continue: q You quit

This is getting frustrating! We can get either printable or whitespace input to work properly, but not both at the same time.

It is normal to experience frustration in programming. It is how you react to the frustration that is important. Persistence pays in programming, both figuratively and literally. Almost always there is a solution, and there is one here. First, though, you should understand the problem you are trying to solve.

The reason you have to press the ENTER key twice after we added the call to the no-argument ignore member function is that the no argument ignore member function removes the next character from the input buffer. However, as shown in Figure 12-4, there is nothing in the input buffer when the no-argument ignore member function is called. Accordingly, the ENTER key needs to be pressed a second time to put something in the input buffer for the no-argument ignore member function to remove.

Figure 12-4: Why pressing the enter key twice is required

As the previous programs and sample inputs and outputs show, we need to call the ignore member function after the get member function if, and only if, a newline character remains in the input buffer. Therefore, the suggested solution is to use an if statement to call the ignore member function only if the character inputted by the user was not a newline character. If it was, then the input buffer would be empty after the newline character was assigned to the variable ch, so the ignore member function should not be called. However, if a printable character was entered, then the newline character would remain in the input buffer after the printable character is assigned to the variable ch, so the ignore function should be called. The following code fragment illustrates this:

if (ch != '\n') cin.ignore();

The following program implements this solution, modifying the previous one by calling the ignore member function only if the input was not a newline character:

#include <iostream> using namespace std; int main(void) { char ch; do { cout << "Press Q or q to quit, any other key to continue: "; cin.get(ch); if (ch != '\n') cin.ignore(); if (ch != 'Q' && ch != 'q') cout << "You want to continue?\n"; else cout << "You quit"; } while (ch != 'Q' && ch != 'q'); return 0; }

Now the program works properly regardless of whether a printable character or the ENTER key is used to continue.

Note  

The condition for both the if and while statements is ch != 'Q' && ch != 'q' . A common rookie mistake is to use the operator when the && should be used, and vice versa. Were the operator used, as in ch != 'Q' && ch != 'q' , the condition would always be true. A variable can have only one value at a time, so the value of ch will never equal Q or q, since it cannot equal both at the same time. Of course, the expression could be recast using the operator as ! (ch == 'Q' && ch == 'q') .

Combining Use of cin , cin.get , and cin.getline

The problem of a newline character remaining in the input buffer is not limited to the situation in which the ENTER key is pressed in response to a prompt to press any key to continue. This problem also arises when cin and the get or getline member functions are used together in one program, since the ENTER key also is used to end input.

The following program is an example of cin and the getline member function used together in one program:

#include <iostream> using namespace std; int main(void) { char name[80]; int courseNum; cout << "Enter course number: "; cin >> courseNum; cout << "Enter your name: "; cin.getline(name, 80); cout << "Course number is: " << courseNum << endl; cout << "Your name is: " << name << endl; return 0; }

Here is some sample input and output:

Enter course number: 802 Enter your name: Course number is: 802 Your name is:

You did not have the opportunity to enter a name. The reason is similar to the situation in the last section in which the user did not have a chance to enter input.

As Figure 12-5 shows, when you typed 802 and then pressed the ENTER key to end input, the input buffer contained not only the number 802 but also the newline character resulting from pressing the ENTER key. The cin with the stream extraction operator (>>) removed from the input buffer and then assigned to the variable courseNum, everything up to, but not including, the newline character, which remained in the input buffer.

Figure 12-5: The input buffer after cin >> courseNum followed by cin.getline(name,80)

Since the newline character remains in the input buffer, you do not have the opportunity to enter input at the next statement: the call of the getline member function. Instead, as Figure 12-5 depicts, the getline member function, which reads whitespace as well as printable characters, removes the newline character from the input buffer and assigns that newline character to the character array name. While the character array name has been assigned a value, it is not a printable character, so it appears in the output that name has no value.

The solution, as pointed out in the previous section, is to follow use of the cin object and the stream extraction operator (>>) with the no-argument version of the ignore (or get ) member function. However, unlike the previous section, there is no need for an if statement, since a cin statement with the stream extraction operator (>>) always leaves a newline character in the input buffer. The following modification of the previous program implements this solution by adding a call to the no-argument version of the ignore member function after use of the cin object with the stream extraction operator (>>):

#include <iostream> using namespace std; int main(void) { char name[80]; int courseNum; cout << "Enter course number: "; cin >> courseNum; cin.ignore(); cout << "Enter your name: "; cin.getline(name, 80); cout << "Course number is: " << courseNum << endl; cout << "Your name is: " << name << endl; return 0; }

As the following sample input and output show, the program now works properly:

Enter course number: 802 Enter your name: Jeff Kent Course number is: 802 Your name is: Jeff Kent

Rules to Live By

Here are three simple rules that will avoid the problem caused by a newline character left in the input buffer ( assuming , of course, you do not have a programming reason to keep the newline character in the input buffer).

Rule #1: Always follow cin and the stream extraction operator (>>) with the ignore member function.

Explanation: The cin object with the stream extraction operator (>>) always leaves a newline character in the input buffer. Clear it out with the ignore member function. For example:

char ch; cin >> ch; cin.ignore();

Rule #2: Don t follow the getline member function with the no-argument ignore member function.

Explanation: The getline member function removes the newline character that terminated input from the input buffer. Therefore, it should not be followed with the ignore member function.

Rule #3: After using the get member function with one argument, such as cin.get(ch), check to see if you have a newline character in the input buffer. If you do, clear it with the ignore member function. If you don t, don t.

Explanation: The get member function with one argument being a single character will leave a newline character in the input buffer if you type a character and then press ENTER . It won t if you just press ENTER . Therefore, you need to check to see if you need to clear the buffer. For example:

char ch; cin.get(ch); if (ch != '\n') cin.ignore();

Категории