On this page

Structs and Enums

Every type you have used so far came with the language or its library: int, double, bool, char, std::string, std::vector. This lesson is about making types of your own.

There are two kinds. A struct groups several values into one. An enum class is a type with a short list of named values and no others. Together they let a program describe the thing it is about, such as a position, a student or a game board, in one variable of one type.

Values that belong together

A position on the canvas is two numbers. Until now a program kept them in two variables, such as door_x and door_y, and nothing but their names said that they belong together. A function that needs two positions takes four ints, and a function cannot return a position at all, because it returns one value.

A struct makes the pair into one thing.

#include <iostream>

struct Spot {
    int x;
    int y;
};

int main() {
    Spot door{120, 250};
    Spot window{300, 120};
    std::cout << "Two spots made\n";
}
Two spots made

The five lines above main are a struct definition. It has these parts.

Part What it is
struct the word that starts the definition
Spot the name of the new type. This course starts the names of its own types with a capital letter, so that a type and a variable are easy to tell apart
{ ... } the members: the values every Spot holds. Each is written like a declaration, with a type and a name
; a semicolon after the closing brace

That last semicolon is easy to forget, because the closing brace of a function has none. When it is missing the compiler rejects the program, and its message often names the line after the definition.

The definition creates a type. It does not create a variable, and it holds no numbers. It only says what a Spot is: an int called x and an int called y.

Variables come from declarations, as always. Spot door{120, 250}; reads: the type Spot, the name door, and in braces a value for each member. The values go to the members in the order the definition lists them, so door holds an x of 120 and a y of 250. window is a second Spot, with an x and a y of its own.

So this program has four ints, two inside each variable. It prints nothing about them yet, because the next section is about how to reach a member.

One member at a time

To use one member, write the variable, a dot, and the member's name.

#include "scimigo.h"
using namespace scimigo;

struct Spot {
    int x;
    int y;
};

int main() {
    canvas(600, 400);
    Spot ball{100, 250};
    circle(ball.x, ball.y, 25, "orange");

    ball.x = ball.x + 150;
    circle(ball.x, ball.y, 25, "orange");

    ball.x = ball.x + 150;
    ball.y = 120;
    circle(ball.x, ball.y, 25, "red");
}

ball.x is "the x inside ball". It is an int variable in every way: it can be read, passed to a function, assigned to and updated. ball.x = ball.x + 150; is the read, work out, store of module 2, done to one member. The other member is not touched.

Two things do not work. x on its own means nothing in main: there is no variable of that name, only a member of some Spot, and the compiler has to be told which one. And Spot.x is wrong, because Spot is a type and holds no values. The dot always follows a variable.

A whole struct can be copied with =, and the copy is independent of the original.

#include <iostream>

struct Spot {
    int x;
    int y;
};

int main() {
    Spot a{3, 4};
    Spot b = a;
    b.x = 50;
    std::cout << a.x << " " << a.y << "\n";
    std::cout << b.x << " " << b.y << "\n";
    a = b;
    std::cout << a.x << " " << a.y << "\n";
}
3 4
50 4
50 4

Spot b = a; copies every member of a into the new variable b. Changing b.x afterwards leaves a alone, as it would for two ints. The assignment a = b; copies every member again, in the other direction.

Giving every member a value

Module 2 warned about int n;. A variable given no value holds whatever was in memory, and reading it is a mistake. The same is true of Spot s;. It has two members and neither has been given a value. This course never writes that line.

There are three ways to make sure that every member has a value.

#include <iostream>
#include <string>

struct Spot {
    int x;
    int y;
};

struct Player {
    std::string name = "nobody";
    int lives = 3;
    int score = 0;
};

int main() {
    Spot origin{};
    std::cout << origin.x << " " << origin.y << "\n";

    Player guest{};
    Player ada{"Ada"};
    Player max{"Max", 5, 120};
    std::cout << guest.name << " " << guest.lives << " " << guest.score << "\n";
    std::cout << ada.name << " " << ada.lives << " " << ada.score << "\n";
    std::cout << max.name << " " << max.lives << " " << max.score << "\n";

    guest.name = "Lin";
    guest.score = 40;
    std::cout << guest.name << " " << guest.lives << " " << guest.score << "\n";
}
0 0
nobody 3 0
Ada 3 0
Max 5 120
Lin 3 40

