Structs and Typedef in C Programming: A Beginner's Guide

Structs and Typedef in C Programming: A Beginner's Guide

Learn how to use structs and typedef to organize your code and make it more efficient

Introduction

Structs in C programming or programming, in general, is a short form of Structures. While Typedef is a short form of the Type definition.

We all know that before we can work with a variable in C Programming, we often time have to declare a data type for that variable depending on the data that is to be stored in the variable. There are cases where we need to store an integer number, we will have to declare a data type of int since that is the data type of an integer, in the case of a string or character, we will have to make use of a char data type etc. Although there are many other data types in C, the most common ones remain the int, char, float etc. There might be other ones like the size_t, ssize_t, pid_t etc.

Structs are also a data type, but this time around it is a user-defined data type, that is you can use it to create a data type according to your need at that particular moment in time. Although you will have to still make use of the standard data types available in C in your struct. It gives you the flexibility of having a data type that can hold more than a single data type value. That is more like representing various standard data types as a single data type in C.

Typedef is closely related to structs, and that is why both are treated together. Typedef is used to give a data type a new name. Each data type has its name, like int, char, or float, so we can easily make use of typedef to give them a name of our choice.

How structs and typedef relates is that we can always use typedef to give our struct data type a single name, so that there will be no need to always call our struct alongside the name of the variable that will be holding whatsoever value we will be including in the struct.

Working with Structs (Structures)

Since we have talked about what structs are in our introductory part and also how useful they are, let us now talk about how to use and work with them.

Struct Definition and Declaration

It is best practice to always define our struct in the global scope of our program, that is outside of all our functions, at the top of our source code, just below our define and include header files. Alternatively, it is also a very good practice to define it in a header file and then include the header file in your source code whenever you want to make use of it in your code, that way it will make your code cleaner and well-structured.

Let us say we want a variable that will be able to store the name, age and email of a student, to do this, we can either decide to make use of our normal way of storing data by doing this:

int main(void)
{
    char *student_name = "Gideon Bature";
    int student_age = 500;
    char *student_email = "infoaboutstudent@email.com";

    return (0);
}

we can decide to define them individually like this or decide to do it in a cleaner, better and more professional way by using structs, since they all belong to a single student, why not we create a data type that will be able to hold all these data in a single variable, instead of defining three different variables, that is where struct comes in. For a struct, we will have something like this:

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
}

int main(void)
{

    return (0);
}

This is how you define a struct, you first start with the keyword struct followed by the name of the struct. The name of the struct here is very important because that is what will be used to access the elements of the struct, that is all the variables that are declared in the struct data type, and it is best practice to start the name of a struct with an uppercase letter so that it can be easily differentiated in your main source code. Not that using a lowercase letter will affect anything, it is just a best practice approach, so that people can easily be able to differentiate a struct data type name from a normal variable name in your code.


Since we have talked about the definition of struct, let us look at the declaration of struct, how do we define a struct, the declaration of struct is quite simple, it is the same way you declare any other data type in C. For example just as how we declare int for a variable that will be holding an integer and char for a variable that will be holding a character or string etc. In this case, you always start with the struct keyword itself followed by the name of the struct and then finally by the variable name that we will be using to store our data. Let us look declare the struct we have above:

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
}

int main(void)
{
    struct Student student;
    return (0);
}

This is how you declare a struct variable, you can see that I also used student as the variable that will be holding the data of the struct data type. If you observe, for the variable, I used the same as the name of the struct, only that in this case, the first letter is in lowercase, now this is not to make it look like it is compulsory to use the same name you used for your struct for your variable also, it is just to help me know what the variable is really for, more like having a descriptive name, aside that you can name your variable anything. Remember, you must use the same name you used for your struct while defining it in your declaration. So the code above can have a variable named for instance let's call it detail, so our declaration becomes:

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
}

int main(void)
{
    struct Student detail;
    return (0);
}

This code will do the same thing, the only difference now is that our variable is now detail and not student. Other than that, the function they will be performing remains the same.

