0

Here's the part of my code:

Problem: It skips the input of "please enter your name" to "please enter your marks"

What I tried: flushall(); _flushall(); - which worked yesterday somehow, and trying to place these functions between printf,scanf..

student *Create_Class(int size) {
    int i, j;
    int idStud, nameStud, markStud;

    student *classStudent;
    classStudent = (student*)malloc(size * sizeof(student));

    for (i = 0; i < size; i++) {
        classStudent[i].name = (char*)malloc(51 * sizeof(char));
        int numOfmarks = 4;

        printf("Please enter your name: ");
        gets(classStudent[i].name);
        _flushall(); //tried _flushall() and it worked yesterday.. tried fflush(NULL) too.
        printf("\nPlease enter 4 marks: ");

        for (j = 0; j < numOfmarks; j++) {
            scanf("%d", &classStudent[i].marks[j]);

        }
        Avg_Mark(&classStudent[i]);
    }
    return classStudent;
}

EDIT: (FULL CODE)

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

typedef struct {
    char *name;
    int marks[4];
    float avg;
} student;

student *Create_Class(int);
void Avg_Mark(student*);
void Print_One(student*);
void exStudents(student *s, int size);


int main() {
    int size, i;
    student *arr;
    printf("\nEnter the number of students: \n");
    scanf("%d", &size);

    arr = Create_Class(size);
    exStudents(arr, size);

    for (i = 0; i < size; i++)
        free(arr[i].name);

    free(arr);
    getch();
}

student *Create_Class(int size) {
    int i, j;
    int idStud, nameStud, markStud;

    student *classStudent;
    classStudent = (student*)malloc(size * sizeof(student));

    for (i = 0; i < size; i++) {
        classStudent[i].name = (char*)malloc(51 * sizeof(char));
        int numOfmarks = 4;
        int sizeOfName;

        printf("Please enter your name: \n");
        _flushall();
        fgets(classStudent[i].name,50,stdin);
        sizeOfName = strlen(classStudent[i].name);
        printf("Please enter 4 marks: ");
        for (j = 0; j < numOfmarks; j++) {
            scanf("%d", &classStudent[i].marks[j]);
        }
        Avg_Mark(&classStudent[i]);
    }
    return classStudent;
}

void Avg_Mark(student *s) {
    int i, numOfMarks = 4, sum = 0;

    for (i = 0; i < numOfMarks; i++) {
        sum += s->marks[i];
    }
    s->avg = (sum / 4.0);
}

void Print_One(student *s) {
    printf("The average of %s is %f", s->name, s->avg);
}

void exStudents(student *s, int size) {
    int flag = 1;

    while (size > 0) {
        if (s->avg > 85) {
            Print_One(s);
            flag = 0;
        }
        s++;
        size--;
    }
    if (flag)
        printf("\n There're no students with above 85 average.");
}
Ilan Aizelman WS
  • 1,630
  • 2
  • 21
  • 44
  • 2
    Mixing line-based input (as with `fgets`) and token-based input (as with `fscanf`) will do that. The solution is to take a two-step approach: Read lines with `fgets` first, then scan them with `sscanf`. (And don't use `gets`; it won't guard against buffer overflows.) – M Oehm Mar 18 '16 at 08:44
  • @MOehm Got 2 errors. 'fgets': too few arguments for call. and fgets goes crazy and doesn't even ask for inputs. it outputs automatically after I enter number of students. – Ilan Aizelman WS Mar 18 '16 at 08:48
  • 1
    `fgets` certainly has some documentation, no? – M Oehm Mar 18 '16 at 08:52
  • @MOehm emm. what do you mean _some_? :P – Sourav Ghosh Mar 18 '16 at 09:00
  • @MOehm I can't use it, it asks to open a file in the 3rd argument. – Ilan Aizelman WS Mar 18 '16 at 09:03
  • @MOehm I tried fgets(classStudent[i].name,50,stdin); – Ilan Aizelman WS Mar 18 '16 at 09:08
  • @SouravGhosh Do you have any ideas? – Ilan Aizelman WS Mar 18 '16 at 09:09
  • 1
    [Please see this discussion on why not to cast the return value of `malloc()` and family in `C`.](http://stackoverflow.com/q/605845/2173917). – Sourav Ghosh Mar 18 '16 at 09:10
  • what happened when you used `fgets(classStudent[i].name,50,stdin);`? – Sourav Ghosh Mar 18 '16 at 09:11
  • @SouravGhosh Still same problem. skips the input of the name to the marks. – Ilan Aizelman WS Mar 18 '16 at 09:12
  • @SouravGhosh I'm editing the post with the full code. – Ilan Aizelman WS Mar 18 '16 at 09:12
  • If it worked yesterday, perhaps you now see the utility of a version control system which would *now* allow you to go back and see the differences or even bisect the range in between to find when the defect got introduced. Start early to use one. Even for small projects. – 0xC0000022L Mar 18 '16 at 09:13
  • @0xC0000022L How do you suggest to solve it then? I've edited my post with a full code. – Ilan Aizelman WS Mar 18 '16 at 09:14
  • @IlanAizelmanWS: generally I solve these things by attaching a debugger, using version control (which enables me to "go back in time") as well as static analyzers and other tools. But I try to stay clear from "please do my homework" questions here on SO ;) – 0xC0000022L Mar 18 '16 at 09:17
  • @0xC0000022L I've done this task completely myself, it's just a minor problem which wasn't there yesterday after I used _flushall(), I'm just trying to understand how can I solve this. – Ilan Aizelman WS Mar 18 '16 at 09:18

