Using structs allows you to group variables of different data types under a single name.
It’s the primary tool in C for creating complex user-defined data types, similar to objects in other languages, but without inheritance or methods.
pure struct: ‘Person’
// Definition of the 'Person' structure
struct Person {
char name[50]; // Character array (text string)
int age; // Integer
float height; // Floating-point number
};
Don’t forget the final semicolon (;)
then we use the struct (complex data type):
struct Person p1;
struct Person p2;
You should always use the struct keyword
Typedef
Using typedef: To simplify and avoid writing struct repeatedly, typedef is used.
typedef struct {
char name[50];
int age;
} Person; // 'Person' is now a new data type.
you make use of that struct:
Person p3;
Person p4;
set the value of a field of the struct:
only for strings:
strcpy(p1.name, "Ana");
for other fields:
p1.age = 30;
reading values in fields of a struct:
printf("Name: %s\n", p1.name); // Output: Name: Ana
Arrow Operator (->)
It is used when accessing the struct through a pointer
// Declare and allocate memory for a pointer to Person
Person *ptr_p5 = (Person *)malloc(sizeof(Person));
// 1. Assign values using the pointer
strcpy(ptr_p5->name, "Luis");
ptr_p5->age = 25;
// 2. Read values
printf("Age: %d\n", ptr_p5->age); // Output: Age: 25
free(ptr_p5); // Free the memory
The arrow operator (->) is simply a shorthand way to dereference the pointer and access the member: (*ptr_p5).name is equivalent to ptr_p5->name
structs as Function Arguments
- Pass by Value (Copy)
- The function receives a complete copy of the structure.
- Any modifications within the function will not affect the original structure
void show_person_by_value(Person p) {
printf("Copied age: %d\n", p.age);
p.age = 0; // Only the local copy changes
}
- Pass by Reference (Pointer)
- The function receives the memory address of the structure (a pointer)
- This is more efficient, especially for large structures, and allows the function to modify the original structure
void modify_person_by_reference(Person * ptr_p) {
ptr_p->age += 1; // Modify the original structure
printf("New age: %d\n", ptr_p->age);
}
This is the most efficient way to work with structs in functions
Nested Structures
a structure can contain another structure
typedef struct {
int day;
int month;
int year;
} Date;
typedef struct {
char description[100];
Date creation_date; // Nested structure
} Task;
// Access:
Task task;
strcpy(task.description, "Complete the project");
Date d = {15, 8, 2023};
task.creation_date = d; // Assigning nested structure
printf("Task creation year: %d\n", task.creation_date.year);
printf("Task creation month: %d\n", task.creation_date.month);
printf("Task creation day: %d\n", task.creation_date.day);
printf("Task description: %s\n", task.description);
Self-Referencing Data Structures
They are used to create dynamic data structures such as linked lists or trees, since a field of the structure is a pointer to another structure of the same type
// Using a struct name for self-referencing
typedef struct Node {
int value;
struct Node *next; // Pointer to the next node
} Node;
If you happen to have two directories and files “a” and “b”, such that a/b
and you need an include “xxxx” in a and make use of it
and b also needs to include “xxxx” and make use of it
The C compiler will throw a redistricting error, something exists that is declared twice.
To solve this:
- we do #include “xxxx.h” in a.c
- and in b.h struct xxxx; // ESSENTIAL: Forward declaration
- in b.c #include “b.h” // Include the structure definition
- a use to b
- In the .c we put #include “x.h” of the .h, we never put #include “x.c” (we don’t put the .c)
It could be a cyclic reference problem, I remember it happened to me in Golang due to a design problem.






a working example: