C Data Structures

Lecture recording

C doesn’t have classes and objects, it isn’t an object oriented programming language.

C does allow you to define custom data structures. These structures can contain variables, arrays, even more data structures. You can think of a structure as like a class without any methods.

Defining A Structure

struct [structure tag] {
    member definition;
    member definition;
    ...
    member definition;
} [one or more structure variables]

The struct keyword is used to create a structure.
The structure tag is the name you give the structure - equivalent to a class name.
The structure variables are the names of specific structure instances - equivalent to object names.

Either structure tag or structure variables can be left out but not both.

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

The code above creates a data structure type called Books. Books can be used as a variable type.

struct Books Book1;
struct Books Book1;

Alternatively you can create a structure without naming it, using it to create variables without creating a data structure type that can be reused.

struct {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book1, book2;

And you can create a named structure and make variables of that type all at the same time.

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book1, book2;

However you create your structure you can use typedef to define a type so you don’t have to use the struct keyword everytime you want to create a variable of your data structure.

typedef struct Books Book;

Book book1, book2;

And finally you can bring all these together to make a new type in one.

typedef struct Book {
   char title[50];
   char author[50];
   char subject[100];
   int book_id;
} Book;
    
Book book1;
Book book2;

C likes to give you options!

Accessing Struct Members

We access the members of a structure using the member access operator (.).

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

typedef struct Book {
   char title[50];
   char author[50];
   char subject[100];
   int book_id;
} Book;

int main() {

  Book book1;

  // Store data in book1
  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

}

Similarly you can retrieve the values

  printf("Title : %s\n", book1.title);
  printf("Author : %s\n", book1.author);
  printf("Subject : %s\n", book1.subject);
  printf("Book ID : %d\n", book1.book_id);

Structures as Function Arguments

You can pass structures to function the same way you pass any other variable or pointer.

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

  typedef struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

void printBook( Book book );

int main(void) {
  
  Book book1;
  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

  printBook(book1);


  return 0;
}

void printBook( Book book )
{
  printf("Title : %s\n", book.title);
  printf("Author : %s\n", book.author);
  printf("Subject : %s\n", book.subject);
  printf("Book ID : %d\n", book.book_id);
}

If you do not use the typedef keyword you will need to use struct throughout.

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

  struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

int main(void) {
  
  struct Book book1;
  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

  printf("Title : %s\n", book1.title);
  printf("Author : %s\n", book1.author);
  printf("Subject : %s\n", book1.subject);
  printf("Book ID : %d\n", book1.book_id);



  return 0;
}

Pointers to Structures

You can define pointers to structures in the same way you define a pointer to any other variable.

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

typedef struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

int main(void) {
  
  Book book1;
  Book *book1_ptr;

  book1_ptr = &book1;
}

We can access the members of a structure directly from the pointer using the indirection operator ->.

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

typedef struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

int main(void) {
  
  Book book1;
  Book *book1_ptr;
  book1_ptr = &book1;

  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

  printf("Title : %s\n", book1_ptr->title);
  printf("Author : %s\n", book1_ptr->author);
  printf("Subject : %s\n", book1_ptr->subject);
  printf("Book ID : %d\n", book1_ptr->book_id);

}

We can, of course, skip creating a pointer variable and get the address directly from the struct variable when we need it.

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

typedef struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

int main(void) {
  
  Book book1;
  
  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

  printf("Title : %s\n", &book->title);
  printf("Author : %s\n", &book->author);
  printf("Subject : %s\n", &book->subject);
  printf("Book ID : %d\n", &book->book_id);

}

So we have three ways to access the data - from the struct variable with ., from a pointer variable using ->, and by getting a pointer from the variable and using ->.

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

typedef struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

int main(void) {
  
  Book book1;
  Book *book1_ptr;
  book1_ptr = &book1;

  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

  // using a struct variable and .
  printf("Title : %s\n", book1.title);
  printf("Author : %s\n", book1.author);
  printf("Subject : %s\n", book1.subject);
  printf("Book ID : %d\n", book1.book_id);

  // using a pointer variable and ->
  printf("Title : %s\n", book1_ptr->title);
  printf("Author : %s\n", book1_ptr->author);
  printf("Subject : %s\n", book1_ptr->subject);
  printf("Book ID : %d\n", book1_ptr->book_id);

  // getting a pointer from a struct variable and using ->
  printf("Title : %s\n", &book1->title);
  printf("Author : %s\n", &book1->author);
  printf("Subject : %s\n", &book1->subject);
  printf("Book ID : %d\n", &book1->book_id);
}

Passing pointers to structures to functions

You can pass pointers to structures to function the same way you pass any other pointer.

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