3 Answers3

0

As you have already been told in comments, the solution is to use a two-step approach: Read lines first, then scan these lines as appropriate. This reflects how users are going to answer your prompts, namely by providing the information and then hitting enter.

Here's a variant of your code which does that. I've also changed the main function, because it also used scanf and I've added a function to strip white space from the string input by fgets. (This function requires the <ctype.h> header.)

#include <ctype.h>

int main()
{
    char line[80];
    int size, i;

    puts("Enter the number of students:");
    if (fgets(line, sizeof(line), stdin) == NULL) exit(1);

    if (sscanf(line, "%d", &size) == 1 && size > 0) {
        student *arr = Create_Class(size);

        exStudents(arr, size);

        for (i = 0; i < size; i++) free(arr[i].name);
        free(arr);
    }

    return 0;
}

/*
 *      strip white space from beginning and end of string
 */
char *strip(char *str)
{
    size_t l = strlen(str);

    while (l > 0 && isspace((unsigned char) str[l - 1])) l--;
    str[l] = '\0';

    while (isspace((unsigned char) *str)) str++;
    return str;
}

/*
 *      Create students and prompt user for input
 */
student *Create_Class(int size)
{
    int i;

    student *classStudent = malloc(size * sizeof(student));

    for (i = 0; i < size; i++) {
        char line[80];
        char *p;
        int okay = 0;

        puts("Please enter your name:");
        if (fgets(line, sizeof(line), stdin) == NULL) exit(1);
        p = strip(line);

        classStudent[i].name = malloc(strlen(p) + 1);
        strcpy(classStudent[i].name, p);

        while (!okay) {
            int j = 0;

            okay = 1;
            puts("Please enter 4 marks:");
            if (fgets(line, sizeof(line), stdin) == NULL) exit(1);

            p = line;
            while (p && j < 4) {
                char *tail;
                int m = strtol(p, &tail, 10);

                if (p == tail) break;

                if (m < 1 || m > 100) {
                    puts("Illegal mark.");
                    okay = 0;
                }

                classStudent[i].marks[j++] = m;
                p = tail;
            }

            if (j < 4) {
                printf("Expected 4 marks, but got %d.\n", j);
                okay = 0;                
            }
        }

        Avg_Mark(&classStudent[i]);
    }

    return classStudent;
}

Please refrain from flushing buffers wihout reason. When a new-line character is written, stdout is flushed, so make it a rule to terminate all your strings with a new-line. New-lines at the beginning of strings instead of at the end are a sign of untidy output.

M Oehm
  • 28,726
  • 3
  • 31
  • 42
  • M oehm, it doesn't make sense. this code looks too complicated and messy. How can I use sscanf and fgets and make it with 2-3 line changes? – Ilan Aizelman WS Mar 18 '16 at 11:16
0

The problem in your code is that scanf does not consume the new-line returned by the user in:

scanf("%d", &size);

So when the program reaches:

fgets(classStudent[i].name,50,stdin);

the remaining new-line in stdin is received before the user can type anything. A solution is to replace the initial scanf call by fgets and atoi calls.

char size_str[5];
fgets(size_str,5,stdin);
size = atoi(size_str);

A combination of fgets and sscanf also works also fine in general to first process user inputs and then convert it.

The variant with sscanf is:

char size_str[5];
fgets(size_str,5,stdin);
sscanf(size_str,"%d\n",&size);

Note that it might be safe to stop the program if the value entered is too large. Here we allow from 0 up to 999.

Note also that you have to do the same change some lines below.

instead of:

        scanf("%d", &classStudent[i].marks[j]);

write:

        char mark_str[5];
        fgets(mark_str,5,stdin);
        sscanf(mark_str,"%d\n",&classStudent[i].marks[j]);

Hope this helps.

chrphb
  • 542
  • 5
  • 5
  • Well, if I use char size_str[4], I'm allowed to enter up to 4 students. what if I want 100 students? Can you please show me how it is done to fgets and sscanf? – Ilan Aizelman WS Mar 18 '16 at 11:09
0

SOLUTION:

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

typedef struct { //struct decleration
    char *name;
    int marks[4];
    float avg;
} student;

//functions decleration
student *Create_Class(int);
void Avg_Mark(student*);
void Print_One(student*);
void exStudents(student *s, int size);


