tags:

views:

195

answers:

3

I'm currently doing a project for a Beginner C programming class where i'm supposed to make a basic ordering system for a company. I've got an issue with one of my functions, it works fine as a separate program but as a function in the ordering program it won't let me input a new item before it exits the function. It seems however to run through everything after the gets(item); as I get the added \n every time I run it.

Here's my code:

do{
printf("Menu here");
scanf("%c", &menu);
switch(menu)
{
 case 'A':
  listItem();
  break;

 case 'B':
  addItem();
  break;

 ...

 case 'X':
  break;
}

printf("Press Enter to continue.");
scanf("%c%c", &enter, &enter);
system("cls");

}while(menu != 'X');


void addItem()
{
    char item[30];
    printf("\nAdd new item: ");
    gets(item);
    FILE * output;
    output = fopen("items.txt", "a");
    fputs(item, output);
    fprintf(output, "\n");
    fclose(output);
}

the stuff after the switch is what my teacher thought would be an ugly but effective way to solve the fact that we don't delve deeper into what he called the "quirks of C input" in this course.

I'm thankful for any tips and answers and will provide more of my code if necessary.

+1  A: 

One immediate thing I noticed. Your do while loop is checking val for "X" whereas that value is actually in menu.

Other than that possibility (val may be "X" to begin with, which could cause exit from the loop regardless of the value entered), nothing jumps out as obviously causing premature exit from a function or loop. I think you'll be better off posting your full code base so we're not guessing too much.

Update:

Don't use the following for homework - you will almost certainly be failed for plagiarism (since your educators, assuming they're not total fools, will be on the lookout for work taken from these sites).

I just wanted to give you an idea of what you can use for user I/O to make your programs a little more robust. As one helpful soul pointed out, you should never use the input routines that don't have buffer over-run protection as an option since that will almost certainly allow malicious input to crash your code (that's the best case, worst case is that they will take over your computer).

That means no gets, you need to use fgets instead since it can limit how much information is actually input. In addition, I tend to avoid the use of scanf and fscanf since any failure in those functions actually leaves the input file pointer at an indeterminate location.

I find it's far better to use fgets to get a whole line, check that you actually got a whole line, then use sscanf on that line. That way, you can be sure you're on a line boundary, that you've got an entire line and that you can then sscanf that line to your heart's content until you match it with something.

To that end, you may want to look over the following code:

#include <stdio.h>

#define FSPEC "file.txt"

// Skip to the end of the line. This is used in some
// places to ensure there's no characters left in the
// input buffer. It basically discards characters
// from that buffer until it reaches the end of a line.

static void skipLine (void) {
    char ch = ' ';
    while ((ch != '\n') && (ch != EOF))
        ch = getchar();
}

 

// Get a line of input from the user (with length checking).

static char *getLine (char *prompt, char *line, int sz) {
    // Output prompt, get line if available.
    // If no line available (EOF/error), output newline.

    printf ("%s", prompt);
    if (fgets (line, sz, stdin) == NULL) {
        printf ("\n");
        return NULL;
    }

    // If line was too long (no '\n' at end), throw away
    // rest of line and flag error.

    if (line[strlen (line) - 1] != '\n') {
        skipLine();
        return NULL;
    }

    // Otherwise line was complete, return it.

    return line;
}

 

// Output the menu and get a choice from the user.

static char doMenu (void) {
    char cmd[1+2]; // need space for char, '\n' and '\0'.

    // Output the menu.

    printf ("\n");

    printf ("\n");
    printf ("Main menu\n");
    printf ("---------\n");
    printf ("1. Input a line\n");
    printf ("2. Output the file\n");
    printf ("3. Clear the file\n");
    printf ("\n");
    printf ("x. Exit\n");
    printf ("\n");

    // Get the user input and return it.

    if (getLine ("Enter choice (1,2,3,x): ", cmd, sizeof(cmd)) == NULL)
        return '\n';

    printf ("\n");

    return cmd[0];
}

 

static void doOption1 (void) {
    FILE *fh;
    char *ln;
    char buff[15+2]; // need space for line, '\n' and '\0'.

    // Get and check line, add to file if okay.

    if ((ln = getLine ("Enter line: ", buff, sizeof(buff))) == NULL) {
        printf ("Bad input line\n");
    } else {
        fh = fopen (FSPEC, "a");
        if (fh != NULL) {
            fputs (ln, fh);
            fclose (fh);
        }
    }
}

 

static void doOption2 (void) {
    FILE *fh;
    int intch;

    // Output the file contents.

    printf ("=====\n");
    fh = fopen (FSPEC, "r");
    if (fh != NULL) {
        while ((intch = fgetc (fh)) != EOF)
            putchar (intch);
        fclose (fh);
    }
    printf ("=====\n");
}

 

static void doOption3 (void) {
    FILE *fh;

    // Clear the file.

    fh = fopen (FSPEC, "w");
    if (fh != NULL)
        fclose (fh);
}

 

// Main program basically just keeps asking the user for input
// until they indicate they're finished.

int main (void) {
    char menuItem;

    // Get asking for user input until exit is chosen.

    while ((menuItem = doMenu()) != 'x') {
        switch (menuItem) {
            case '1': doOption1(); break;
            case '2': doOption2(); break;
            case '3': doOption3(); break;
            default:  printf ("Invalid choice\n"); break;
        }
    }

    return 0;
}
paxdiablo
The break will only break out of the switch-statement and not the while-loop.
Lucas
@Lucas, your statement is correct but I can't locate where I actually made that contention. I stated that the loop would exit if val was set to "X", nothing about the break statements exiting the loop.
paxdiablo
the menu and val issue is me screwing up my translation of my variables to english, i missed to change the last val to menu, the loop condition is valid in my original code
Fredrik Johansson
@paxdiablo: I believe Lucas is referring to using a `while(1)` loop. I thought the same thing at first, that a forever loop was the poorer choice, until I thought about the post-switch processing. Since the menu loop isn't in `main()`, a forever loop and a `return` for `case 'X':` should work quite well.
outis
@paxdiablo: I misunderstood your first sentence. It read it as you were implying that the "break" from the switch-break would break out of the while loop.
Lucas
Thanks a bunch for this, I will take some time to sit down and really take in what you've written here.
Fredrik Johansson
+1  A: 

You're reading one character at a time, and scanf() is doing some buffering. If I type "ABC" and hit enter, your program will read 'A', do the action for 'A', print "hit enter to continue", reading 'B' and 'C' in the scanf that immediately follows. So of course it's going to return early; you've already given it some input, namely 'B' and 'C'.

I suggest you use another method of taking input. For example, you could switch to line-based commands (perhaps using fgets()), which require you to hit enter at every step. Or you could use curses or whatever platform-specific thing you need to do non-buffered input, so that you react to keys getting pressed and not the buffering provided by stdio.

asveikau
+4  A: 

What is happening is this:

  1. Program prints the menu.
  2. User types "B<enter>".
  3. scanf reads the B character. The <enter> is still waiting in the input stream.
  4. addItem is called.
  5. gets() is called, reads the <enter> that's still waiting, and returns an empty line.

You can fix it by reading and discarding everything up to and including the next newline after you read the menu selection character with scanf:

int c;

printf("Menu here");
scanf("%c", &menu);
do {
    c = getchar();
} while (c != EOF && c != '\n');
caf