1

So I have written some code that has a switch, and I need to give it an integer to select a case within the switch. I can't use scanf() because I have multiple fgets() further down the line and the '\n' from the scanf() input breaks the code.

Here is my code:

main.c

#include "functions.h"
#include <stdlib.h>

int main() {
    int choice;
    char temp[10];
    
    do {
        printf("Menu\n\n");
        printf("1. Read student information from file\n");
        printf("2. Write student information to file\n");
        printf("3. Exit\n");
        fgets(choice, 10, stdin);
    
        switch (choice) {
          case 1:
            fileRead();
            break;
          case 2:
            fileWrite();
            break;
          default:
            printf("Program stopped!\n");
            break;
        }
    } while (choice != 3);
    return 0;
}

functions.h

#ifndef UNTITLED17_FUNCTIONS_H
#define UNTITLED17_FUNCTIONS_H

#include <stdio.h>
#include <string.h>

struct student {
    char studentID[100];
    char studentName[100];
    char age[100];
} student_t;

void fileRead() {
    FILE *f = fopen("student_read.txt", "r");
    if (f == NULL) {
        printf("Failed to open file(s)!\n");
    }
    printf("Type your student ID:");
    fgets(student_t.studentID, 100, stdin);

    printf("Type your name:");
    fgets(student_t.studentName, 100, stdin);

    printf("Type your age:");
    fgets(student_t.age, 100, stdin);

    printf("Student id: %s\n", student_t.studentID);
    printf("Name: %s\n", student_t.studentName);
    printf("Age: %s\n", student_t.age);
}

void fileWrite() {
    FILE *f = fopen("student_write.txt", "w");
    if (f == NULL) {
        printf("Failed to open file(s)!\n");
    }
    printf("Type your student ID:");
    fgets(student_t.studentID, 100, stdin);
    printf("Type your name:");
    fgets(student_t.studentName, 100, stdin);
    printf("Type your age:");
    fgets(student_t.age, 100, stdin);

    printf("Student id: %s\n", student_t.studentID);
    printf("Name: %s\n", student_t.studentName);
    printf("Age: %s\n", student_t.age);
}

#endif //UNTITLED17_FUNCTIONS_H

Any ideas?

Thanks :)

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 3
    Use `fgets` and `sscanf` the buffer, then the other `fgets` calls will work as expected. – Cheatah Dec 02 '21 at 19:33
  • 4
    Don't put function definitions into `h` file. It is for declarations (prototypes) only. – Eugene Sh. Dec 02 '21 at 19:35
  • 1
    The compiler didn't complain about `fgets(choice, 10, stdin);`? – Scott Hunter Dec 02 '21 at 19:36
  • @ScottHunter yeah but i just ran out of ideas –  Dec 02 '21 at 19:43
  • @Cheatah buffer? –  Dec 02 '21 at 19:43
  • 1
    There's nothing preventing you from using `scanf`, but you need to use it correctly. If you want to consume the newline, `scanf("%d",...)` won't do that, but there's nothing preventing you from consuming it some other way. (Eg, call `fgetc` in a loop) – William Pursell Dec 02 '21 at 19:46
  • @Ole-Johan Yes. `choice` will contain a string, not a number. You will have to `sscanf(choice, "%d", &n);` where `n` is an `int`, for example. – Cheatah Dec 02 '21 at 19:48
  • 1
    Or you can just use `atoi(choice)` to convert the string to an integer. (This isn't perfect, and will quietly give you 0 even if there was no number typed at all, but it's quick & easy.) – Steve Summit Dec 02 '21 at 20:01
  • 2
    @Ole-Johan Congratulations, though, for being aware of the `scanf`/`fgets` interlace problem. We get dozens of questions per day asking why `fgets` doesn't work. This is the first time I can remember seeing a question from someone who already knew about that issue, and was trying to do something better. – Steve Summit Dec 02 '21 at 20:07
  • @SteveSummit yeah because i just asked a question about it an hour ago LOL –  Dec 02 '21 at 20:07

