Skip to main content

Structures

In this lesson, you learn how to create data types made up of many smaller elements - what in C++ we call structures.

Motivation

Goblin enemy presentation
Goblin image by LuizMelo

If, for example, when creating a game 🎮, we want to include opponents in our program, we will usually have to write down some information about each of them.

Consider what data about the enemies in the game can be useful? It can be, for example:

  • name 👾
  • health 💚
  • strength 💪

etc.

Using the knowledge we have acquired so far, if we wanted to write a program that stores this information, we could do it like this:

#include <string>

int main() {
std::string enemy_name = "Goblin";
float enemy_health = 50;
float enemy_strength = 12;
// ...
}

When we want to have more opponents in the game, we will encounter a problem, or rather inconvenience:

If we use multiple arrays for this purpose:

std::vector< std::string >	enemy_names;
std::vector< float > enemy_health;
std::vector< float > enemy_strength;

then each opponent will be described under the same index in these tables:

  • enemy_names[ index ] describes the name
  • enemy_health[ index ] describes life points
  • enemy_strength[ index ] describes the points of strength
important

This method involves "spreading" information about a single opponent across multiple tables.

Adding one enemy to a set in such a program would look like this:

enemy_names.push_back("Goblin");
enemy_health.push_back(50);
enemy_strength.push_back(10);

The more information we want to store about our opponents, the more bothersome it will be. Fortunately, structures come to our aid here.

Creating structures

Let's recall what data we need to store:

  • name 👾
  • life 💚
  • strength 💪

We are about to add a structure that allows us to create an object that contains these 3 things.

#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// We leave it empty for now
}

The above code introduces a new structure - Enemy.

Remember

A structure is a description, a pattern, a recipe for how to create an object (in this case, an enemy).

To create a structure, we write its name after the struct keyword, then we put its contents between the curly braces { and }.

The contents can be, for example, variables.

Semicolon

Note the obligatory semicolon after the curly braces to close the structure definition:

struct Enemy
{
std::string name;
float health;
float strength;
};

Objects

Here's how to create an object that uses the Enemy structure (which is treated as a formula for creating an object):

// prism-push-types:Enemy
int main()
{
Enemy boss;
}

Thus, we contained all these 3 fields (name, health and strength) inside one variable boss.

Naming

From now on we will say that it boss is an object of the type Enemy. This means that it was created using Enemy structure as a formula.

Accessing fields

As mentioned above, it boss contains 3 things (fields), i.e. it consists of three variables. To get to a specific member of this object, we need to use the following notation:

Set the name of the boss to 'Ogre'
boss.name = "Ogre";

We use a dot . to refer to the object's field. In the same way, we can e.g. modify the enemy's strength:

Modifying the fields of an object
boss.strength	= 50; // I set bosses strength to 50

// Boss enables "fury" skill - strength increases
// Health decreases by a half
boss.strength += 25;
boss.health *= 0.5f;

... or display information about it:

Using object fields
#include <iostream>
#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// I create boss object
Enemy boss;
// I assign values to its fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

std::cout << boss.name << " has "
<< boss.health << " hp and "
<< boss.strength << " strength."
<< std::endl;
}

Passing objects to functions

Nothing prevents you from creating a function that takes an object of a certain structure as a parameter. A good example will be just displaying information about an enemy:

A function that displays information about an opponent
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}
Order

print_enemy_info requires a type Enemy to exist before the function itself is defined. This means we need to put the function underneath the structure creation (see example below).

Using the above information, we will create a "game" that will have two opponents:

  • ordinary enemy 👹:
    Goblin warrior, 60 health, 14 strength

  • boss 💀:
    Ogre, 250 health, 50 strength

A fragment of a game code with an Ogre and a Goblin
#include <iostream>
#include <string>

// Creating struct
struct Enemy
{
std::string name;
float health;
float strength;
};

// Function that display information about an enemy
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// The main function
int main()
{
// I create boss and goblin objects
Enemy boss;
Enemy goblin;

// I setup goblin object fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I setup boss object fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

// and print information about them
print_enemy_info(goblin);
print_enemy_info(boss);
}

Objects inside arrays

We can put objects inside arrays just like normal variables:

Vector of enemies
std::vector< Enemy > enemies;

Below is an example of how to add to such an array:

Adding objects inside vector
// ...

int main()
{
std::vector< Enemy > enemies;

// (optional)
// A code block to limit the scope
// of local variables inside
{
// I create goblin variable 👉 locally 👈
Enemy goblin;
// I setup the fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I add the object to the vector
enemies.push_back( goblin );
}
// 👈 from this moment, goblin exists ONLY inside the vector

// Print every enemy in the vector
for (Enemy enemy : enemies)
print_enemy_info(enemy);
}
Example

