سؤال

I'm sorry if I duplicate, but I have tried EVERYTHING, and I can't figure out why this code keeps breaking. The highest-priority goal was to make this code handle input safely, or just anything that the user can type into the console, without it breaking. However, I also need it to be able to run more than once. fgets() won't let me do that since it keeps reading '\n' somewhere and preventing me from entering input more than once when it hits the end of the do/while loop. I have tried fflushing stdin, I have tried scanf("%d *[^\n]"); and just regular scanf("%d *[^\n]");, but none of those work, and, in fact, they break the code! I used this website to try to get the "Safely handling input" code to work, but I don't completely understand what they're doing. I tried to jerry-rig (spelling?) it as best I could, but I'm not sure if I did it right. Did I miss something? I didn't think a problem this seemingly simple could be so much of a headache! >_<

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

using namespace std;

#define BUF_LEN 100
#define SPACE 32
#define SPCL_CHAR1F 33
#define SPCL_CHAR1L 47
#define SPCL_CHAR2F 58
#define SPCL_CHAR2L 64
#define SPCL_CHAR3F 91
#define SPCL_CHAR3L 96
#define NUMF  48
#define NUML 57
#define UC_CHARF 65
#define UC_CHARL 90
#define LC_CHARF 97
#define LC_CHARL 122

void main ()
{
    char* buffer;
    int SpcCounter=0, SpclCounter=0, NumCounter=0,LcCounter=0, UcCounter=0;
    char line[BUF_LEN],response[4];
    char*input="";
    bool repeat=false;

    do
    {
        for(int i=0;i<BUF_LEN;i++)
        {
            line[i]=NULL;
        }
        buffer=NULL;
        printf("Enter your mess of characters.\n");

        buffer=fgets(line,BUF_LEN,stdin);

        //To handle going over the buffer limit: BROKEN
        if(buffer!=NULL)
        {
            size_t last=strlen(line)-1;

            if(line[last]=='\n')

                line[last]='\0';
            else
            {
                fscanf(stdin,"%c *[^\n]");
            }
        }


        for(int i=0;i<BUF_LEN;i++)
        {
            char temp=buffer[i];
            if(temp==SPACE||temp==255)
                SpcCounter++;
            else if((temp >= SPCL_CHAR1F && temp <= SPCL_CHAR1L)||/*Special  characters*/
                (temp >= SPCL_CHAR2F && temp <= SPCL_CHAR2L)||
                (temp >= SPCL_CHAR3F && temp <= SPCL_CHAR3L))
                SpclCounter++;
            else if (temp >=NUMF && temp <= NUML)/*Numbers*/
                NumCounter++;
            else if (temp >= UC_CHARF && temp <= UC_CHARL)/*Uppercase letters*/
                UcCounter++;
            else if (temp >= LC_CHARF && temp <= LC_CHARL)/*Lowercase letters*/
                LcCounter++;
        }

        printf("There were %i space%s, %i special character%s, %i number%s, and %i letter%s,\n"
            "consisting of %i uppercase letter%s and %i lowercase.\n",
            SpcCounter,(SpcCounter==1?"":"s"),SpclCounter,(SpclCounter==1?"":"s"), NumCounter,(NumCounter==1?"":"s"),UcCounter+LcCounter,
            (UcCounter+LcCounter==1?"":"s"), UcCounter,(UcCounter==1?"":"s"), LcCounter);
        printf("Would you like to do this again? (yes/no)");

        input=fgets(response,4,stdin);
        /*
        ALL BROKEN
        if(input!=NULL)
        {
            size_t last=strlen(response)-1;

            if(response[last]=='\n')
                response[last]='\0';
            else
            {
                fscanf(stdin,"%*[^\n]");
                fscanf(stdin,"%c");
            }
        }
        */

        //To capitalize the letters
        for(int i=0;i<4;i++)
        {
            char* temp=&response[i];
            if (*temp >= LC_CHARF && *temp <= LC_CHARL)
                *temp=toupper(*temp);//Capitalize it
        }
        //To set repeat: WORKS, BUT WEIRD
        repeat=!strncmp(input,"YES",4);
    }
    while(repeat);
}
هل كانت مفيدة؟

المحلول

