6

I'll be flat out honest, this is a small snippet of code I need to finish my homework assignment. I know the community is very suspicious of helping students, but I've been racking my head against the wall for the past 5 hours and literally have accomplished nothing on this assignment. I've never asked for help on any assignments, but none have given me this much trouble.

All I'm having trouble with is getting the program to strip the leading whitespace out. I think I can handle the rest. I'm not asking for a solution to my overall assignment, just a nudge on this one particular section.

I'll post the full assignment text here, but I am NOT posting it to try to get a full solution, I'm only posting it so others can see the conditions I have to work with.

"This homework will give you more practice in writing functions and also how numbers are read into a variable. You need to write a function that will read an unsigned integer into a variable of type unsigned short int. This will have a maximum value of 65535, and the function needs to take care of illegal numbers. You can not use "cin >>", inside the function. The rules for numeric input are basically as follows:

1) skip all leading white spaces 2) first character found must be numeric else an error will occur 3) numeric characters are then processed one at a time and combine with number 4) processing stops when non-numeric found

We will follow these rules and also add error handling and overflow. If an illegal entry is made before a numeric than an error code of "1" will be sent back, if overflow occurs, that is number bigger then 65535, then error code of "2" will be sent back. If no error then "0" is sent back.

Make sure the main function will continue to loop until the user enters a “n” or “N” for NO, the main should test the error code returned from the function called “ReadInt” and display appropriate error messages or display the number if there is no error. Take care in designing the “ReadInt” function, it should be value returning and have a reference parameter. The function needs to process one character at a time from the input buffer and deal with it in a correct fashion. Once the number has been read in, then make sure the input buffer is empty, otherwise the loop in main may not work correct. I know this is not how the extraction works, but lets do it this way.

You do not need to turn in an algorithm with this assignment, but I would advise you to write one. And the debugger may prove helpful as well. You are basically rewriting the extraction operator as it works on integers."

A majority of my code won't make sense as I've been deleting things and adding things like crazy to try everything I can think of.

#include <iostream>
#include <CTYPE.h>

using namespace std;

int ReadInt (unsigned short int &UserIn);

int main()
{
    int Error;
    unsigned short int UserInput;
    char RepeatProgram;

    do
    {
        Error=ReadInt(UserInput);

        if (Error==0)
            cout << "Number is " << UserInput << endl;

        else if (Error==1)
            cout << "Illegal Data Entry\n";

        else if (Error==2)
            cout << "Numerical overflow, number too big\n";

        cout << "Continue?  n/N to quit: ";
        cin >> RepeatProgram;

        cout << endl;

    } while (RepeatProgram!='N' && RepeatProgram!='n');

}

int ReadInt (unsigned short int &UserIn)
{
    int Err=0;
    char TemporaryStorage;
    long int FinalNumber=0;

    cout << "Enter a number: ";

    //cin.ignore(1000, !' '); this didn't work

    cin.get(TemporaryStorage);

    cout << TemporaryStorage;//I'm only displaying this while I test my ideas to see if they are working or not, before I move onto the the next step

    cout << endl;

    return Err;
}

I really appreciate any help I may get and hope I don't give the impression that I'm looking for a full free solution to the whole problem. I want to do this on my own, I'm just lot on this beginning.