After reading this lesson, review this sample program: 👾 Battle Arena and its overview. There you will see how arrays and structures are used in practice.

Default field values

We can give the structure elements default values, so we won't have to fill them in every time.

A good example of using the default value is a variable that stores the total amount of damage an opponent has dealt. To start with, for each enemy, this value will have to be equal 0.

Field values

If you leave the structure field with no default value , e.g .:

struct Car
{
int number_of_wheels;
};

it doesn't mean number_of_wheels will be set to 0 at the moment of object creation, you have to do it manually!

To assign a default value to a structure field, we use the usual initialization, known for creating variables:

Default value for 'total_damage' ⚔
// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;

float total_damage = 0;
};

Now when we create some enemy:

Enemy snake; // snake as an example

then the value of its field total_damage

snake.total_damage

will be set to 0 automatically.

You can find out about it, e.g. by printing it:

int main() {
Enemy snake;
snake.name = "Snake";
// 🟡 Note, I'm not setting "total_damage" manually

std::cout << snake.name
<< " has dealt "
<< snake.total_damage
<< " total damage";
}
Result
Snake has dealt 0 total damage

Potential errors

important

This section requires improvement. You can help by editing this doc page.

Here is a list of common errors related to this lesson:

No semicolon after definition

Just for the record.

Invalid order

Make sure the structure is defined before using it for the first time.

Wrong code example:

🔴 Incorrect order
// ❌ Error: usage of type "Enemy" before its definition
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;
};
tip

This problem can be solved in a different, more convenient way than moving the function print_enemy_info below the structure definition, with the so-called forward declaration. We will mention it later in the course.

Modifying inside structure definition

Variables cannot be modified inside a structure definition. It is only possible to assign an initial value:

🔴 Attempt to modify field in a wrong place
struct Enemy
{
std::string name;
float health;
float strength;

int total_damage = 0; // OK ✅

// ❌ Error: Attempt to modify field in a wrong place
health = 250;
};

Structures

In this lesson, you learn how to create data types made up of many smaller elements - what in C++ we call structures.

Motivation

Goblin enemy presentation
Goblin image by LuizMelo

If, for example, when creating a game 🎮, we want to include opponents in our program, we will usually have to write down some information about each of them.

Consider what data about the enemies in the game can be useful? It can be, for example:

  • name 👾
  • health 💚
  • strength 💪

etc.

Using the knowledge we have acquired so far, if we wanted to write a program that stores this information, we could do it like this:

#include <string>

int main() {
std::string enemy_name = "Goblin";
float enemy_health = 50;
float enemy_strength = 12;
// ...
}

When we want to have more opponents in the game, we will encounter a problem, or rather inconvenience:

If we use multiple arrays for this purpose:

std::vector< std::string >	enemy_names;
std::vector< float > enemy_health;
std::vector< float > enemy_strength;

then each opponent will be described under the same index in these tables:

  • enemy_names[ index ] describes the name
  • enemy_health[ index ] describes life points
  • enemy_strength[ index ] describes the points of strength
important

This method involves "spreading" information about a single opponent across multiple tables.

Adding one enemy to a set in such a program would look like this:

enemy_names.push_back("Goblin");
enemy_health.push_back(50);
enemy_strength.push_back(10);

The more information we want to store about our opponents, the more bothersome it will be. Fortunately, structures come to our aid here.

Creating structures

Let's recall what data we need to store:

  • name 👾
  • life 💚
  • strength 💪

We are about to add a structure that allows us to create an object that contains these 3 things.

#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// We leave it empty for now
}

The above code introduces a new structure - Enemy.

Remember

A structure is a description, a pattern, a recipe for how to create an object (in this case, an enemy).

To create a structure, we write its name after the struct keyword, then we put its contents between the curly braces { and }.

The contents can be, for example, variables.

Semicolon

Note the obligatory semicolon after the curly braces to close the structure definition:

struct Enemy
{
std::string name;
float health;
float strength;
};

Objects

Here's how to create an object that uses the Enemy structure (which is treated as a formula for creating an object):

// prism-push-types:Enemy
int main()
{
Enemy boss;
}

Thus, we contained all these 3 fields (name, health and strength) inside one variable boss.

Naming

From now on we will say that it boss is an object of the type Enemy. This means that it was created using Enemy structure as a formula.

Accessing fields

As mentioned above, it boss contains 3 things (fields), i.e. it consists of three variables. To get to a specific member of this object, we need to use the following notation:

Set the name of the boss to 'Ogre'
boss.name = "Ogre";

