Kodewick
Lesson 23 of 30Contents

Structs

Lesson 23 of 30 · Part 5 · Your own types · about 15 minutes

A student has a name and a score. Until now we could only keep them in two separate variables: name and score. With many students we'd need two arrays and would have to keep them lined up by hand. A struct puts several related pieces of data into one variable:

main.c
1#include <stdio.h>2 3struct student {4    char name[20];5    int score;6};7 8int main(void) {9    struct student s = { "Ana", 95 };10    printf("%s: %d points\n", s.name, s.score);11    return 0;12}
Output
Ana: 95 points

Press Step: in memory, s is drawn as one big box with two smaller cells inside, name and score. They are two parts of one variable, and they always stay together.

Defining a struct

struct student { ... }; defines a new type: it says what a variable of this kind holds. Each item inside is called a member:

PartIn the example aboveWhat it is
Keywordstructsays a struct follows
Namestudentwhat this struct is called; you use it as struct student
Memberschar name[20]; int score;each one has a type and a name, just like declaring a variable
End};a semicolon after the closing brace, easy to forget

The definition only describes what it looks like and takes no memory. Writing struct student s; is what makes an actual variable. Use the dot . to reach a member, for reading and for writing. You can set members one at a time; for a string member, use strcpy (lesson 14):

main.c
1#include <stdio.h>2#include <string.h>3 4struct product {5    char name[20];6    double price;7    int stock;8};9 10int main(void) {11    struct product p;12    strcpy(p.name, "Notebook");13    p.price = 12.5;14    p.stock = 40;15    p.stock = p.stock - 3;16    printf("%s costs %.2f, %d left\n", p.name, p.price, p.stock);17    return 0;18}
Output
Notebook costs 12.50, 37 left

p.stock works like any other int: you can assign to it and use it in arithmetic.

Initializing

When you declare it, you can give all the values at once in curly braces, in the order the members were defined. If you give fewer values, the rest of the members are 0 (an empty string for a string):

main.c
1#include <stdio.h>2 3struct product {4    char name[20];5    double price;6    int stock;7};8 9int main(void) {10    struct product a = { "Pencil", 1.5, 120 };11    struct product b = { "Eraser", 2.0 };12    printf("%s %.2f %d\n", a.name, a.price, a.stock);13    printf("%s %.2f %d\n", b.name, b.price, b.stock);14    return 0;15}
Output
Pencil 1.50 120
Eraser 2.00 0

The order matters: written as { 1.5, "Pencil", 120 }, the values land in the wrong members, and the compiler gives an error or a warning.

Assignment copies

You can't assign a whole array with =, but you can assign a struct. b = a; copies every member of a into b, even a char array inside it. After the copy they are two separate variables:

Guess first

What does this program print?

1#include <stdio.h>2 3struct student {4    char name[20];5    int score;6};7 8int main(void) {9    struct student a = { "Ana", 80 };10    struct student b = a;11    b.score = 100;12    printf("%d %d\n", a.score, b.score);13    return 0;14}

But you can't compare two structs with ==. The compiler won't accept it:

main.c
1#include <stdio.h>2 3struct point {4    int x;5    int y;6};7 8int main(void) {9    struct point a = { 1, 2 };10    struct point b = { 1, 2 };11    if (a == b) {12        printf("Same\n");13    }14    return 0;15}
Output
main.c: In function 'main':
main.c:11:11: error: invalid operands to binary == (have 'struct point' and 'struct point')
   11 |     if (a == b) {
      |           ^~

To compare them, compare member by member: a.x == b.x && a.y == b.y.

Arrays of structs

Structs can go in an array too. songs[i] is one song, and songs[i].seconds is how long that song is. A playlist:

main.c
1#include <stdio.h>2 3struct song {4    char title[30];5    int seconds;6};7 8int main(void) {9    struct song songs[3] = {10        { "Morning", 269 },11        { "By the Sea", 226 },12        { "Way Home", 223 }13    };14    int total = 0;15    for (int i = 0; i < 3; i++) {16        printf("%d. %s (%d:%02d)\n", i + 1, songs[i].title, songs[i].seconds / 60, songs[i].seconds % 60);17        total += songs[i].seconds;18    }19    printf("Total: %d min %d s\n", total / 60, total % 60);20    return 0;21}
Output
1. Morning (4:29)
2. By the Sea (3:46)
3. Way Home (3:43)
Total: 11 min 58 s

Each song's title and length stay together, so they can't get out of step. %02d pads the seconds with a 0 when there's only one digit, so 4:05 never shows as 4:5.

Passing to functions

A struct can be a parameter. Just like an int, it is passed by value: the parameter is a complete copy (lesson 8). So when the function changes it, the outside doesn't see it:

main.c
1#include <stdio.h>2 3struct account {4    char owner[20];5    int balance;6};7 8void deposit(struct account a, int amount) {9    a.balance += amount;10    printf("In the function: %d\n", a.balance);11}12 13int main(void) {14    struct account acc = { "Ana", 100 };15    deposit(acc, 50);16    printf("In main: %d\n", acc.balance);17    return 0;18}
Output
In the function: 150
In main: 100

Press Step. When you go into deposit, its stack frame gets a whole a of its own, with owner and balance both copied over. Only this copy changes, and it disappears when the function ends.

One way out is to have the function return the changed struct and let the caller store it back:

main.c
1#include <stdio.h>2 3struct account {4    char owner[20];5    int balance;6};7 8struct account deposit(struct account a, int amount) {9    a.balance += amount;10    return a;11}12 13int main(void) {14    struct account acc = { "Ana", 100 };15    acc = deposit(acc, 50);16    printf("%s's balance: %d\n", acc.owner, acc.balance);17    return 0;18}
Output
Ana's balance: 150

Tip: Copying a big struct on every call is wasteful. More often you pass its address, and the function changes it through a pointer. That's the next lesson.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

A book

Define struct book with a title (a char array of 20 bytes) and pages (an int). Declare the book { "Dune", 412 } and print Dune: 412 pages.

It should print
Dune: 412 pages
exercise1.c
1#include <stdio.h>2 3// define struct book here4 5int main(void) {6 7    return 0;8}
Output
Press Run or Ctrl+Enter
Exercise 2

The cheapest

Find the product with the lowest price in items and print Cheapest: Toothbrush 3.50.

It should print
Cheapest: Toothbrush 3.50
exercise2.c
1#include <stdio.h>2 3struct product {4    char name[20];5    double price;6};7 8int main(void) {9    struct product items[4] = {10        { "Toothpaste", 8.90 },11        { "Towel", 12.00 },12        { "Toothbrush", 3.50 },13        { "Soap", 4.20 }14    };15 16    return 0;17}
Output
Press Run or Ctrl+Enter
Exercise 3

The sale that didn't happen

half_price is meant to cut the price in half, but the old price still shows. Fix it: have the function return the changed product, and have main store it back in p. The program should print Towel 6.00.

It should print
Towel 6.00
exercise3.c
1#include <stdio.h>2 3struct product {4    char name[20];5    double price;6};7 8void half_price(struct product p) {9    p.price = p.price / 2;10}11 12int main(void) {13    struct product p = { "Towel", 12.00 };14    half_price(p);15    printf("%s %.2f\n", p.name, p.price);16    return 0;17}
Output
Press Run or Ctrl+Enter

What you learned

  • struct name { members; ... }; defines a new type that keeps related data together; it ends with a semicolon.
  • Initialize it with { ... } in member order; members you leave out are 0. Or set members one at a time, using strcpy for strings.
  • The dot . reads and writes a member; s.score works like any other variable.
  • b = a; copies the whole struct, and afterward the two are separate; structs can't be compared with ==.
  • An array of structs keeps a list of records together: songs[i].title.
  • A struct passed to a function is a copy; to change the one outside, return the new struct (or pass a pointer, next lesson).