JosephTLyons
  • 2,075
  • 16
  • 39
  • http://www.cplusplus.com/reference/istream/istream/ignore/ Here you can find out how to ignore any number of characters. – Neil Kirk Nov 06 '15 at 08:37
  • When you read numbers and strings from a C++ stream (like `std::cin`) the input operator `>>` skip leading white-space. – Some programmer dude Nov 06 '15 at 08:38
  • 2
    There is a lot of irrelevant information in your question which will make it harder for people to help as usually they will skim and miss the important bits. – Neil Kirk Nov 06 '15 at 08:39
  • Also, that's a lot of text for a (possibly) simple problem. We don't really need to read all of that, please try to cut it down to the exact problem you're having and how you tried to solve it. If we want more information we will ask for it. – Some programmer dude Nov 06 '15 at 08:39
  • Sorry guys, I'm not used to StackOverFlow, Im a huge forum guy and the forums I'm used to usually require tons of details, I merely wanted to prove I had been trying to work on it on my own before asking and I wanted to post the assignment so others didn't post solutions that were outside the scope of what I could use. – JosephTLyons Nov 06 '15 at 08:41
  • I'll try to skim it down – JosephTLyons Nov 06 '15 at 08:43
  • @joe_04_04: Hey, congrulations! Finally a student that understands that he/she must post what he/she has got until now, and not just copy-paste the teacher's assignment ;). – 3442 Nov 06 '15 at 08:44
  • The worst thing is we have done much more difficult assignments than this and I've had no issues, but something about working this one out has my head entirely screwed up. I feel very silly. – JosephTLyons Nov 06 '15 at 08:48
  • 1
    @joe_04_04: Don't feel like that. Trust me when I say that the IOStreams library is badly-designed, counter-intuitive, and a complete insane crap, but we have no way to change it. There's a common quote in SO that says `No one in its sane mind would design iostreams as it was designed. It's full of legacy and dirty workaround.s`. – 3442 Nov 06 '15 at 09:19
  • @KemyLand: Those ignorant might agree with your assessment. Those understanding the requirements and also constraints under which it was created (notably absense of variadic templates) seem to have a differnt assessment. ... and if it is so bad, how cone there never was a remotely viable proposal to replace it? – Dietmar Kühl Nov 06 '15 at 09:48
  • @DietmarKühl: There never wasn't a (standard) replacement proposal because of this preciousness called "backwards compatibility". I don't know why, but it appears that standard committes put backwars-compatibility (and all their implications) over the overall need for something better, one of the reasons, IMHO, the "standard committee" should actually be the community as a whole, and not a select group of corporations and individuals, that, even if they have an exceptional knowledge/experience with the language, are still biased towards their own needs. – 3442 Nov 06 '15 at 09:53
  • @DietmarKühl: BTW, you may want to read [this](http://stackoverflow.com/questions/2753060/who-architected-designed-cs-iostreams-and-would-it-still-be-considered-wel). – 3442 Nov 06 '15 at 10:02
  • @KemyLand: you might want to research my background and to find that I'm pretty aware of the history and tales around IOStreams. I have actively refuted P.J.Plauger's assertions that IOStreams have to be inefficient (simply by creating an implementation which was more efficient than the equivalents). PJ.'s C/C++ User Journal articles (around 1996/1997) and his [bad] implementation are the primary source of the claims of inefficiency. Although decent implementations (libc++, libstdc++) tend to be at least on par with they have quite a bit of optimization potential left. – Dietmar Kühl Nov 06 '15 at 10:16
  • @DietmarKühl: I'm not talking about efficiency/performance in any respect. IOStreams will beat Java's `System.out.print()` any day of the week, and that's just enough. I'm talking about design. Do we need those `uflow()` and `underflow()` functions that do two different things? That's just stupid, I have to say. I don't negate your experience in the topic in any respect, but seriously, is this what the C++ language merits? *The sole programming language on Earth that can both beat C performance-wise and be low-level/high-level at the same time*? – 3442 Nov 06 '15 at 10:20
  • @KemyLand: really, `uflow()` is what's upsetting you? Good news: if you implement a buffer for your stream you'll never need to implement it and it'll never be called and for efficiency you'd want to set up a buffer anyway. If you don't set up a buffer, not even a one character buffer both functions are needed. Maybe it would have been more prudent to always travel in terms of a buffer and a corresponding simpler interface - except that some level of backward compatibility with (namely "synchronized" use standard input, output, and error) actually requires unbuffered operation. – Dietmar Kühl Nov 06 '15 at 10:28
  • @DietmarKühl: It's not just `uflow()`. May I mention `pptr()`, `epptr()`, `sbumpc()`, and `sgetc()`? I don't want to just "implement a buffer for my stream" so that I do as it those functions never existed. They exist, and make the IOStreams library show its age. Seriously, where do you find stuff like that in the containers section of the library? Why didn't they deprecated/removed all that stuff in C++11, and left compatibility code with its old libraries and standards, and new code to be free of backwards-compatibility? This has only made everything worse, as new code **CONTINUATION**. – 3442 Nov 06 '15 at 10:34
  • **CONTINUATION** depends on that old stuff, and will depend on it for the lifespan of its standard. IMHO, Stroustrup made a fatal error when trying to have (at least a little of) compatibility with C in the then "C with Classes", although that's probably a factor towards C++'s popularity... – 3442 Nov 06 '15 at 10:37

2 Answers2

6

As a preface, I want to state that this is a question made by a student, but unlike most of their type, it is a quality question that merits a quality answer, so I'll try to do it ;). I won't try to just answer your concrete question, but also to show you other slight problems in your code.

First of all, let's analyze your code step by step. More or less like what a debugger would do. Take your time to read this carefully ;)...

#include <iostream>
#include <CTYPE.h>

Includes headers <iostream> and <ctype.h> (the uppercase works because of some flaws/design-decisions of NTFS in Windows). I'ld recommend you to change the second line to #include <cctype> instead.

using namespace std;

This is okay for any beginner/student, but don't get an habit of it! For the purposes of "purity", I would explicitly use std:: along this answer, as if this line didn't existed.

int ReadInt (unsigned short int &UserIn);

Declares a function ReadInt that takes a reference UserIn to type unsigned short int and returns an object of type int.

int main()
{

Special function main; no parameters, returns int. Begin function.

    int Error;
    unsigned short int UserInput;
    char RepeatProgram;

Declares variables Error, UserInput, and RepeatProgram with respective types int, unsigned short int, and char.

    do
    {

Do-while block. Begin.

        Error=ReadInt(UserInput);

Assign return value of ReadInt of type int called with argument UserInput of type int& to variable Error of type unsigned short int.

        if (Error==0)
            std::cout << "Number is " << UserInput << endl;

If Error is zero, then print out UserInput to standard output.

        else if (Error==1)
            std::cout << "Illegal Data Entry\n";

        else if (Error==2)
            std::cout << "Numerical overflow, number too big\n";

Otherwise, if an error occurs, report it to the user by means of std::cout.

        std::cout << "Continue?  n/N to quit: ";
        std::cin >> RepeatProgram;

Query the user if he/she wants to continue or quit. Store the input character in RepeatProgram of type char.

        std::cout << std::endl;

Redundant, unless you want to add padding, which is probably your purpose. Actually, you're better off doing std::cout << '\n', but that doesn't matters too much.

    } while (RepeatProgram!='N' && RepeatProgram!='n');

Matching expression for the do-while block above. Repeat execution of the given block if RepeatProgram is neither lower- or uppercase- letter N.

}

End function main. Implicit return value is zero.

int ReadInt (unsigned short int &UserIn)
{

Function ReadInt takes a reference UserIn to unsigned short int and returns an object of type int. Begin function.

    int Err=0;
    char TemporaryStorage;
    long int FinalNumber=0;

Declares variables Err, TemporaryStorage, and FinalNumber of respective types int, char, and long int. Variables Err and FinalNumber are initialized to 0 and 0, respectively. But, just a single thing. Didn't the assignment said that the output number be stored in a unsigned short int? So, better of this...

    unsigned short int FinalNumber = 0;

Now...

    std::cout << "Enter a number: ";

    //std::cin.ignore(1000, !' '); this didn't work

Eh? What's this supposed to be? (Error: Aborting debugger because this makes no logic!**). I'm expecting that you just forgot the // before the comment, right? Now, what do you expect !' ' to evaluate to other than '\0'? istream::ignore(n, ch)will discard characters from the input stream until either n characters have been discarded, ch is found, or the End-Of-File is reached.

A better approach would be...

   do
       std::cin.get(TemporaryStorage);
   while(std::isspace(TemporyStorage));

Now...

    std::cin.get(TemporaryStorage);

This line can be discarded with the above approach ;).

Right. Now, where getting into the part where you obviously banged your head against all solid objects known to mankind. Let me help you a bit there. We have this situation. With the above code, TemporaryStorage will hold the first character that is not whitespace after the do-while loop. So, we have three things left. First of all, check that at least one digit is in the input, otherwise return an error. Now, while the input is made up of digits, translate characters into integers, and multiply then add to get the actual integer. Finally, and this is the most... ahem... strange part, we need to avoid any overflows.

    if (!std::isdigit(TemporaryStorage)) {
        Err = 1;
        return Err;
    }

    while (std::isdigit(TemporaryStorage)) {
        unsigned short int OverflowChecker = FinalNumber;

        FinalNumber *= 10; // Make slot for another digit
        FinalNumber += TemporaryStorage - '0'; '0' - '0' = 0, '1' - '0' = 1...

        // If an unsigned overflows, it'll "wrap-around" to zero. We exploit that to detect any possible overflow
        if (FinalNumber > 65535 || OverflowChecker > FinalNumber) {
            Err = 2;
            return Err;
        }

        std::cin.get(TemporaryStorage);
    }

    // We've got the number, yay!
    UserIn = FinalNumber;

The code is self-explanatory. Please comment if you have any doubts with it.

    std::cout << TemporaryStorage;//I'm only displaying this while I test my ideas to see if they are working or not, before I move onto the the next step

    cout << endl;

    return Err;

Should I say something here? Anyway, I already did. Just remember to take that std::couts out before showing your work ;).

}