4 Answers4

1

As OP mentions the qualified nature of input, I'd go with a simple fgets(), atoi(). Read into a buffer and convert to an int.

//fgets(choice, 10, stdin);
//switch(choice){

// Initialize just in case no input.
// Not so big to prevent atoi() overflow - which is UB. 
char buf[5] = ""; 

fgets(buf, sizeof buf, stdin);
choice = atoi(buf);
switch(choice) {

If input was not qualified, I'd use a larger buffer, strtol() and more error checking.

In that case, makes sense to make a helper function to get an int.

Some untested code:

#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>

// On EOF, return EOF
// Else return 1 for success, 0 failure.
int getint(const char *prompt, int *dest) {
  // Some buffer generously sized to the type.
  char buf[sizeof *dest * CHAR_BIT];

  if (prompt) {
    fputs(prompt, stdout);
  }
  if (fgets(buf, sizeof buf, stdin) == NULL) {
    return EOF;
  }
  char *endptr;
  errno = 0;
  long val = strtol(buf, &endptr, 0);
  if (buf == endptr) {
    return 0;  // No conversion
  }
  if (errno == ERANGE) {
    return 0;  // out of long range
  }
  #if LONG_MIN < INT_MIN || LONG_MAX > INT_MAX
  if (val > INT_MAX || val < INT_MIN) {
    errno = ERANGE;
    return 0; // Out of int range
  }
  #endif
  *dest = (int) val;
  return 1;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
0

fgets, as the s suggests, is meant for reading a string from a file, which is why the compiler complains about the first argument: it should be a pointer to where that string will go. Once there, as @Cheetah suggests, parse that to get the integer value it represents.

Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
0

This line from your code reads a line into the choice buffer:

fgets(choice, 10, stdin);

You can add this directly afterwards:

int n;
if (sscanf(choice, "%d", &n) != 1)) {
    fprintf(stderr, "Invalid input\n");
    exit(1);
}

Most notably here is the sscanf that reads the choice buffer that should contain a string, since it was returned by fgets. The value is stored in n. You can check that in your switch statement.

I added an if around the sscanf, to check that 1 is returned, that is the number of arguments that were read successfully.

Cheatah
  • 1,825
  • 2
  • 13
  • 21
  • 1
    I ended up doing this: `int choice; char choiceTemp[10]; fgets(choiceTemp, 10, stdin); sscanf(choiceTemp, "%i", &choice);` –  Dec 02 '21 at 19:55
  • Yes that should do the job. But beware that people can input invalid lines. – Cheatah Dec 02 '21 at 19:57
  • yeah, the input is prederetmined cause the program is tested using a python program so i have som leeway –  Dec 02 '21 at 19:57
  • `choice` was an `int`, not a buffer of any kind. – Scott Hunter Dec 02 '21 at 19:58
  • @ScottHunter im very very new to coding so i dont really know what that means, but atleast it works –  Dec 02 '21 at 19:58
  • By a *very* liberal definition of `works`. The way @Ole-Johan implemented your idea is correct. – Scott Hunter Dec 02 '21 at 20:00
0

fgets(choice, 10, stdin); is definitely incorrect as choice is an int, not a pointer to char nor a char array.

You should read the string and convert its contents as an integer this way:

    if (!fgets(temp, sizeof temp, stdin))
        return 1;
    choice = atoi(temp);

Also note these remarks:

  • if the file cannot be open in fileRead() and fileWrite(), you should return after you output the error message.
  • functions should not be defined in header files, only declarations belong there
  • you should test the return value of fgets() to detect end of file.
  • fileRead and fileWrite should return an success/failure indicator.
  • you should strip the newline left by fgets() at the end of the array. Even better: you should write a function that reads a line into the destination array, truncating to available space and stripping initial and trailing whitespace.
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • yeah i fixed it by making a temp char for the fgets and converting it using sscanf –  Dec 02 '21 at 20:13