CS140 Lecture notes -- Structs

  • James S. Plank
  • Directory: /home/plank/cs140/Notes/Structs
  • Lecture notes: http://www.cs.utk.edu/~plank/plank/classes/cs140/Notes/Structs
  • Fri Aug 31 11:44:01 EDT 2007

    Structs

    Structs are a second way (arrays being the first) of aggregating types. They are much more flexible than arrays, and resemble C++ classes, only stripped down so that there are no methods, no constructor/destructors, no differentiation between public/private/protected variables, and no automatic initializaion.

    Let's take an example where you want to keep information (such as first name, last name, age) on a person, so that all the information on the person is in one place. This is a perfect place for a struct.

    Look at struct1.c:

    #include <stdio.h>
    #include <string.h>
    
    typedef struct {
      char *fname;
      char *lname;
      int  age;
    } Person;
    
    print_person(Person *p)
    {
      printf("%s %s: Age: %d\n", p->fname, p->lname, p->age);
    }
    
    int main()
    {
      char line[1000];
      Person p;
    
      printf("Enter first name: "); 
      if (fgets(line, 1000, stdin) == NULL) exit(0); 
      line[strlen(line)-1] = '\0';
      p.fname = strdup(line);
    
      printf("Enter last name: "); 
      if (fgets(line, 1000, stdin) == NULL) exit(0); 
      line[strlen(line)-1] = '\0';
      p.lname = strdup(line);
    
      printf("Enter age: "); 
      if (fgets(line, 1000, stdin) == NULL) exit(0); 
      p.age = atoi(line);
    
      print_person(&p);
      return 0;
    }
    

    Note the typedef statement. This means that you can use the name Person to access the struct. Without the typedef, you have to do something like:

    struct person {
      char *fname;
      char *lname;
      int  age;
    };
    
    And then you name variables such as p with ``struct person p''. To see an example of that, see struct1a.c, which is equivalent to struct1.c without the typedef. Note how the typedef makes the program easier to read.

    Now, struct1 statically allocates one Person struct as a local variable, and then fills it in with info from standard input.

    Then print_person() is called to print out the struct. Note, if I have a struct (such as p in main()), then I access its fields with a dot. If I have a pointer to a struct (such as p in print_person()), then I access its fields with an arrow (->). Note also how I called print_person() with the address of p. You should always do this with a struct - calling it with p as an argument will either be disallowed by the compiler, or worse yet, will make a copy of p. Get into the habit of passing pointers.

    You will typically be dealing with pointers to structs, so you should get used to using the arrows rather than the dots.


    Struct2.c

    Struct2.c shows a more typical use of structs:

    #include <stdio.h>
    #include <string.h>
    
    typedef struct {
      char *fname;
      char *lname;
      int  age;
    } Person;
    
    print_person(int i, Person *p)
    {
      printf("Person %3d: %-20s %-20s Age: %3d\n", i, p->fname, p->lname, p->age);
    }
    
    int main()
    {
      char line[1000];
      Person *parray;
      int n, i;
    
      printf("How many people will you enter? ");
      if (fgets(line, 1000, stdin) == NULL) exit(0); 
      n = atoi(line);
      if (n <= 0) exit(0);
    
      parray = (Person *) malloc(sizeof(Person) * n);
      if (parray == NULL) { perror("malloc"); exit(1); }
    
      for (i = 0; i < n; i++) {
        printf("Enter first name of person %d: ", i); 
        if (fgets(line, 1000, stdin) == NULL) exit(0); 
        line[strlen(line)-1] = '\0';
        parray[i].fname = strdup(line);
      
        printf("Enter last name of person %d: ", i); 
        if (fgets(line, 1000, stdin) == NULL) exit(0); 
        line[strlen(line)-1] = '\0';
        parray[i].lname = strdup(line);
      
        printf("Enter age of person %d: ", i); 
        if (fgets(line, 1000, stdin) == NULL) exit(0); 
        parray[i].age = atoi(line);
      } 
    
      for (i = 0; i < n; i++) {
        print_person(i, &(parray[i]));
      }
      return 0;
    }
    

    Instead of reading in one person , we prompt the user for a number of people to read in, and then we read in that many people. In order to have this work with any number of people, we must use malloc() to allocate an array with the appropriate number of people. Then we fill it in, just as struct1.c. Finally, we traverse the array and print each person. Note how I make the call to print_person(). The following also would have worked:

      print_person(i, parray+i);
    
    Personally, I like &(parray[i]) because it states more plainly what you're doing, but both ways work.

    Also, look at print_person(). I have put field widths into the printf() statement. The format string says to:

    Try it out (I like this example as it shows how long ago I wrote the original version of these notes -- I was 32 in 1998):
    UNIX> struct2
    How many people will you enter? 3
    Enter first name of person 0: Jim 
    Enter last name of person 0: Plank
    Enter age of person 0: 32
    Enter first name of person 1: Brad
    Enter last name of person 1: Vander Zanden
    Enter age of person 1: 35
    Enter first name of person 2: Katie
    Enter last name of person 2: Plank
    Enter age of person 2: 4
    Person   0: Jim                  Plank                Age:  32
    Person   1: Brad                 Vander Zanden        Age:  35
    Person   2: Katie                Plank                Age:   4
    UNIX> 
    
    Note that by using fgets() I was able to handle that two-word last name.

    Struct3.c

    Struct3.c tweaks struct2.c so that parray is now an array of pointers to Person structs, rather than an array of Person structs.

    #include <stdio.h>
    #include <string.h>
    
    typedef struct {
      char *fname;
      char *lname;
      int  age;
    } Person;
    
    print_person(int i, Person *p)
    {
      printf("Person %3d: %-20s %-20s Age: %3d\n", i, p->fname, p->lname, p->age);
    }
    
    int main()
    {
      char line[1000];
      Person **parray, *p;
      int n, i;
    
      printf("How many people will you enter? ");
      if (fgets(line, 1000, stdin) == NULL) exit(0); 
      n = atoi(line);
      if (n <= 0) exit(0);
    
      parray = (Person **) malloc(sizeof(Person *) * n);
      if (parray == NULL) { perror("malloc"); exit(1); }
    
      for (i = 0; i < n; i++) {
    
        p = (Person *) malloc(sizeof(Person));
        if (p == NULL) { perror("malloc"); exit(1); }
        parray[i] = p;
    
        printf("Enter first name of person %d: ", i); 
        if (fgets(line, 1000, stdin) == NULL) exit(0); 
        line[strlen(line)-1] = '\0';
        p->fname = strdup(line);
      
        printf("Enter last name of person %d: ", i); 
        if (fgets(line, 1000, stdin) == NULL) exit(0); 
        line[strlen(line)-1] = '\0';
        p->lname = strdup(line);
      
        printf("Enter age of person %d: ", i); 
        if (fgets(line, 1000, stdin) == NULL) exit(0); 
        p->age = atoi(line);
      } 
    
      for (i = 0; i < n; i++) {
        print_person(i, parray[i]);
      }
      return 0;
    }
    

    Each Person struct is now allocated right before being filled in. I think that this code looks cleaner than struct2.c, but of course, individual tastes do vary.