In the previous section, we introduced the std::pair
type, which allows us to create containers for storing two pieces of data, of any type.
We provide the two data types we will need to store within chevrons, <
and >
:
std::pair<int, float> MyPair;
We can then access the values stored in those slots using class variables called first
and second
:
#include <utility>
#include <iostream>
int main() {
std::pair<int, float> MyPair{42, 9.8f};
std::cout << "First " << MyPair.first
<< "\nSecond " << MyPair.second;
}
First: 42
Second: 9.8
std::pair
is an example of a template and, in this lesson, we’ll see how we can create our own templates to give similar flexibility.
So far, we've been working under the restrictions that functions must be declared before they are used. For example, we've been unable to create code like shown below:
int main() {
// Add is not defined yet
Add(1, 2);
}
int Add(int x, int y) {
return x + y;
}
Error: 'Add': identifier not found
Because the compiler reads our files top-to-bottom, when it reaches line 3, it encounters a call to a function that it’s not aware of.
The easy solution we've been using has been to move Add
above main
. However, we may not want to do this for stylistic reasons.
Worse, not all problems in this category can be solved by just moving functions around.
%
)In this short lesson, we'll explore how the modulus operator helps in obtaining the remainder after division. This has many applications and is particularly useful when we’re solving problems using loops.
We can imagine that the answer to a division question like might be , with left over. This is an example of modular arithmetic.
When performing division using modular arithmetic, we get two results:
FizzBuzz is a word game used to teach young school children about division. The students sit in a circle and take turns counting, one number each.
Any number that is divisible by 3 is replaced with the word "Fizz"; numbers divisible by 5 are replaced with "Buzz", and numbers divisible by 15 are replaced with "FizzBuzz".
The sequence looks like this:
1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz,
11, Fizz, 13, 14, FizzBuzz, 16, 17...
This game, and variants of it, gained popularity within the programming community.
This is because implementing a program that can "solve" FizzBuzz requires a solid foundation in programming. It uses variables, operators, loops, conditional statements, and functions.
It is a useful milestone to ensure we understand all these basic principles. For this reason, candidates applying to programming jobs are sometimes asked to implement FizzBuzz in their interviews.
In the previous lesson, we introduced the concept of ticking and tick functions, which allow our game objects to update on every iteration of our application loop.
However, we should note that when we’re implementing logic in a Tick()
function, we don’t know how quickly our objects will tick. That is, we do not know how much time has passed since the previous invocation of that object’s Tick()
 function.
// Goblin.h
#pragma once
#include "GameObject.h"
class Goblin : public GameObject {
public:
int xPosition;
void Tick() override {
// This object moves by one pixel per
// invocation of Tick(), but how frequently
// is Tick() invoked?
xPosition += 1;
}
};
As we add more complexity to our tick functions or more ticking objects, our game will need to perform more work on each iteration of our application loop. As such, it will iterate less frequently, meaning Tick()
is invoked less frequently, meaning our object will move slower.
On the other hand, if we perform optimizations or our user has a more powerful computer, our loop can iterate faster, causing our objects to move faster.
To address this inconsistency, we typically want to define properties like movement speed in terms of real-world units of time, such as seconds or milliseconds. Let’s learn how to do this.
Previously, we introduced the SDL_GetTicks64()
function, which returns the number of milliseconds that have passed since SDL was initialized.
However, in some situations, milliseconds are not granular enough. We need to use timers that use smaller units, such as microseconds and nanoseconds.
Timers that use these smaller units are typically called high-resolution timers. How they work and how we access them is something that varies from platform to platform, but SDL provides some utilities that can help us.
At this point, we’re familiar with the many ways we store and transfer objects around our program. We can pass them as arguments to functions, return them from functions, and store them as variables.
// Passing an int to a function
SetNumber(42);
// Returning an int from a function
int GetNumber() {
return 42;
}
// Storing an int to a member variable
SomeObject.SomeMember = 42;
What might be less familiar is that we can do all of these things with functions, too:
void SomeFunction() {
// ...
];
// Passing a function to a function
SetFunction(MyFunction);
// Returning a function from a function
auto GetFunction() {
return MyFunction;
}
// Storing a function as a member variable
SomeObject.SomeFunction = MyFunction;
This capability gives us a huge amount of flexibility in how we design our program and when used well, it helps us keep our code simple even as our behaviors get more and more complex.
A language that allows functions to be treated like any other data type is said to support first-class functions. C++ supports this concept in a few different ways. In the rest of this chapter, we’ll focus on one of them - function pointers.
Previously, we’ve seen how we can create pointers to free functions:
#include <functional>
void FreeFunction() {/*...*/}
int main() {
std::function FunctionPtr{FreeFunction};
}
In large projects, it’s more common that we’ll be working with member functions - that is, functions that are defined as part of a class
or struct
:
class SomeClass {
void MemberFunction() {/*...*/}
};
In this lesson, we’ll learn how to work with pointers to these types of functions, and manage the additional complexity that is involved.
Tick()
functions to update game objects independently of eventsIn our previous projects, we’ve updated the state of our objects based on events detected in our application loop:
// Application Loop
while (shouldContinue) {
// Handle Events
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) {
shouldContinue = false;
}
GameObject.HandleEvent(Event);
}
// Render Objects
GameWindow.Render();
GameObject.Render(GameWindow.GetSurface());
// Update Frame
GameWindow.Update();
}
However, in more complex projects, most objects may need to update their state consistently, even when they’re not receiving any events. For example, characters not controlled by our player may need to continue to act and move, and animation, and visual effects should continue to update.
In this lesson, we’ll introduce how to write data from our program to external sources. We’ll focus on writing to files for now, but the techniques we cover lay the foundations for working with other data streams, such as network traffic.
As we might expect, SDL’s mechanism for writing data shares much in common with their API for reading data. We’ll rely on SDL_RWops
objects, and the functions we use will be familiar to what we learned in the previous lesson.
Like before, we’ll start with a basic main
function that initializes SDL, and calls Write()
and Read()
functions within an included File
 namespace.
// main.cpp
#include <SDL.h>
#include "File.h"
int main(int argc, char** argv) {
SDL_Init(0);
File::Write("output.txt");
File::Read("output.txt");
return 0;
}
Our Read()
function logs out the file’s contents, using techniques we covered in the previous lesson. In this lesson, we’ll work on the Write()
function, which is currently empty:
// File.h
#pragma once
#include <iostream>
#include <SDL.h>
namespace File {
void Read(const std::string& Path) {
char* Content{static_cast<char*>(
SDL_LoadFile(Path.c_str(), nullptr)
)};
if (Content) {
std::cout << "Content: " << Content;
} else {
std::cout << "Error loading file: "
<< SDL_GetError();
}
SDL_free(Content);
}
void Write(const std::string& Path) {
// ...
}
}
Running our program, we should see an error output from our Read()
function, as it’s trying to read a file that we haven’t created yet:
Error loading file: Parameter 'src' is invalid