Empty braces. Spot origin{}; gives every member the zero of its type: 0 for a number, false for a bool, the empty text for a string. The two characters {} are the difference between a Spot that is safe to read and one that is not.

Default member values. Player gives each member a value in the definition itself: int lives = 3;. This is the value a member gets whenever the braces say nothing about it. Player guest{}; therefore starts as nobody, with 3 lives and a score of 0.

Values in the braces. They fill the members from the first one, in the order of the definition, and names play no part in it. Player ada{"Ada"} gives a value to name only. lives and score keep their defaults. Player max{"Max", 5, 120} gives all three. You cannot skip a member: there is no way to write "the score is 120, and leave the rest". The braces can only stop early.

A value of the wrong type, or more values than there are members, is a compile error. Player p{5}; does not compile, because the first member is a string and 5 is not text.

After that, any member can be changed with an assignment, as the last lines do.

Make a habit of the braces. Write Spot s{}; even when the next lines set both members. Then no member is ever without a value.

Passing a struct to a function

A struct is passed to a function in the same three ways as any other value. Module 10 showed them for an int and a vector.

#include <iostream>

struct Spot {
    int x;
    int y;
};

void print_spot(const Spot& spot) {
    std::cout << "(" << spot.x << ", " << spot.y << ")\n";
}

void move_right(Spot& spot, int by) {
    spot.x = spot.x + by;
}

Spot below(Spot spot, int by) {
    spot.y = spot.y + by;
    return spot;
}

int main() {
    Spot lamp{40, 60};
    print_spot(lamp);

    move_right(lamp, 100);
    print_spot(lamp);

    Spot shadow = below(lamp, 200);
    print_spot(shadow);
    print_spot(lamp);

    print_spot(Spot{7, 9});
}
(40, 60)
(140, 60)
(140, 260)
(140, 60)
(7, 9)
Parameter What the function gets Use it when
Spot spot a copy of the caller's struct, every member the function needs a copy of its own to change
Spot& spot the caller's own variable under another name the function is there to change the caller's struct
const Spot& spot the caller's variable, to look at only the function only reads it. This is the usual choice

move_right takes a Spot&, so spot.x = ... inside it changes lamp in main. The second line of output shows it.

below takes a copy. It changes the copy's y and returns the copy. A function returns one value, and that value can be a whole struct. main stores it in shadow. The fourth line of output shows that lamp was not changed by the call.

print_spot takes a const Spot&. Nothing is copied, and the compiler rejects any statement in its body that would change a member.

The last call passes Spot{7, 9}. A type's name followed by braces makes a value of that type without giving it a name, in the way 7 is an int with no name. Such a value can be given to a Spot or a const Spot& parameter. It cannot be given to a Spot& parameter, because there is no variable for the function to change.

Many of them

A vector holds many values of one type, and that type can be a struct. This is how a program keeps a list of things that each have several properties.

#include <iostream>
#include <string>
#include <vector>

struct Parcel {
    std::string to;
    int grams = 0;
};

int main() {
    std::vector<Parcel> van = {{"Lisbon", 800}, {"Oslo", 2300}};
    van.push_back(Parcel{"Turin", 450});
    Parcel late{"Cork", 1200};
    van.push_back(late);

    for (Parcel& parcel : van) {
        parcel.grams += 50;
    }

    int total = 0;
    for (const Parcel& parcel : van) {
        std::cout << parcel.to << ": " << parcel.grams << "\n";
        total += parcel.grams;
    }
    std::cout << "Parcels: " << van.size() << "\n";
    std::cout << "First: " << van[0].to << "\n";
    std::cout << "Total: " << total << "\n";
}
Lisbon: 850
Oslo: 2350
Turin: 500
Cork: 1250
Parcels: 4
First: Lisbon
Total: 4950

The type of van is std::vector<Parcel>: a vector whose elements are Parcels. In the list it starts with, each inner pair of braces is one Parcel. push_back adds a whole Parcel at the end, either a value made on the spot or a copy of a variable.