int main() {
    /*variable declerations*/
    int i, size;
    char line[80];
    student *arr;

    /*Input number of students*/
    printf("\nEnter the number of students: \n");
    fgets(line, sizeof(line), stdin);
    sscanf(line, "%d", &size);

    /*Get name of students, marks, and calculate average above 85*/
    arr = Create_Class(size);
    exStudents(arr, size);

    /*Free memory*/
    for (i = 0; i < size; i++)
        free(arr[i].name);
    free(arr);

    getch();
}

student *Create_Class(int size) { /*Get names of each student, and their 4 marks.*/
    /*Variable declerations*/
    int i, j;
    char line[51];
    student *classStudent;

    /*Dynamic allocation to assign structure to every student*/
    classStudent = (student*)malloc(size * sizeof(student));

    /*Get name of students and their 4 marks*/
    for (i = 0; i < size; i++) {

        /*Variable decleration and dynamic allocation of 51 chars*/
        classStudent[i].name = (char*)malloc(51 * sizeof(char));
        int numOfmarks = 4;
        int sizeOfName;

        /*Input name of student*/
        printf("Please enter your name: \n");
        scanf("%s", classStudent[i].name);

        /*Input marks of student*/
        printf("Please enter 4 marks: ");
        for (j = 0; j < numOfmarks; j++) {
            scanf("%d", &classStudent[i].marks[j]);
        }
        /*Calculate average, and print averages of students above 85*/
        Avg_Mark(&classStudent[i]);
    }
    return classStudent;
}

/*Calculate averages of students*/
void Avg_Mark(student *s) {
    int i, numOfMarks = 4, sum = 0;

    for (i = 0; i < numOfMarks; i++) {
        sum += s->marks[i];
    }
    s->avg = (sum / 4.0);
}

/*Print average (if bigger than 85)*/
void Print_One(student *s) {
    printf("The average of %s is %0.1f\n", s->name, s->avg);
}
/*Check whether the average is bigger than 85*/
void exStudents(student *s, int size) {

    int flag = 1; //flag to check if there are any students with avg above 85

    while (size > 0) {
        if (s->avg > 85) {
            Print_One(s); //Print the average
            flag = 0; //We found atleast one student with avg > 85
        }
        s++; //Advance to next student
        size--;
    }
    if (flag)
        printf("\n There're no students with above 85 average.");
}
Ilan Aizelman WS
  • 1,630
  • 2
  • 21
  • 44
  • 1
    This solution works only for single-word names. It also breaks the correspondence between prompts and user input. Wait for someone typing "Jon Doe" as name. The name will be "John", there will be four uninitialised grades (because you try to scan "Doe" as number four times without checking the result) and the next student will be "Doe", at which time scanning resumes its original rhythm. – M Oehm Mar 18 '16 at 11:34
  • @MOehm Good enough for me. for now. – Ilan Aizelman WS Mar 18 '16 at 11:41
  • Yes, good enough for a programmer who thinks he can fix his programs with 2-3 line changes. You could even accept your own answer so that is is kept a a shining example for posterity. – M Oehm Mar 18 '16 at 11:43
  • @MOehm Well, it should work with scanf/gets, and it doesn't. which is very weird, it does work for my friend though. I don't understand why everything needs to be so complicated with like 30 line changes which makes everything look so messy. – Ilan Aizelman WS Mar 18 '16 at 11:47
  • @MOehm Guess what? It works perfectly with Visual Studio 2013. Just with addition of "flushall();" – Ilan Aizelman WS Mar 18 '16 at 18:00
  • Well, congratulations. It's not a portable solution, though. (I don't use VS2013 and seeing the `flushall` makes me cringe.) Taking user input is hard, especially when you want to cater for input errors, which you should. Don't and you'll shoot yourself in the foot sooner or later. (It's okay, though, if your program is a small utility that noone except you is ever going to use.) C is not particularly suited to take user input and `scanf` isn't the sharpest knife in the standard lib drawer either. The number of C questions concerning `scanf` hereb on SO is telling. – M Oehm Mar 18 '16 at 18:13
  • @MOehm I'm going to study JAVA in about 2 semesters, so I don't really care about c, except getting the homework done and understanding the fundumentals like structures etc. I'm more into Unity, developing games and applications using C# and JAVA. so I believe I won't shoot my foot, I'll keep using VS13 because its scanf is awesome. Just wanted to let you know :) – Ilan Aizelman WS Mar 18 '16 at 18:18
  • So you've wasted a lot of people's time for something you don't care about, insulting them on your way. Way to go, Ilan Aizelman! I can't say I've got high hopes for your career as a software developer. – M Oehm Mar 18 '16 at 18:24
  • @MOehm Sometimes I like to be arrogant, that's all. But yea, it was my mistake to rush and post here about that. Well, who knows, maybe I would never figure out I need to download VS13 if I didn't post. – Ilan Aizelman WS Mar 18 '16 at 18:31
  • 1
    Being arrogant every now and then is all welland good, but you have to be in a good position for it. Anyway, enough said. You've got your solution, case closed. – M Oehm Mar 18 '16 at 18:37