Methods
This lesson will show you how to make use of functions inside a structure creating so-called methods.
Motivation
In programming, objects from real life are often mirrored, for example when creating a racing game 🏎 we will want to have vehicles that are defined by their
- traits, such as:
- brand
- model 🚘
- max. speed 🚀
- behaviour, e.g:
- acceleration 💨
- braking 🛑
This way of describing objects, separating features and behaviors, is very common. In the lesson on structures we learned how to contain different information about objects within a single type that we created ourselves. By doing so, we described its traits. Now we're going to move on to methods that will allow us to "teach" the object to perform a specific task - that is, to define its behaviour.
Introduction
For the purposes of this lesson, let's create a structure that contains the following features of a car:
struct Car
{
// car information
std::string brand;
std::string model;
int year_of_production;
// movement
float top_speed = 200; // (km/h)
float acceleration = 50; // (km/h per second)
float speed = 0; // current one (km/h)
};
Inside the main
function, let's create an object of this structure:
// prism-push-types:Car
int main()
{
Car car;
car.brand = "Ford";
car.model = "Focus";
car.year_of_production = 2010;
}
Now we will move on to how to make it (car
) work.
Creating and using methods
Inside the Car
structure, at its end, let's put a function, named accelerate
,
which will increase the speed
by the acceleration value of the acceleration
:
struct Car
{
// ...
float acceleration = 50;
float speed = 0;
// ...
// increases the speed
void accelerate()
{
speed += acceleration;
}
};
Methods are functions that are members of a structure (or a class - more on that later in the course). They operate on other structure members and/or provide some functionality for instances of it.
Now we can call accelerate
on an object, like this:
// prism-push-types:Car
// ...
int main()
{
Car car;
// initial values...
// calling for the first time
car.accelerate();
std::cout << "Current speed: " << car.speed << " km/h\n";
// calling for the second time
car.accelerate();
std::cout << "Current speed: " << car.speed << " km/h\n";
}
Current speed: 50 km/h
Current speed: 100 km/h
We use the dot operator (car.accelerate
) to access the method we want to call and then inside the parentheses()
we pass any required arguments as necessary.
object.method_name(arguments);
function_name(arguments);
Differences between functions and methods
Most methods could be rewritten as regular free functions (outside of any structure).
The accelerate
method we created above could be rewritten as a function:
// prism-push-types:Car
void accelerate(Car& car)
{
car.speed += car.acceleration;
}
and called like this:
// prism-push-types:Car
Car car;
// initial values...
// calling for the first time
accelerate(car);
std::cout << "Current speed: " << car.speed << " km/h\n";
// calling for the second time
accelerate(car);
std::cout << "Current speed: " << car.speed << " km/h\n";
See full code
// prism-push-types:Car
#include <iostream>
struct Car
{
// car information
std::string brand;
std::string model;
int year_of_production;
// movement
float top_speed = 200; // (km/h)
float acceleration = 50; // (km/h per second)
float speed = 0; // current one (km/h)
};
void accelerate(Car& car)
{
car.speed += car.acceleration;
}
int main()
{
Car car;
car.brand = "Ford";
car.model = "Mustang";
car.year_of_production = 1969;
car.top_speed = 250;
car.acceleration = 60;
// initial values...
// calling for the first time
accelerate(car);
std::cout << "Current speed: " << car.speed << " km/h\n";
// calling for the second time
accelerate(car);
std::cout << "Current speed: " << car.speed << " km/h\n";
}
As you can see, the main difference is that we need to pass the object as an argument when calling a function, but we do not need to do this for a method.
Definition order
Inside a structure, methods need not be defined before declaring a variable or another method, which also belongs to this structure:
- ✔ Good
- ✔ Good (order kept)
- ❌ Bad
Note the order in which the methods are defined: limit_speed
was used in accelerate
,
even though its definition is below. Similarly, the speed
and acceleration
fields
have been used before their declaration, because it is in the code under this usage.
This is allowed within the structure.
struct Car
{
// function that increases the speed
void accelerate()
{
speed += acceleration;
limit_speed();
}
void limit_speed() {
if (speed > top_speed)
speed = top_speed;
}
// class data members
float top_speed = 200;
float acceleration = 50;
float speed = 0;
// the rest...
};
The order of functions and variables is set according to their usage - also correct.
struct Car
{
// class data members
float top_speed = 200;
float acceleration = 50;
float speed = 0;
// the rest...
void limit_speed() {
if (speed > top_speed)
speed = top_speed;
}
// function that increases the speed
void accelerate()
{
speed += acceleration;
limit_speed();
}
};
Outside of a structure, such reordering is not allowed without using a separate declaration:
void printHelloWorld()
{
// ❌ Error, using `world` function before definition
std::cout << "Hello, " << world();
}
// Return the "World!" string
std::string world()
{
return "World!";
}
Declaration and definition
As with functions, we can separate the method declaration and definition. This way we are able to move their definitions outside of the structure body:
struct Car
{
// class data members
float top_speed = 200;
float acceleration = 50;
float speed = 0;
// the rest...
// Methods declarations:
void limit_speed();
void accelerate();
};
void Car::limit_speed()
{
if (speed > top_speed)
speed = top_speed;
}
void Car::accelerate()
{
speed += acceleration;
limit_speed();
}
Note that in this case, we precede the method name with the structure name Car
,
and a double colon ::
, the so-called scope resolution operator.
// prism-push-types:type
type StructureName::method_name(parameters)
{
// ...
}
One of the advantages of this notation is the ability to separate the interface of the structure from its implementation. This way, once you've implemented methods, each time you look at the structure, you will only see the set of variable and method names that you will use, without getting distracted by the implementation details.
This notation is also crucial when splitting the code into multiple files - we'll tell about it further on in the course.
Examples
This section requires improvement. You can help by editing this doc page.
Potential errors
This section requires improvement. You can help by editing this doc page.