Struct Definition should be done outside the functions, it should be defined in the global scope, or better still should be defined in a header file and the header file included in any file in which the struct is to be used.

While Struct Declaration is always done in the function scope, it is done in the function where it is needed, it could either be the main function or any other function that has access to the struct definition.

Accessing Elements of Structs

Even though we have seen how to declare and define our struct, we still need to know how to access the elements of a struct. And this is necessary because that is how we can be able to update the struct as well as get the values of each element that is declared in the struct. Let us use the code above to both set the values of elements in our struct, as well as to access them and print them to standard output.

#include <stdio.h>

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
};

int main(void)
{
    struct Student student;

    student.student_name = "Gideon Bature";
    student.student_age = 96;
    student.student_email = "info@email.com";

    printf("Name:  %s\n", student.student_name);
    printf("Age:   %d\n", student.student_age);
    printf("Email: %s\n", student.student_email);

    return (0);
}

From the code above, to access the element of a struct either to update the value or simply use the value, we use what is called the dot operator. You simply take the name of the variable you declared with the struct data type, and then add a dot at the end, followed by the name of the element that is defined in the struct whose value you want to access.

From our code, to access the elements: student_name, student_age and student_email, we simply need to use the declared struct variable, followed by a dot and then the name of the element. For example, to access the element student_name, we simply need to use student.student_name which will then give us access to the student_name element.

Pointers to Structs (Structures)

Just as how we can have a pointer to any normal data type, like a pointer to a char, a pointer to an int and a pointer to a float, so also we can have a pointer to a struct.

Whenever we have a pointer to a struct, there is a slight difference in how we can access each element of the struct.

If you understand what pointers are, they are simply variables that store the address of other variables in memory. So it means that whenever we have a pointer to a struct, that pointer will be holding the address of where the struct is located in memory. Simply put as it is pointing to the struct in memory.

To access the elements of the struct in this case, it will not be sufficient to just use the dot notation alone, we will need to do something first, which is to, first of all, dereference the pointer, which is getting the main element of the struct through the address before using the dot notation to finally access the element in the struct. Secondly, we can just use the arrow notation directly without having to, first of all, dereference before using the dot notation. We will be using the code above to illustrate the two methods using the same code.

#include <stdio.h>

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
};

int main(void)
{
    struct Student student;
    struct Student *ptr;
    ptr = &student;

    (*ptr).student_name = "Gideon Bature";
    (*ptr).student_age = 96;
    (*ptr).student_email = "info@email.com";

    printf("Name:  %s\n", (*ptr).student_name);
    printf("Age:   %d\n", (*ptr).student_age);
    printf("Email: %s\n", (*ptr).student_email);

    return (0);
}

Using the dot notation here, we first have to dereference the pointer to the struct which here was defined as ptr, before using the dot notation. Note that the dereferenced pointer is inside a bracket, this is deliberate and not just to make it fancy, since we first need to dereference the pointer before we use the dot notation to access the elements, we will need to wrap the dereferenced pointer (ptr) inside a bracket so that it can be carried out first, as any operation inside a bracket takes more priority over any other operation outside a bracket, and is executed first.


#include <stdio.h>

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
};

int main(void)
{
    struct Student student;
    struct Student *ptr;
    ptr = &student;

    ptr->student_name = "Gideon Bature";
    ptr->student_age = 96;
    ptr->student_email = "info@email.com";

    printf("Name:  %s\n", ptr->student_name);
    printf("Age:   %d\n", ptr->student_age);
    printf("Email: %s\n", ptr->student_email);

    return (0);
}

This second method makes use of the arrow notation, here we don't need to dereference the pointer for us to be able to access the element. We simply just use the arrow symbol -> which is a combination of the hyphen - followed by a greater than symbol > . Using the arrow symbol will give you direct access to the struct element while accessing them through the pointer to the struct.

Working with Typedef (Type definition)

The typedef which is also known as type definition is used to give a type a new name. We can Instead of using so many names for our data type, most especially for data types like struct and also for data types that include data type modifiers and the rest.