Both loops are the loops of module 10. for (Parcel& parcel : van) makes parcel another name for each element in turn, so adding 50 grams of packaging changes the elements. With Parcel parcel in that place the loop would change copies and the vector would stay as it was. for (const Parcel& parcel : van) looks at each element without copying it.

van[0].to is read from left to right. van[0] is the first element, which is a Parcel. The dot then takes its member to.

The other way to store this data is two vectors, one of names and one of weights, where position 2 of each belongs to the same parcel. That falls apart as soon as one of them is sorted, or gets an element the other does not. In a vector of structs a parcel's name and weight stay together because they are one element.

A type with a few named values

Some values are one of a few possibilities. The weather in a simple game is sunny, cloudy or rainy. You could store that in an int and agree that 0 means sunny. Then nothing stops the variable from holding 7, and a reader meeting if (weather == 2) has to remember what 2 stood for. You could store it in a string, and then "suny" compiles and is wrong.

An enum class is a type whose values are exactly the names you list.

#include <iostream>
#include <string>

enum class Weather { Sunny, Cloudy, Rainy };

std::string name_of(Weather weather) {
    switch (weather) {
        case Weather::Sunny:
            return "sunny";
        case Weather::Cloudy:
            return "cloudy";
        case Weather::Rainy:
            return "rainy";
    }
    return "unknown";
}

int main() {
    Weather today = Weather::Cloudy;
    Weather tomorrow = Weather::Rainy;
    std::cout << "Today: " << name_of(today) << "\n";
    if (tomorrow == Weather::Rainy) {
        std::cout << "Take an umbrella tomorrow\n";
    }
    today = tomorrow;
    if (today != Weather::Cloudy) {
        std::cout << "Now it is " << name_of(today) << "\n";
    }
}
Today: cloudy
Take an umbrella tomorrow
Now it is rainy

enum class Weather { Sunny, Cloudy, Rainy }; defines the type Weather and its three values. Like a struct definition, it goes above the functions that use it and ends with a semicolon.

A value is written with the type's name in front: Weather::Sunny. These are the same two colons as in std::cout, and they mean "the Sunny that belongs to Weather". Sunny on its own is a name the compiler does not know.

A Weather is not a number and not text.

  • Weather w = 1; does not compile, and neither does Weather w = "Sunny";. The only values are the three that are listed, so a Weather variable can never hold nonsense.
  • Two values can be compared with == and !=. A Weather can be assigned, passed to a function, returned, and stored in a vector or a struct.
  • std::cout << today; does not compile. The names exist for the compiler, and the running program has no text for them.

That is why the program has the function name_of. It takes a Weather and returns the text to show. Its body is a switch with one case for every value. Each case returns, which leaves the function at once, so no break is needed.

The return "unknown"; after the switch is never reached today. It is there so that the function still returns a string if someone later adds a value to Weather and forgets to add its case.

Structs inside structs

A member can have any type, and that includes a struct you defined yourself, a vector, and an enum class.

#include <iostream>
#include <string>
#include <vector>

struct Spot {
    int x;
    int y;
};

struct Disc {
    Spot centre;
    int radius = 10;
};

struct Playlist {
    std::string title;
    std::vector<int> seconds;
};

int main() {
    Disc coin{{200, 150}, 40};
    coin.centre.x = coin.centre.x + 20;
    std::cout << coin.centre.x << " " << coin.centre.y << " " << coin.radius << "\n";

    Playlist morning{"Morning", {185, 240}};
    morning.seconds.push_back(210);
    std::cout << morning.title << ": " << morning.seconds.size() << " songs\n";
    std::cout << "Second song: " << morning.seconds[1] << " seconds\n";
}
220 150 40
Morning: 3 songs
Second song: 240 seconds

A Disc has a Spot for its centre. Spot has to be defined above Disc, because the compiler reads from the top and must know what a Spot is when it meets the member.

In Disc coin{{200, 150}, 40}; the outer braces are the Disc and the inner braces are its first member, the Spot.

Read a chain of dots from left to right, and ask at each step what type you have.

Expression Its type
coin Disc
coin.centre Spot
coin.centre.x int
morning.seconds std::vector<int>
morning.seconds[1] int

