A struct groups several related variables of possibly different types under one name, similar to an object without methods.
typedef struct {
char name[50];
int age;
float gpa;
} Student;
int main(void) {
Student s1 = {"Nadia", 22, 3.8f}; // initialize in declaration order
printf("%s is %d\n", s1.name, s1.age); // dot notation for direct access
Student *sp = &s1;
printf("%f\n", sp->gpa); // arrow notation, for access through a pointer
return 0;
}
Dot notation (s1.age) accesses a member directly on a struct value; arrow notation (sp->gpa) is shorthand for (*sp).gpa — dereferencing a struct pointer and then accessing a member — used whenever you're working through a pointer to a struct rather than the struct itself, which is the common case when passing structs to functions efficiently (passing a pointer avoids copying the whole struct).
typedef struct {...} Student; names the struct type Student directly, letting you write Student s1 instead of struct Student s1 everywhere — this is the standard idiom in modern C code. Structs can be nested inside other structs, and copying one struct into another (Student s2 = s1;) performs a full member-by-member copy, unlike arrays, which cannot be assigned this way.