To use this typedef, just like our struct, it has to be defined in the global scope, outside of all of the functions, and if possible let it come immediately after the header files included. An alternative is to declare inside a header file and the header file be included at the top of the source code file in which the data type defined by the typedef will be used.

So to use this, you simply have to use the keyword typedef followed by what you want to rename as a data type and then put the name you want to rename the data type(s) to at the end of the line, followed by a semi-colon. So you can now use the new name you have named the data type(s) in your main function and other functions.

For example, let's say we want to declare a lot of variables in our code with the data type of unsigned long int Now instead of declaring this every single time we want to declare a variable, we can save ourselves the stress of having to type all these all the time by using typedef to give it a new name, so we can decide to do this:

#include <stdio.h>
typedef long unsigned int ul_int;

int main(void)
{
    ul_int number1 = 25416;
    ul_int number2 = -25416;

    printf("Number1: %lu\n", number1);
    printf("Number2: %lu\n", number2);

    return (0);
}

Following this example, I renamed the whole long unsigned int as ul_int, thus moving forward, I was able to use just ul_int instead and you can see how I tested it with both a positive and a negative number, and that is why for the negative number, which is -25416, the output was another positive number, which goes to show that the unsigned among the data type is also functional and also the number of digits returned also shows that the long is active as well, as a normal int doesn't have enough space for this number of digits.

Using Typedef (Type definition) with Structs (Structure)

Typedef can also be used with structures to define a new data type. This can be done in two ways, either alongside while defining the struct, or after defining the struct using the normal way struct is been used to name/define a new data type from another data type as we have seen initially.

So following the first method, let us take the code we used while explaining the struct:

#include <stdio.h>

typedef struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
} stu_t;

int main(void)
{
    stu_t student;

    student.student_name = "Gideon Bature";
    student.student_age = 96;
    student.student_email = "info@email.com";

    printf("Name:  %s\n", student.student_name);
    printf("Age:   %d\n", student.student_age);
    printf("Email: %s\n", student.student_email);

    return (0);
}

Just like how I explained it, the first method is to declare the struct starting with the typedef, and then at the end of the struct declaration we put the name of the struct data type, which in this our code is stu_t. So afterward whenever we want to declare a variable as a struct data type, we don't need to use the struct Student again, we just need to use the keyword stu_t. Another thing with this method is that you don't necessarily need to add a name to the struct while declaring it, since whether or not you put the struct name, the data type defined by the typedef will always be valid. So we can still have something like this:

#include <stdio.h>

typedef struct
{
    char *student_name;
    int student_age;
    char *student_email;
} stu_t;

int main(void)
{
    stu_t student;

    student.student_name = "Gideon Bature";
    student.student_age = 96;
    student.student_email = "info@email.com";

    printf("Name:  %s\n", student.student_name);
    printf("Age:   %d\n", student.student_age);
    printf("Email: %s\n", student.student_email);

    return (0);
}

And it will run just fine.


Now going by the second method, we first have to declare the struct and then afterward use our typedef to name the struct data type with our new keyword. Let us see how that is been done:

#include <stdio.h>

struct Student
{
    char *student_name;
    int student_age;
    char *student_email;
};

typedef struct Student stu_t;

int main(void)
{
    stu_t student;

    student.student_name = "Gideon Bature";
    student.student_age = 96;
    student.student_email = "info@email.com";

    printf("Name:  %s\n", student.student_name);
    printf("Age:   %d\n", student.student_age);
    printf("Email: %s\n", student.student_email);

    return (0);
}

unlike the first method, for this second method to work, you will have to declare the struct alongside the name, without the name of the struct, the compiler will throw an error.

Conclusion

The concept of structs and typedef is simple yet very powerful, in that it is used to achieve so many outstanding things in C Programming, so take your time and make sure you understand it very well before you proceed.

We will be looking into other concepts about Structs (Structures), such as arrays and structs, that is when we have an array inside of a struct and many other instances of array and struct.

Thank you for reading. You can connect with me on Twitter and LinkedIn.