We use a dot . to refer to the object's field. In the same way, we can e.g. modify the enemy's strength:

Modifying the fields of an object
boss.strength	= 50; // I set bosses strength to 50

// Boss enables "fury" skill - strength increases
// Health decreases by a half
boss.strength += 25;
boss.health *= 0.5f;

... or display information about it:

Using object fields
#include <iostream>
#include <string>

struct Enemy
{
std::string name;
float health;
float strength;
};

int main()
{
// I create boss object
Enemy boss;
// I assign values to its fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

std::cout << boss.name << " has "
<< boss.health << " hp and "
<< boss.strength << " strength."
<< std::endl;
}

Passing objects to functions

Nothing prevents you from creating a function that takes an object of a certain structure as a parameter. A good example will be just displaying information about an enemy:

A function that displays information about an opponent
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}
Order

print_enemy_info requires a type Enemy to exist before the function itself is defined. This means we need to put the function underneath the structure creation (see example below).

Using the above information, we will create a "game" that will have two opponents:

  • ordinary enemy 👹:
    Goblin warrior, 60 health, 14 strength

  • boss 💀:
    Ogre, 250 health, 50 strength

A fragment of a game code with an Ogre and a Goblin
#include <iostream>
#include <string>

// Creating struct
struct Enemy
{
std::string name;
float health;
float strength;
};

// Function that display information about an enemy
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// The main function
int main()
{
// I create boss and goblin objects
Enemy boss;
Enemy goblin;

// I setup goblin object fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I setup boss object fields
boss.name = "Ogre";
boss.health = 250;
boss.strength = 50;

// and print information about them
print_enemy_info(goblin);
print_enemy_info(boss);
}

Objects inside arrays

We can put objects inside arrays just like normal variables:

Vector of enemies
std::vector< Enemy > enemies;

Below is an example of how to add to such an array:

Adding objects inside vector
// ...

int main()
{
std::vector< Enemy > enemies;

// (optional)
// A code block to limit the scope
// of local variables inside
{
// I create goblin variable 👉 locally 👈
Enemy goblin;
// I setup the fields
goblin.name = "Goblin warrior";
goblin.health = 60;
goblin.strength = 14;

// I add the object to the vector
enemies.push_back( goblin );
}
// 👈 from this moment, goblin exists ONLY inside the vector

// Print every enemy in the vector
for (Enemy enemy : enemies)
print_enemy_info(enemy);
}
Example

After reading this lesson, review this sample program: 👾 Battle Arena and its overview. There you will see how arrays and structures are used in practice.

Default field values

We can give the structure elements default values, so we won't have to fill them in every time.

A good example of using the default value is a variable that stores the total amount of damage an opponent has dealt. To start with, for each enemy, this value will have to be equal 0.

Field values

If you leave the structure field with no default value , e.g .:

struct Car
{
int number_of_wheels;
};

it doesn't mean number_of_wheels will be set to 0 at the moment of object creation, you have to do it manually!

To assign a default value to a structure field, we use the usual initialization, known for creating variables:

Default value for 'total_damage' ⚔
// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;

float total_damage = 0;
};

Now when we create some enemy:

Enemy snake; // snake as an example

then the value of its field total_damage

snake.total_damage

will be set to 0 automatically.

You can find out about it, e.g. by printing it:

int main() {
Enemy snake;
snake.name = "Snake";
// 🟡 Note, I'm not setting "total_damage" manually

std::cout << snake.name
<< " has dealt "
<< snake.total_damage
<< " total damage";
}
Result
Snake has dealt 0 total damage

Potential errors

important

This section requires improvement. You can help by editing this doc page.

Here is a list of common errors related to this lesson:

No semicolon after definition

Just for the record.

Invalid order

Make sure the structure is defined before using it for the first time.

Wrong code example:

🔴 Incorrect order
// ❌ Error: usage of type "Enemy" before its definition
void print_enemy_info(Enemy enemy)
{
std::cout << enemy.name << " has "
<< enemy.health << " hp and "
<< enemy.strength << " strength."
<< std::endl;
}

// Creating the structure
struct Enemy
{
std::string name;
float health;
float strength;
};
tip

This problem can be solved in a different, more convenient way than moving the function print_enemy_info below the structure definition, with the so-called forward declaration. We will mention it later in the course.

Modifying inside structure definition

Variables cannot be modified inside a structure definition. It is only possible to assign an initial value:

🔴 Attempt to modify field in a wrong place
struct Enemy
{
std::string name;
float health;
float strength;

int total_damage = 0; // OK ✅

// ❌ Error: Attempt to modify field in a wrong place
health = 250;
};