For safe, secure user input in C (and in C++ if I'm using C-style strings), I usually revert to an old favorite of mine, the getLine function:

// Use stdio.h and string.h for C.
#include <cstdio>
#include <cstring>

#define OK       0
#define NO_INPUT 1
#define TOO_LONG 2
static int getLine (char *prmpt, char *buff, size_t sz) {
    int ch, extra;

    // Output prompt then get line with buffer overrun protection.
    if (prmpt != NULL) {
        printf ("%s", prmpt);
        fflush (stdout);
    }
    if (fgets (buff, sz, stdin) == NULL)
        return NO_INPUT;

    // If it was too long, there'll be no newline. In that case, we flush
    // to end of line so that excess doesn't affect the next call.
    if (buff[strlen(buff)-1] != '\n') {
        extra = 0;
        while (((ch = getchar()) != '\n') && (ch != EOF))
            extra = 1;
        return (extra == 1) ? TOO_LONG : OK;
    }

    // Otherwise remove newline and give string back to caller.
    buff[strlen(buff)-1] = '\0';
    return OK;
}

This function:

  • can output a prompt if desired.
  • uses fgets in a way that avoids buffer overflow.
  • detects end-of-file during the input.
  • detects if the line was too long, by detecting lack of newline at the end.
  • removes the newline if there.
  • "eats" characters until the next newline to ensure that they're not left in the input stream for the next call to this function.

It's a fairly solid piece of code that's been tested over many years and is a good solution to the problem of user input.

In terms of how you call it for the purposes in your question, I would add something very similar to what you have, but using the getLine function instead of directly calling fgets and fiddling with the results. First some headers and the same definitions:

#include <iostream>
#include <cstdlib>
#include <cctype>

#define BUF_LEN 100
#define SPACE 32
#define SPCL_CHAR1F 33
#define SPCL_CHAR1L 47
#define SPCL_CHAR2F 58
#define SPCL_CHAR2L 64
#define SPCL_CHAR3F 91
#define SPCL_CHAR3L 96
#define NUMF 48
#define NUML 57
#define UC_CHARF 65
#define UC_CHARL 90
#define LC_CHARF 97
#define LC_CHARL 122

Then the first part of main gathering a valid line (using the function) to be evaluated:

int main () {
    int SpcCounter, SpclCounter, NumCounter, LcCounter, UcCounter;
    char line[BUF_LEN], response[4];
    bool repeat = false;

    do {
        SpcCounter = SpclCounter = NumCounter = LcCounter = UcCounter = 0;

        // Get a line until valid.

        int stat = getLine ("\nEnter a line: ", line, BUF_LEN);
        while (stat != OK) {
            // End of file means no more data possible.

            if (stat == NO_INPUT) {
                cout << "\nEnd of file reached.\n";
                return 1;
            }

            // Only other possibility is "Too much data on line", try again.

            stat = getLine ("Input too long.\nEnter a line: ", line, BUF_LEN);
        }

Note that I've changed where the counters are set to zero. Your method had them accumulating values every time through the loop rather than resetting them to zero for each input line. This is followed by your own code which assigns each character to a class:

        for (int i = 0; i < strlen (line); i++) {
            char temp=line[i];
            if(temp==SPACE||temp==255)
                SpcCounter++;
            else if((temp >= SPCL_CHAR1F && temp <= SPCL_CHAR1L)||
                (temp >= SPCL_CHAR2F && temp <= SPCL_CHAR2L)||
                (temp >= SPCL_CHAR3F && temp <= SPCL_CHAR3L))
                SpclCounter++;
            else if (temp >=NUMF && temp <= NUML)
                NumCounter++;
            else if (temp >= UC_CHARF && temp <= UC_CHARL)
                UcCounter++;
            else if (temp >= LC_CHARF && temp <= LC_CHARL)
                LcCounter++;
        }

        printf("There were %i space%s, %i special character%s, "
               "%i number%s, and %i letter%s,\n"
               "consisting of %i uppercase letter%s and "
               "%i lowercase.\n",
            SpcCounter,  (SpcCounter==1?"":"s"),
            SpclCounter, (SpclCounter==1?"":"s"),
            NumCounter, (NumCounter==1?"":"s"),
            UcCounter+LcCounter, (UcCounter+LcCounter==1?"":"s"),
            UcCounter, (UcCounter==1?"":"s"),
            LcCounter);

Then finally, a similar way as above for asking whether user wants to continue.

        // Get a line until valid yes/no, force entry initially.

        *line = 'x';
        while ((*line != 'y') && (*line != 'n')) {
            stat = getLine ("Try another line (yes/no): ", line, BUF_LEN);

            // End of file means no more data possible.

            if (stat == NO_INPUT) {
                cout << "\nEnd of file reached, assuming no.\n";
                strcpy (line, "no");
            }

            // "Too much data on line" means try again.

            if (stat == TOO_LONG) {
                cout << "Line too long.\n";
                *line = 'x';
                continue;
            }

            // Must be okay: first char not 'y' or 'n', try again.

            *line = tolower (*line);
            if ((*line != 'y') && (*line != 'n'))
                cout << "Line doesn't start with y/n.\n";
        }
    } while (*line == 'y');
}   

That way, you build up your program logic based on a solid input routine (which hopefully you'll understand as a separate unit).

You could further improve the code by removing the explicit range checks and using proper character classes with cctype(), like isalpha() or isspace(). That would make it more portable (to non-ASCII systems) but I'll leave that as an exercise for later.

A sample run of the program is:

Enter a line: Hello, my name is Pax and I am 927 years old!
There were 10 spaces, 2 special characters, 3 numbers, and 30 letters,
consisting of 3 uppercase letters and 27 lowercase.
Try another line (yes/no): yes

Enter a line: Bye for now
There were 2 spaces, 0 special characters, 0 numbers, and 9 letters,
consisting of 1 uppercase letter and 8 lowercase.
Try another line (yes/no): no
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top