2

Greetings!

Lets cut the excessive intro this time and get straight to the point.

I have a problem in C++ using the isalnum method.

the code:

int playAgainst = 0;
do
{
    cout << "Who do you want to play against?(1/2)\n";
    cout << "1: Human player\n";
    cout << "2: Computer player\n";
    cin >> playAgainst
} while(!isalnum(playAgainst) && playAgainst != 0);

As seen in the code, I'm providing the user with a choice. Play against human or play against a computer.

What I want is, as long as the user enters anything else then an integer value(cin >> playAgainst) to repeat the question. However, If i enter a char, or string value, it keeps looping endlessly. I am not 100% sure, but it would be obvious, if the problem is, that the non int value is already saved as the value for playAgainst.. How can I check in this bit of code if the input value is int before saving it?

Or is the only possibility to save as a char/string and then check?

If the latter is the case, a new problem arises. isalnum only accepts int as parameter, atleast from what I know. How will I check if that string or char is an int?

Thank you for taking the time to read. And hopefully Ill be accepting a reply as answer soon ^^

Thanks everyone for the answers. I have gotten what I wanted, and everything has been solved.

The reason I chose for the accepted answer, is well... because initially, it made my code work the way I want it to. I want to accept multiple answers though..

Joey Roosing
  • 2,145
  • 5
  • 25
  • 42

6 Answers6

3

Remove the isalnum() check. cin >> playAgainst will convert anything except a number to zero, which will be caught by the second check. Note that this is only an option because zero is not a valid input.

isalnum() is useful if you're interpreting the characters yourself, but in this case the stream has already done that for you.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
2

Make playAgainst a char and compare against '0', not 0. Right now, the user has to enter the ASCII (or whatever your character set is) code for the character '1' or '2'.

isalnum won't work on ints outside the valid range of char, except for EOF. (The fact that it takes an int argument is a leftover from C, which has different integer promotions rules than C++. to accomodate for EOF.)

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
  • while(playAgainst != '1' || playAgainst != '2') This works. Although its not exactly what I was hoping for. (ex C#- er :P) Thanks though! What I'm afraid of in this case is, what if I add 800 more options(fiction..) i have to write alooot of || operations. In that case, checking if the value is numeric, and <= 800 would be a better solution? – Joey Roosing Mar 30 '11 at 11:31
  • You can't fit 800 values into a typical `char` anyway. If you do want an `int`, then `isalnum` is no use. `cin >> i` will fail if you don't enter a valid integer anyway. – Fred Foo Mar 30 '11 at 11:33
  • it was a fictional question, perhaps a bit silly one. And that is exactly the problem I was worried about. That the value has to be int. could try a try-catch block to solve this. – Joey Roosing Mar 30 '11 at 11:35
  • @Joey Actually that's what isalnum does. It just checks if the ASCII value is within certain intervals a-z or A-Z or 0-9. And that's it, no fancy magic there. – Lundin Mar 30 '11 at 11:38
  • 1
    @larsmans The fact that isalnum takes an int is not a leftover from C (except in the sense that all of is leftover from C). isalnum takes an int so that it can take EOF, as well as a character. (It returns false for EOF.) Also, the integer promotion rules of C and of C++ are exactly the same. Maybe you're thinking of the fact that '0' is an int in C, but a char in C++. – James Kanze Mar 30 '11 at 17:03
  • @James Kanze: The fact that C I/O functions use `int`s instead of `char`s is so the functions can provide an invalid value (`EOF`). Are you sure that also applies to the `` functions? I've seen them implemented by arrays, and IIRC `EOF` was -1 on that system. – David Thornley Mar 30 '11 at 17:33
  • @David Thornley Yes. And the usual implementation of the `` functions (back when they were almost always macros) was `(__ctype[(ch) + 1] & M_xxx) != 0`. The `+ 1` was there to support `EOF` (which is almost universally -1, although the only formal requirement is that it be negative). – James Kanze Mar 30 '11 at 17:51
  • @James Kanze: thanks! I'd never noticed the `EOF` handling in ``. Answer updated. – Fred Foo Mar 31 '11 at 09:00
1

This is how the compiler will implement isalnum:

int isalnum (int ch)
{
  return (ch >= 'a' && ch <= 'z') || 
         (ch >= 'A' && ch <= 'Z') || 
         (ch >= '0' && ch <= '9');
}

So you might as well write that code snippet in your own code, and it will be equivalent with an inlined version of isalnum.

Lundin
  • 195,001
  • 40
  • 254
  • 396
  • That doesn't really answer the question: why doesn't `isalnum` work in this situation? – Mike Seymour Mar 30 '11 at 11:46
  • Thanks for clarifying how isalnum is intepreted. Always usefull to know. However Mike Seymour is right. But thats fine. I have learned a good amount from all the answers combined! Thanks. – Joey Roosing Mar 30 '11 at 11:53
  • Indeed, it doesn't answer the question, though it is relevant as clarification. I'd post it as a comment, if comments didn't have inferior code formatting options. So blame the SO site. – Lundin Mar 30 '11 at 12:52
  • 2
    I've yet to see a library which used that solution. Historically, isalnum was a macro, and it only refered to its argument once. Something like `(__ctype[(ch) + 1] & M_ALNUM) != 0`. – James Kanze Mar 30 '11 at 17:04
  • @James Ironically, this was actually taken straight from an embedded systems compiler source code. Perhaps they rely on their optimizer to make something prettier out of the code. Or more likely, they just didn't want to allocate a lookup table. Actually I doubt that look-up version will be any notable improvement speed-wise, as you will have to perform array indexing, addition, AND, and the compared against zero. – Lundin Mar 30 '11 at 20:01
  • @Lundin The use of the lookup table is traditional; it's certainly not the only solution. But it was the one used in the early C compilers from Bell Labs, and for the most part, everyone has followed suite. Today, the requirements of localization argue even more strongly for the table solution, with an additional indirection for the table. And the motivation for the table was never performance, IIUC. It's the fact that a function like the above doesn't work for most encodings (EBCDIC was the original motivation, but Latin-1 is widely used today). – James Kanze Mar 31 '11 at 08:16
1

It's because you don't clear the buffer. When the input is invalid, you need to clear it from the buffer, and then you can move on to the next input, else you're trying to extract the same input every time (which fails, because it's the same bad input), and thus enter an infinite loop.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • This is a great answer. Thank you very much. I didnt even think about clearing the buffer. Wake-up slap is well appreciated. – Joey Roosing Mar 30 '11 at 11:59
1