  typedef struct Book {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} Book;

void printBook( Book *book ); // Note the change here, parameter is a pointer to a struct

int main(void) {
  
  Book book1;
  strcpy(book1.title, "Lovecraft Country");
  strcpy(book1.author, "Matt Ruff");
  strcpy(book1.subject, "Horror");
  book1.book_id = 19334;

  printBook(&book1);

  // or alternatively
  Book *book_ptr = &book1;
  printBook(book_ptr);
  // but why bother creating the pointer variable if you aren't going to use it again?

  return 0;
}

void printBook( Book *book )
{
  printf("Title : %s\n", book->title);
  printf("Author : %s\n", book->author);
  printf("Subject : %s\n", book->subject);
  printf("Book ID : %d\n", book->book_id);
}

Structs within Structs

The members within a struct do not need to be simple primitive variables, they can also be other structs you have created.

For example we could create a struct to store the corners of a square like this :

typedef struct square {
    float corner_1_x;
    float corner_1_y;
    float corner_2_x;
    float corner_2_y;
    float corner_3_x;
    float corner_3_y;
    float corner_4_x;
    float corner_4_y;
} square;

but it is very clumsy. We would be better first defining a point that contains an x and a y value and using that in square.

typedef struct point {
    float x;
    float y;
} point;

typedef struct square {
    point corner1;
    point corner2;
    point corner3;
    point corner4;
} square;

Of course we’d be better off still is we used an array.

typedef struct point {
    float x;
    float y;
} point;

typedef struct square {
    point corners[4];
} square;

We use it something like this

#include <stdio.h>

typedef struct point {
    float x;
    float y;
} point;

typedef struct square {
    point corners[4];
} square;

void setPoint(square,int,float,float);
void printSquare(square);

int main(void) {

  square my_square;

  setPoint(my_square,0,1.0,1.0);
  setPoint(my_square,1,4.0,1.0);
  setPoint(my_square,2,4.0,4.0);
  setPoint(my_square,3,1.0,4.0);

  printSquare(my_square);
  return 0;
}

void setPoint(square a_square,int corner,float x,float y)
{
  a_square.corners[corner].x = x;
  a_square.corners[corner].y = y;  
}

void printSquare(square a_square)
{
  for (int i=0; i<4; i++)
  {
    printf("Corner %d (%f,%f)\n", i, a_square.corners[i].x, a_square.corners[i].y);
  }
}

which gives us the output

Corner 0 (0.000000,0.000000)
Corner 1 (0.000000,0.000000)
Corner 2 (0.000000,0.000000)
Corner 3 (-0.000000,0.000000)

That’s the wrong output! What’s gone wrong, why aren’t we setting the values?

Lets add the line printf("Corner %d (%f,%f)\n", corner, a_square.corners[corner].x, a_square.corners[corner].y); to the end of the setPoint function and see what happens.

Corner 0 (1.000000,1.000000)
Corner 1 (4.000000,1.000000)
Corner 2 (4.000000,4.000000)
Corner 3 (1.000000,4.000000)
Corner 0 (0.000000,0.000000)
Corner 1 (0.000000,0.000000)
Corner 2 (0.000000,0.000000)
Corner 3 (-0.000000,0.000000)

The first time the corners are printed out (from setPoint) the values are set but when we print them from printSquare the values have been rest to zero. What is going on?

Well the problem here is we pass the square data structure by value. That means, just like with primitive variables, a new local value is created and the values are copied in to it. It is the local variable version a_square that is changed, not the original on in my_square. If we want to change the values of a structure in a function we have to pass a pointer to the address of that structure variable to the function.

#include <stdio.h>

typedef struct point {
    float x;
    float y;
} point;

typedef struct square {
    point corners[4];
} square;

void setPoint(square*,int,float,float);
void printSquare(square);

int main(void) {

  square my_square;

  setPoint(&my_square,0,1.0,1.0);
  setPoint(&my_square,1,4.0,1.0);
  setPoint(&my_square,2,4.0,4.0);
  setPoint(&my_square,3,1.0,4.0);

  printSquare(my_square);
  return 0;
}

void setPoint(square *a_square,int corner,float x,float y)
{
  a_square->corners[corner].x = x;
  a_square->corners[corner].y = y;
}

void printSquare(square a_square)
{
  for (int i=0; i<4; i++)
  {
    printf("Corner %d (%f,%f)\n", i, a_square.corners[i].x, a_square.corners[i].y);
  }

}

Now our setPoint function works as intended.

Corner 0 (1.000000,1.000000)
Corner 1 (4.000000,1.000000)
Corner 2 (4.000000,4.000000)
Corner 3 (1.000000,4.000000)

Now because we’ve passed a pointer to the square data structure we are using that pointer to access the memory address the original square struct is stored in and we are updateing that.

Code