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:

https://gitlab.com/com.leibnix/structsinc