The problem is that you're inputing an int, and not a char. And if the text in the input isn't an int, then the input fails. In which case, playAgainst isn't modified, and the failure is memorized in std::cin until you explicitly clear the error. And inputting from a stream in an error state is a no-op. What you probably want to do is

  1. Input a single character: if you don't want to skip spaces, using `std::cin.get( ch )` or `ch = std::cin.get()`. (In the latter case, `ch` should be an `int`, since it must also handle `EOF`. On the other hand, you can use `::isalnum` on it directly, which you can't do if `ch` is a `char`.
  2. Fully check for valid input: not just `::isalnum`, but rather whether the input is a legal selector in your list. Something along the lines of:
        ch != EOF && std::find( legalChars.begin(), legalChars.end(), (char)ch ) != legalChars.end()
    
  3. In case of error, clear any remaining input, say with:
               std::cin.ignore(INT_MAX, '\n');
    

In practice, you'll probably want to treat EOF differently from an erroneous command. (If you don't clear the input after EOF, you won't be able to read anything else. But presumably, if you got EOF, it's because the user gave up, and doesn't want to try any more.)

Finally, it's probably preferrable to keep all of the information in a common location, using a table of:

struct Command
{
    char        op;
    char const* prompt;
    void (*     func)();
};

You then loop over a table of these to output the prompt, search it to see if the character was legal, and finally, call the function on the entry you found. Or define an abstract base class, a concrete class deriving from it for each command, and use an std::map<char, AbstractBase*> for the mapping, etc. Very C++, but perhaps a bit overkill for such a simple case.

James Kanze
  • 150,581
  • 18
  • 184
  • 329
0

Why not use isdigit().

Sadique
  • 22,572
  • 7
  • 65
  • 91
  • @Acme Not sure, but wether I change it or not, the produced result is exactly the same, I do know the difference though, I should probably use isdigit, since I dont need to check for upper/lower case – Joey Roosing Mar 30 '11 at 11:27
  • @Joey Roosing :: Simply use `isdigit()` to make sure its a number then test for `<1` or `>2` depending on the way you want your loop to work. – Sadique Mar 30 '11 at 11:29
  • And make sure you don't confuse numeric and character values; to use `isdigit()`, you'd have to read a character not an integer, and compare with `'1'` and `'2'`. – Mike Seymour Mar 30 '11 at 11:38
  • @Acme : I am. However this does not much in solving my problem. Because in the code example, I use cin >> playAgainst, which determines the value of playAgainst, before I can do a check, since isdigit(cin >> playAgainst) will produce an error. It will endlessly loop, because the value thats supposed to be int, is not int – Joey Roosing Mar 30 '11 at 11:43
  • @Mike Seymour : Thank you. A logical answer indeed! I suppose having a char is the only solution to what I really want to accomplish. Thanks :) aswell as Acme! – Joey Roosing Mar 30 '11 at 11:45
  • @Joey: no, it's not the only solution; my answer gives an alternative which might be simpler. – Mike Seymour Mar 30 '11 at 11:47