morning.seconds is a vector like any other. Everything a vector can do is written after it: push_back, size(), an index in square brackets.

With members like these, one struct can hold everything a program has to remember. Everything about a quiz, for instance, can be one struct: a vector of answers given so far, the number of the current question, and whether time has run out. Such a struct is called the program's state. A function that takes the state by reference can change any part of it, and the caller still passes one argument.

Are two structs equal?

You can copy a struct with =. You cannot compare two of them with ==.

#include <iostream>

struct Spot {
    int x;
    int y;
};

int main() {
    Spot a{3, 4};
    Spot b{3, 4};
    if (a == b) {
        std::cout << "the same\n";
    }
}

This does not compile. The message says, in one wording or another, that there is no == for two Spots.

The compiler does not guess what "equal" means for a type you made. For a Spot the meaning is clear enough. For a Player it is not: are two players equal when they have the same name, or only when the lives and the score agree as well? So you say what you mean, in a function.

#include <iostream>

struct Spot {
    int x;
    int y;
};

struct Disc {
    Spot centre;
    int radius = 10;
};

bool same_spot(const Spot& a, const Spot& b) {
    return a.x == b.x && a.y == b.y;
}

bool same_disc(const Disc& a, const Disc& b) {
    return same_spot(a.centre, b.centre) && a.radius == b.radius;
}

int main() {
    Spot a{3, 4};
    Spot b{3, 4};
    Spot c{3, 9};
    std::cout << same_spot(a, b) << "\n";
    std::cout << same_spot(a, c) << "\n";

    Disc coin{{200, 150}, 40};
    Disc button{{200, 150}, 12};
    std::cout << same_disc(coin, button) << "\n";
}
1
0
0

same_spot compares member with member. The members are ints, and == is defined for those. Two spots are the same only if every member agrees, so the comparisons are joined with &&. With || in its place, a and c would count as the same, because their x members agree.

Inside such a function, == can be used on any member whose type has it: numbers, bool, char, strings, and the values of an enum class. A member that is itself a struct is compared by calling its own function, as same_disc calls same_spot for the two centres. A member that is a vector is compared one element at a time. First the two sizes must be equal. Then a loop goes over the positions and returns false at the first one where the elements differ.

The same holds for < and for printing with <<: neither exists for a new type until somebody writes it. Part IV of the course shows how to give your own type an == of its own. Until then, a function with a clear name does the job.

Your turn

Now try it yourself

Predict what a program will do, run it, and step through it line by line. The checks tell you when your own code works.

Loading the lab…

In this lab

  1. A new type

    This program defines a struct named Spot above main, and main declares two variables of that type. It prints one line that never changes. The questions are about what the program holds while it runs.

  2. Move the spot

    This program makes a Spot and draws an orange circle there. Then it draws a blue circle at the same Spot, so the blue one covers the orange one.

    Between the two draws, change the members of spot so that the blue circle appears 100 to the right of the orange one and 50 lower.

    Change spot itself. Leave both circle statements as they are, and leave the two lines that set start_x and start_y: the checks start the spot in other places.

  3. Every member gets a value

    Label has a default value for each of its three members. main creates four labels in four different ways and prints the members of each.

    Decide all four lines before you run it.

  4. A copy, or the same one?

    Two functions each add 100 to the x of a Spot. The only difference between them is one character in the first line. A third function prints a Spot.

  5. A row of stars

    This program draws three stars. Each star is kept in a variable of its own, so every new star needs a new name and a new call.

    Put the stars in a vector, and work on all of them with loops.

    1. In main, replace the three variables with one std::vector<Star> named stars that holds the same three stars, in the same order.
    2. Add two more stars at the end with push_back: one with x 410, y 80 and size 12, and one with x 520, y 60 and size 18.
    3. Fill in move_down. It adds by to the y of every star in the caller's vector.
    4. In main, call move_down(stars, 40); and then draw every star with one loop that calls draw_star.

    The checks call move_down with a vector of their own.

  6. Only these states

    Light is an enum class with three values. The function advice has a switch over a Light.

  7. A struct inside a struct

    A Segment has two Spots. A Team has a name and a vector of scores. main makes one of each, changes them, and prints four values.

  8. Are they equal?

    main makes two Spots with the same members and compares them with ==.

    The first question is about this program. The others each show a version of a function same_spot, and only the last version is right.

  9. A box type

    A Box is a rectangle on the canvas. main is written and uses a type and three functions that do not exist yet. Write them above main.

    struct Box has four int members, in this order: x, y, width, height. (x, y) is the top-left corner. The checks create a Box with Box{x, y, width, height}, so the names and the order must be exactly these. With other names the checks cannot be built, and you will be told so.

    area takes a Box and returns its area as an int: width times height.

    contains takes a Box and then a Spot. It returns true when the spot is inside the box. The left and top edges belong to the box and the right and bottom edges do not: the spot is inside when its x is at least the box's x and less than x + width, and the same holds for y and the height.

    grow takes a Box and then an int named by. It returns nothing. It makes the caller's box larger by by on all four sides: the corner moves up and to the left by by, and the width and the height each grow by twice by. A negative by makes the box smaller in the same way.

    Choose the kind of each struct parameter yourself: by value, by reference or by const reference. A function that only looks at a Box should neither copy it nor be able to change it. The checks look at the first line of each function as well as at what it does.

    main prints:

    6000
    1
    0
    30 20 120 80
    
  10. A class list

    The input is a class list: on each line a name, which is one word, and a whole-number score from 0 to 100. Write a program that keeps the list in a vector of structs and reports on it.

    struct Student has two members, in this order: name, a std::string, and score, an int. The checks create one with Student{"Ana", 71}. With other member names, or the members in the other order, the checks cannot be built, and you will be told so.

    Student best(const std::vector<Student>& students) returns the student with the highest score. When several share the highest score, it returns the one that comes first in the vector. The vector is never empty.

    double average(const std::vector<Student>& students) returns the mean of the scores, with its fraction. The vector is never empty.

    Neither function prints. The checks call both with vectors of their own.

    main reads a name and a score until the input ends, adding each student to the vector. Then it prints three lines:

    Students: 5
    Best: Tomas (88)
    Average: 72.8
    

    The average is printed with one digit after the point. If the input holds no student at all, main prints only No students and calls neither function.

  11. A traffic light

    main is written. It draws three traffic lights in a row, each in the state that follows the one before. Write the type and the two functions it needs, above main.

    enum class Light has the values Red, Amber and Green. The checks use Light::Red, Light::Amber and Light::Green.

    Light next_light(Light light) returns the state that comes after light. This light goes from red to green, from green to amber, and from amber back to red. That is not the order in which the values are listed.

    void draw_light(Light light, int x) draws one traffic light whose left edge is at x, in this order:

    1. the housing: a rectangle in "black", 80 wide and 240 high, with its top-left corner at (x, 80);
    2. three lamps, each a circle of radius 30 with its centre at x + 40. The top lamp is at y 130, the middle one at y 200 and the bottom one at y 270.

    Exactly one lamp is lit. For Light::Red it is the top lamp, in "red". For Light::Amber it is the middle lamp, in "orange". For Light::Green it is the bottom lamp, in "green". The two lamps that are not lit are "gray".

    The checks call both functions themselves.

  12. The board

    Tic-Tac-Toe is played on nine cells. A program that plays it has to remember two things: what is in each cell, and whose turn it is. Write the types that hold this state and three functions that work on it. Module 15 builds the whole game on them, so the names matter.

    enum class Mark has the values Empty, X and O, in that order. (O is the capital letter, not the digit.)

    struct Board has two members, in this order: cells, a std::vector<Mark>, and turn, a Mark. The cells are numbered 0 to 8, row by row: 0, 1, 2 are the top row and 6, 7, 8 the bottom row. turn is the mark that will be placed next. The checks create a board with Board{cells, turn}. With other names, or the members in the other order, the checks cannot be built, and you will be told so.

    Board new_board() returns a board with nine cells, all Mark::Empty, and Mark::X to move.

    bool place(Board& board, int position) tries to put the mark whose turn it is into the cell at position.

    • If position is not from 0 to 8, or that cell is not empty, it changes nothing and returns false.
    • Otherwise it puts board.turn into the cell, passes the turn to the other mark, and returns true.

    bool same_board(const Board& a, const Board& b) returns true when the two boards have the same mark in every cell and the same turn. Both boards always have nine cells.

    None of the functions prints. main prints 1, 0, 1, 0, 1, one on each line.

  13. Distance and midpoint

    A place on a map has two coordinates with fractions. Write a type for it and two functions, above main.

    struct Place has two double members, in this order: x and y. The checks create one with Place{1.5, 2.0}.

    double distance(const Place& a, const Place& b) returns the straight-line distance between the two places. Work out the difference of the two x values and the difference of the two y values. Multiply each difference by itself, add the two results, and take the square root of the sum.

    One library function is new here: std::sqrt(value), from the header <cmath>, returns the square root of a double. std::sqrt(25.0) is 5.

    Place midpoint(const Place& a, const Place& b) returns a new Place halfway between the two: its x is the mean of the two x values, and its y the mean of the two y values.

    Neither function prints, and neither changes its arguments. main prints 5, then 1.5 2.

  14. Sort the class

    main reads a class list into a vector of Students, sorts it, and prints it. Write the function that sorts, above main.

    void sort_by_score(std::vector<Student>& students) puts the caller's vector in order, with the highest score first. Students with the same score go in alphabetical order of their names, so Kofi 88 comes before Tomas 88. Names start with a capital letter and no two students have the same name.

    Use the selection sort of module 11, with one change: what decides whether one element comes before another is now a question about two members. A function bool comes_before(const Student& a, const Student& b) that answers it keeps the sort itself short. When two elements are exchanged, exchange the whole students, so that a name and its score stay together.

    The checks call sort_by_score with vectors of their own, among them an empty one.

  15. Seasons

    main reads month numbers until the input ends and prints the season of each. Write the type and the two functions it uses, above main.

    enum class Season has the values Winter, Spring, Summer and Autumn.

    Season season_of(int month) returns the season of a month number from 1 to 12, for the northern half of the world: December, January and February (12, 1 and 2) are winter, 3 to 5 are spring, 6 to 8 are summer, and 9 to 11 are autumn. The caller promises that month is from 1 to 12.

    std::string name_of(Season season) returns the name of the season as text, with a capital letter: "Winter", "Spring", "Summer" or "Autumn".

    Neither function prints. For the input 1 4 7 10 12, main prints:

    1: Winter
    4: Spring
    7: Summer
    10: Autumn
    12: Winter
    
  16. A time of day

    A time of day on a 24-hour clock is two numbers. Write a type for it and three functions, above main.

    struct Time has two int members, in this order: hours, from 0 to 23, and minutes, from 0 to 59. The checks create one with Time{9, 45}.

    Time add_minutes(const Time& time, int n) returns a new Time that is n minutes after time, where n is 0 or more. It wraps past midnight: 20 minutes after 23:50 is 0:10. n can be more than a day. The argument is not changed.

    bool earlier(const Time& a, const Time& b) returns true when a comes before b on the same day. A time is not earlier than itself.

    bool same_time(const Time& a, const Time& b) returns true when the two are the same time.

    None of them prints. main prints 11 15, then 1, then 0.

  17. Count the marks

    This uses the Mark and Board types of the lab. The definitions are given. Write two functions that answer questions about a board, above main.

    int count_marks(const Board& board, Mark mark) returns how many cells of the board hold mark. mark can be any of the three values, Mark::Empty included.

    bool is_full(const Board& board) returns true when no cell is empty.

    Neither function prints or changes the board. main prints 3, 2, 4 and 0, one on each line.

  18. Model something you know

    Choose something you know well that a program could keep track of: a card game, a week's timetable, or the stock of a small shop. Describe it with types of your own.

    1. Decide what one item is: a card, a lesson, a product. Define a struct for it.
    2. Find every property that is one of a few fixed possibilities, such as a card's suit, a weekday or a product's category. Give each an enum class, and write a name_of function for it.
    3. Write a function that prints one item on one line. It takes the item by const reference.
    4. In main, put at least five items in a vector and print them all with a loop.

    Nothing checks this one. When you have finished, go through the list below.