End function ReadInt.

3442
  • 8,248
  • 2
  • 19
  • 41
  • This is absolutely awesome, really appreciate the heck out of you going the full length of helping me out. Its 530 AM here, I'll break check this out when I wake up fully and break it down. Once again, really appreciate it. I didn't expect anyone to do the full code, so I will ignore the bottom half until I write my own version up and then compare it to yours. – JosephTLyons Nov 06 '15 at 10:35
  • 1
    @joe_04_04: Thank you ;). Comment here if you have any doubts. – 3442 Nov 06 '15 at 10:37
  • I'll more than likely mark this as the solution, as we JUST talked about the isdigit and isspace, making your solution HIGHLY relevant. Thanks a ton, I truly appreciate it. Not many people would go to this extent. I'm only about 10 weeks into coding so I may have a few questions, but no doubts! – JosephTLyons Nov 06 '15 at 10:41
  • 1
    @joe_04_04: And I truly appreciate your motivation. Seriously, that's the most important trait for someone who writes software: Do it because you want to expand your horizons and you like it ;). Just to be sure, I repeat, any question/doubt/whatever relating to this, just comment. The purpose of this community is no other than to help others. – 3442 Nov 06 '15 at 10:51
  • Thanks a ton KernyLand, I'm going through this code now. – JosephTLyons Nov 06 '15 at 19:34
  • My biggest question is how TemporaryStorage is working. TemporaryStorage is a char datatype, so it can only hold one character at a time, so how is it that input buffer is being stored in there? I guess I'm a bit confused on how each new character from the input buffer is stored in the TemporaryStorage. My second question is, how is it that in some places, using cin.get prompts the compiler to get an answer from the user, and other places it seems to be advancing to the next character in the input buffer? I'm not too familiar with how cin.get actually works. – JosephTLyons Nov 06 '15 at 20:35
  • I got everything in my code working except the part where it clears the input before before going back to the main, which my code isn't doing, which is messing with the loop, but I'll continue to work on it. – JosephTLyons Nov 06 '15 at 20:36
  • do { cin.get(TemporaryStorage); } while(isspace(TemporaryStorage)); My hypothesis for how this is working is, if the input buffer is empty, cin.get will accept data from the user, if the buffer is not empty, cin.get will grab the next character in the input buffer. so in the case of the loop, the first time prompts input, the second time it grabs the next letter, if it is a space, it grabs the next letter, and so on until the next character it grabs is not a space, then it stores that in TemporaryStorage and leaves the loop – JosephTLyons Nov 06 '15 at 20:42
  • Anytime after that, cin.get(TemporaryStorage) is asking the computer to replace the current character in TemporaryStorage with the next character from the input buffer. This is my hypothesis on what is happening, but I could be WAYYY off base, as its just a guess based on what's happening. – JosephTLyons Nov 06 '15 at 20:45
  • This is what I have. As I said, I would only use the section of code I was stuck up on, then I went from there on my own. I didn't make any of the other changes you suggested yet because I wanted to finish it off on my own, but compare after to see how pro would do it versus how I did it. http://codepad.org/BB5x9rDg – JosephTLyons Nov 07 '15 at 07:29
  • 1
    @joe_04_04: Sorry, I couldn't answer in time. Anyway, `cin.get()` means "get a character from standard input". If you're using an IDE (as it appears to be), such as Visual Studio, Code::Block, or whatever, then 'standard input' means the prompt the "compiler" gives you. Now, if at the standard input is empty (you haven't entered nothing), then the console (the prompt) will first ask you to enter a single line of text. Then, `cin.get()` gets that line (including '\n') and takes one character. Once that input "buffer" is empty, another line is read by the prompt. **CONTINUATION** – 3442 Nov 07 '15 at 12:45
  • 1
    @joe_04_04: **CONTINUATION**: Now, something like `do { cin.get(TemporaryStorage); } while(isspace(TemporaryStorage));` means: "**while** TemporaryStorage **is**white**space**, let TemporaryStorage be another input character, and **do** it at least once". – 3442 Nov 07 '15 at 12:49
  • Thanks a ton KernyLand. This is my last question (promise). This is the code I used to clear my input buffer after. while (TemporaryStorage!='\n')//clears input buffer up until newline { cin.get(TemporaryStorage); } I figured there would be some sort of default code I could call to do it, but could not find anything that would work. Is this an ok thing to do? Or is there a better implementation? – JosephTLyons Nov 07 '15 at 20:02
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/94511/discussion-between-kemyland-and-joe-04-04). – 3442 Nov 08 '15 at 00:04
2

You can skip leading whitespace from a stream using std::ws. For example:

std::cin >> std::ws;

This use of >> just invokes the manipulator std::ws on the stream. To meet the teacher's requirements you can invoke it directly:

std::ws(std::cin);

Formatted input automatically skips whitespace. Note that should also always check whether input was successful:

if (std::cin.get(TemporaryStorage)) {
    ...
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 2
    The OP explicitly stated that *he can't use `std::cin >>`*! – 3442 Nov 06 '15 at 09:38
  • @KemyLand: too much text in the question. I adjusted the answer to spell out how to avoid using the input operator while still using `>>`. – Dietmar Kühl Nov 06 '15 at 09:44
  • Sorry for the lengthy text guys, its long because I included my teachers notes in there also. I tried to slim it down, but didn't want to remove anything that might narrow down appropriate responses. Thanks for the help, really appreciate it. – JosephTLyons Nov 06 '15 at 10:37