Structs: Custom Data Types
So far we’ve worked with single values. An int here, a char there.
Real programs need to group related information together. That’s what structs do.
Think about a student record. You need a name, an age, and a grade. You could use three separate variables:
char student_name[50];
int student_age;
double student_grade;But that’s messy. What if you have 30 students? That’s 90 variables. And how do you keep track of which name goes with which age?
A struct lets you bundle related data into one package.
Your First Struct
struct Student {
char name[50];
int age;
double grade;
};This creates a new type called struct Student. It contains three pieces of data. Think of it like creating a form - it says what information belongs together.
Using a Struct
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
double grade;
};
int main(void) {
struct Student alice;
// Use the dot (.) to access each piece
strcpy(alice.name, "Alice Smith"); // We'll explain strcpy below
alice.age = 15;
alice.grade = 92.5;
printf("Name: %s\n", alice.name);
printf("Age: %d\n", alice.age);
printf("Grade: %.1f\n", alice.grade);
return 0;
}The dot . means “get this piece from the struct.” alice.age means “the age part of alice.”
Note: To copy text into a character array, we use strcpy() from <string.h>. You can’t use = to copy strings in C - that only works for numbers.
Filling Everything at Once
Instead of setting each field separately, you can fill everything at once:
struct Student alice = {"Alice Smith", 15, 92.5};Or if you want to be extra clear:
struct Student alice = {
.name = "Alice Smith",
.age = 15,
.grade = 92.5
};The second way is longer but easier to read. You can see exactly which value goes where.
The typedef Shortcut
Typing struct Student everywhere gets old. The typedef keyword creates a shorter name:
typedef struct {
char name[50];
int age;
double grade;
} Student; // Now "Student" is a type, not "struct Student"
int main(void) {
Student alice = {"Alice Smith", 15, 92.5}; // Much cleaner!
printf("%s is %d years old\n", alice.name, alice.age);
return 0;
}This is the pattern you’ll see in most C code.
Arrays of Structs
Just like you can have an array of integers, you can have an array of structs:
#include <stdio.h>
typedef struct {
char name[50];
int score;
} Player;
int main(void) {
Player players[] = {
{"Alice", 2500},
{"Bob", 1800},
{"Charlie", 3200}
};
int count = sizeof(players) / sizeof(players[0]);
// Find the highest score
int best = 0;
for (int i = 1; i < count; i++) {
if (players[i].score > players[best].score) {
best = i;
}
}
printf("Winner: %s with %d points!\n",
players[best].name, players[best].score);
return 0;
}Structs and Functions
You can pass structs to functions just like any other type:
#include <stdio.h>
typedef struct {
double x;
double y;
} Point;
// Function that takes a struct
void print_point(Point p) {
printf("(%.1f, %.1f)\n", p.x, p.y);
}
// Function that returns a struct
Point create_point(double x, double y) {
Point p = {x, y};
return p;
}
int main(void) {
Point p = create_point(3.0, 4.0);
print_point(p); // (3.0, 4.0)
return 0;
}Pass by Value (Structs Are Copied)
Just like with regular variables, when you pass a struct to a function, the function gets a copy:
void try_to_change(Point p) {
p.x = 999; // Only changes the copy
}
int main(void) {
Point p = {10, 20};
try_to_change(p);
printf("x = %.1f\n", p.x); // Still 10!
return 0;
}Pointers to Structs
To actually change a struct in a function, pass a pointer to it:
void actually_change(Point *p) {
p->x = 999; // The arrow -> accesses fields through a pointer
}
int main(void) {
Point p = {10, 20};
actually_change(&p);
printf("x = %.1f\n", p.x); // Now it's 999!
return 0;
}The Arrow Operator ->
When you have a pointer to a struct, use -> instead of . to access fields:
p.x- “the x field of p” (p is a struct)p->x- “the x field of what p points to” (p is a pointer to a struct)
The arrow -> is actually shorthand for (*p).x - “follow the pointer, then get the field.” The arrow is just easier to read.
Nested Structs
Structs can contain other structs:
typedef struct {
int day;
int month;
int year;
} Date;
typedef struct {
char name[50];
Date birthday;
Date enrollment;
} Student;
int main(void) {
Student alice = {
"Alice Smith",
{15, 6, 2009}, // birthday: June 15, 2009
{1, 9, 2024} // enrollment: September 1, 2024
};
printf("%s was born on %d/%d/%d\n",
alice.name,
alice.birthday.month,
alice.birthday.day,
alice.birthday.year);
return 0;
}A Practical Example: Student Grades
Let’s build something useful:
#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 30
typedef struct {
char name[50];
int scores[5];
double average;
} Student;
void calculate_average(Student *s) {
int total = 0;
for (int i = 0; i < 5; i++) {
total += s->scores[i];
}
s->average = total / 5.0;
}
void print_student(Student s) {
printf("%-20s ", s.name);
for (int i = 0; i < 5; i++) {
printf("%3d ", s.scores[i]);
}
printf(" Avg: %.1f\n", s.average);
}
int main(void) {
Student class[] = {
{"Alice", {92, 88, 95, 90, 87}, 0},
{"Bob", {78, 82, 75, 80, 85}, 0},
{"Charlie", {95, 98, 92, 96, 94}, 0}
};
int count = sizeof(class) / sizeof(class[0]);
// Calculate averages
for (int i = 0; i < count; i++) {
calculate_average(&class[i]);
}
// Print everyone
printf("%-20s Test Scores Average\n", "Name");
printf("------------------------------------------------\n");
for (int i = 0; i < count; i++) {
print_student(class[i]);
}
// Find top student
int top = 0;
for (int i = 1; i < count; i++) {
if (class[i].average > class[top].average) {
top = i;
}
}
printf("\nTop student: %s with %.1f average\n",
class[top].name, class[top].average);
return 0;
}Try It Yourself
- Create a
Rectanglestruct with width and height. Write functions to calculate area and perimeter. - Create a
Timestruct (hours, minutes, seconds). Write a function that adds two times together. - Create a
Bookstruct (title, author, year, price). Write a program that stores 5 books and finds the cheapest one.
Common Mistakes
- Forgetting the semicolon after the struct definition (the
}needs a;after it) - Using
.when you should use->(and vice versa) - Trying to use
=to copy strings into struct fields (usestrcpy()instead)
Next Up
In Part 14, we’ll learn about dynamic structs - creating structs at runtime and understanding how they’re stored in memory.
Enjoyed This?
If this helped something click, subscribe to my YouTube channel. More content like this, same approach - making things stick without insulting your intelligence. It’s free, it helps more people find this stuff, and it tells me what’s worth making more of.