Greystone
Greystone

Reputation: 13

Loop in a function to store data in a struct in c

I am trying to write a function that will allow me to put in the name, age, and salary of three people into a struct. I then want to print them out. I'm only a few weeks into my first programming class, please bear with me, and couldn't find any examples that made sense to me. I ran the loop without [i] but the data would just write over itself. (I think?) I don't want the code written for me but if anyone has any idea about the mechanics of what I'm trying to do or could point me in the correct direction I would be very grateful.(A lot of the printf's are just there for me at the moment and will be removed later.)

#include <stdio.h>
#include <conio.h>
#include <string.h>
#define SIZE 20
struct manager{
    char *name[SIZE];
    int *age[4];
    int *salary[10];
}employee;

void addInfo(struct manager* file[]);

int main(void){
    printf("\n\n");
    printf("\t***************************\n\t*Enter 1 to add employees.*\n\t*Enter 2 to search.\t  *\n\t*Enter 3 to update.\t  *\n\t*Enter 4 to delete.\t  *\n\t*Enter 0 to exit.\t  *\n\t***************************\n\n");
    addInfo(&employee);

    return 0;
}

void addInfo(struct manager* file[]){
    int i = 0;
    int counter = 1;
    int otherCounter = 1;
    for(i = 0; i < 3; i++){
        /*printf("Enter the name of employee #%d: \n"),otherCounter;
          gets(&employee.name[i]);
          printf("Enter the age of employee #%d: \n",otherCounter);
          gets(&employee.age[i]);
          printf("Enter the salary of employee #%d: \n",otherCounter);
          gets(&employee.salary[i]);
        */
        printf("Enter the name of employee #%d: \n"),otherCounter;
        scanf("%s",&employee.name[i]);
        printf("Employee name: %s\n",employee.name[i]);
        printf("Enter the age of employee #%d: \n",otherCounter);
        scanf("%d",&employee.age[i]);
        printf("Employee age: %d\n",employee.age[i]);
        printf("Enter the salary of employee #%d: \n",otherCounter);
        scanf("%d",&employee.salary[i]);
        printf("Employee salary: %d\n",employee.salary[i]);
        ++otherCounter;
    }

    for(i = 0; i < 3; i++){
        printf("\nEmployee #%d:\n",counter);
        printf("Employee name: %s\n",employee.name[i]);
        printf("Employee age: %d\n",employee.age[i]);
        printf("Employee salary: %d\n\n",employee.salary[i]);
        ++counter;
    }
}

Upvotes: 0

Views: 1936

Answers (2)

David C. Rankin
David C. Rankin

Reputation: 84569

Taking it one step beyond your original approach, do not take user input with scanf() and never user scanf() without checking the return. Why? scanf() is full of pitfalls for the new C programmer because characters will remain unread in stdin just waiting to bite you again if a matching failure occurs. Further, using scanf() to read into a string without providing a field-width modifer is just as unsafe and subject to explain as using gets(). See Why gets() is so dangerous it should never be used!

Instead, use a line-oriented input function like fgets() with an adequate sized buffer to read all user-input (don't skimp on buffer size, adjust if you are programming on a micro-controller, otherwise a 1K buffer is fine) This ensures that all characters entered by the user (in all but extreme cases of the cat stepping on the keyboard) are read and nothing will remain in stdin where it can effect your next attempted input. You can also use POSIX getline() which is the second commonly used line-oriented input function.

The only caveat using line-oriented input function is that they read and include the trailing '\n' in the buffer they fill. It isn't a problem, simply use strcspn() to get the number of character before the '\n' character and then overwrite the trailing '\n' with the nul-terminating character '\0', equivalent to plain old 0. See man 3 strspn E.g. if you read into a buffer, say char buf[1024] and you need to trim the trialing newline, simply use:

    buf[strcspn (buf, "\n")] = 0;

(you can include the '\r' carriage-return as well if on windows to trim beginning with whichever occurs first, such as if a DOS line-ending mysteriously made its way into the input using "\r\n" as the delimiter)

Don't #include <conio.h> as a header. It is an anachronism from the DOS days and is 100% non-portable to anything other than windows.

Putting It To Use To Read 1-Employee At A Time

In your case, and following your comment below @paddy's answer, you don't want to write your "add" function to "add" a specific number of anything, you want to write your add function to add one employee and to perform error detection and reporting though the return so you know whether the add succeeded or failed and how it failed. To make things easier, you can use a typedef for your struct, so you can refer to employee as a type rather than having to refer to struct manager, e.g.

#define NEMP     3          /* if you need a constant, #define one (or more) */
#define MAXNM   20
#define MAXC  1024

typedef struct {            /* typedef makes working with structs convenient */
    char name[MAXNM];
    unsigned age, salary;
} employee;

Now you can declare your add function to take an array of employee as a parameter and return an int with 1 indicating success, 0 indicating an error in input (such as someone entering "five" instead of 5 as a value) or returning -1 if the user cancels input by generating a manual EOF by pressing Ctrl+d or Ctrl+z on windows. You begin your function definition by declaring an adequately sized buffer for user input and a variable to use to check the length of name before copying to your struct, e.g.

int addemployee (employee *e)
{
    char buf[MAXC];
    size_t len;

To take the input for name (no need to prompt with printf() there are no conversions, just use fputs() which provides line-end control, compared to puts() and then validate the return by checking whether it is NULL indicating the user canceled input, trim the newline from buf and then copy to your employee name, e.g.

    fputs ("\nemployee name   : ", stdout);         /* no conversion - use fputs */
    if (fgets (buf, MAXC, stdin) == NULL) {         /* read all input with fgets() */
        fputs ("(user canceled input)\n", stderr);  /* validate EVERY input */
        return -1;
    }
    
    len = strcspn (buf, "\r\n");                    /* length before either "\r\n" */
    if (len >= MAXNM) {                             /* validate length */
        fputs ("error: name too long.\n", stderr);
        return 0;
    }
    buf[len] = 0;                                   /* trim trailing '\n' */
    
    memcpy (e->name, buf, len + 1);                 /* copy to name */

(you don't need strcpy() because you already know the length of the string, so there is no need to have strcpy() scan for the end-of-string again, just use memcpy())

For age and salaray, the approach is identical. Read with fgets() and then convert and validate the conversion using sscanf(), validating all the way:

    fputs ("employee age    : ", stdout);           /* prompt for age */
    if (!fgets (buf, MAXC, stdin)) {                /* validate EVERY input */
        fputs ("(user canceled input)\n", stderr);
        return -1;
    }
    if (sscanf (buf, "%u", &e->age) != 1) {         /* validate conversion */
        fputs ("error: invalid unsigned input\n", stderr);
        return 0;
    }

(note: you do not need to trim the '\n' from buf when converting to a numeric value as the '\n' is whitespace and will be ignored by the conversion)

When you need to output each employee, now write a function that takes the array of employee and the number of employees contained and output each, e.g.

void prnemployees (employee *e, int n)
{
    for (int i = 0; i < n; i++)
        printf ("\n%s\n  age    : %u\n  salary : %u\n", 
                e[i].name, e[i].age, e[i].salary);
}

With those two functions, your main() function reduces to simply declaring an array of employee and then looping while the number of valid employees entered is less than the array size, only incrementing your employee count on a successful return from addemployee(). Print the employees when done, e.g.

int main (void) {

    int n = 0;                              /* input counter */
    employee emp[NEMP] = {{ .name = "" }};  /* array of employee, initialize all zero */
    
    while (n < NEMP) {
        int result = addemployee (&emp[n]); /* add employee VALIDATE return */
        if (result == -1)                   /* if user canceled, stop input */
            break;
        else if (result == 1)               /* if good input, increment count */
            n++;
        /* for 0 return, do nothing, error message output in funciton */
    }
    
    prnemployees (emp, n); 
}

Putting it altogether, you would have:

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

#define NEMP     3          /* if you need a constant, #define one (or more) */
#define MAXNM   20
#define MAXC  1024

typedef struct {            /* typedef makes working with structs convenient */
    char name[MAXNM];
    unsigned age, salary;
} employee;

int addemployee (employee *e)
{
    char buf[MAXC];
    size_t len;
    
    fputs ("\nemployee name   : ", stdout);         /* no conversion - use fputs */
    if (fgets (buf, MAXC, stdin) == NULL) {         /* read all input with fgets() */
        fputs ("(user canceled input)\n", stderr);  /* validate EVERY input */
        return -1;
    }
    
    len = strcspn (buf, "\r\n");                    /* length before either "\r\n" */
    if (len >= MAXNM) {                             /* validate length */
        fputs ("error: name too long.\n", stderr);
        return 0;
    }
    buf[len] = 0;                                   /* trim trailing '\n' */
    
    memcpy (e->name, buf, len + 1);                 /* copy to name */
    
    fputs ("employee age    : ", stdout);           /* prompt for age */
    if (!fgets (buf, MAXC, stdin)) {                /* validate EVERY input */
        fputs ("(user canceled input)\n", stderr);
        return -1;
    }
    if (sscanf (buf, "%u", &e->age) != 1) {         /* validate conversion */
        fputs ("error: invalid unsigned input\n", stderr);
        return 0;
    }
    
    fputs ("employee salary : ", stdout);           /* prompt for salary */
    if (!fgets (buf, MAXC, stdin)) {                /* validate EVERY input */
        fputs ("(user canceled input)\n", stderr);
        return -1;
    }
    if (sscanf (buf, "%u", &e->salary) != 1) {      /* validate conversion */
        fputs ("error: invalid unsigned input\n", stderr);
        return 0;
    }
    
    return 1;
}

void prnemployees (employee *e, int n)
{
    for (int i = 0; i < n; i++)
        printf ("\n%s\n  age    : %u\n  salary : %u\n", 
                e[i].name, e[i].age, e[i].salary);
}

int main (void) {

    int n = 0;                              /* input counter */
    employee emp[NEMP] = {{ .name = "" }};  /* array of employee, initialize all zero */
    
    while (n < NEMP) {
        int result = addemployee (&emp[n]); /* add employee VALIDATE return */
        if (result == -1)                   /* if user canceled, stop input */
            break;
        else if (result == 1)               /* if good input, increment count */
            n++;
        /* for 0 return, do nothing, error message output in funciton */
    }
    
    prnemployees (emp, n); 
}

Example Use/Output

With intentional errors in input:

$ ./bin/addemployee

employee name   : Mickey Mouse
employee age    : 92
employee salary : 200000

employee name   : Minnie Mouse
employee age    : too old
error: invalid unsigned input

employee name   : Minnie Mouse
employee age    : 92
employee salary : 250000

employee name   : Pluto (dog)
employee age    : 90
employee salary : bone
error: invalid unsigned input

employee name   : Pluto (dog)
employee age    : 90
employee salary : 10000

Mickey Mouse
  age    : 92
  salary : 200000

Minnie Mouse
  age    : 92
  salary : 250000

Pluto (dog)
  age    : 90
  salary : 10000

The point being that user-input in C isn't difficult, but new C programmers generally make it difficult by choosing the wrong tool for the job and failing to check the return of the tool they do choose -- usually scanf(). Don't get me wrong, scanf() can be used properly for user-input, but until you have read, word-for-word man 3 scanf and understand every caveat and which conversion specifiers discard whitespace and which don't -- don't use it. Use a line-oriented approach with an appropriately size buffer and trim the '\n' if you are using the buffer as a character string.

Let me know if you have any further questions.

Upvotes: 1

paddy
paddy

Reputation: 63481

It looks like you're completely confused. All your definitions look weird and the way you're using the values is wrong.

Start with something like this:

#define MAX_NAME 20
#define MAX_EMPLOYEES 100

struct Employee
{
    char name[MAX_NAME];
    int age;
    int salary;
};

struct Employee employees[MAX_EMPLOYEES];

Make your function just read a single employee. If you really want the prompts to say which employee number you're reading, you can pass that in as a parameter.

void readEmployeeInfo(struct Employee *e)
{
    printf("Enter name:\n");
    scanf("%s", e->name);
    printf("Enter age:\n");
    scanf("%d", e->age);
    printf("Enter salary:\n");
    scanf("%d", e->salary);
}

Add other functions for things like displaying an employee record. This keeps it clean, and ensures a function just does one thing.

To read some number of employees in a loop:

for (int i = 0; i < 3; i++)
{
    readEmployeeInfo(&employees[i]);
}

That should get you started.

Upvotes: 1

Related Questions