Everything a computer does eventually boils down to moving numbers and text around in memory. Variables are how we, as programmers, give those numbers and text names we can actually remember and work with. Without variables, we would have no way to store a player's score, track the position of a spaceship, or remember whether the player has collected the key. In other words, without variables, we have no games.
In this chapter, we will cover the most important C++ types that our games will rely on for the rest of the book. We will also meet the basic tools for doing math on those types, and a handful of modern C++ features that make life easier. None of it is difficult in isolation, but taken together, this is the foundation everything else is built on.
In this chapter, we will:
- Understand what a variable is and how the computer stores data
- Learn the most common C++ numeric types:
int,float,double,long, andlong long - Perform mathematical operations on variables
- Use
boolvariables to represent true/false states - Work with single characters using
char - Store and manipulate text with
std::string - Use constants to make code safer and more readable
- Understand type casting and when to use it
- Meet the
autokeyword and see when it helps - Get a first look at scope
- Learn about the special type called Struct
- Try an optional AI exercise to explore variables further
Let's start at the beginning.
What Is a Variable?
A variable is a named container for a piece of data. That's it. You tell the computer "I want a chunk of memory, I want to call it score, and I want it to hold a whole number." From that moment on, whenever you use the word score in your code, the computer knows exactly which piece of memory you mean and what kind of data lives there.
A useful way to picture this is a row of labeled boxes on a shelf. Each box holds exactly one thing, and each box has its name written on the front. When you want to know what's in score, you look at the score box. When you want to change it, you reach in and swap what's inside for something new.
There are two things we always have to tell the computer when we create a variable: its type and its name. The type tells the computer what kind of thing will live in the box — a whole number, a decimal, a letter, a word, a true/false value, and so on. The name is what we will use to refer to it in our code.
There are a few rules about variable names in C++. They can contain letters, numbers, and underscores. They cannot start with a number. They cannot contain spaces. And they cannot be the same as a C++ keyword like int or while. Beyond the rules, there are strong conventions. C++ programmers typically use camelCase for variable names — lowercase for the first word, uppercase for the start of each subsequent word, like playerScore, enemyHealth, or isGameOver. We will stick to this convention throughout the book.
A well-named variable is worth its weight in gold. x might be fine for a throwaway example, but playerScore tells you, and anyone reading your code later, exactly what the value represents. Good names are one of the cheapest and most effective things you can do to make your code easier to work with.
Now let's meet the types, starting with the one you will use the most.
int
The int type stores a whole number — positive, negative, or zero. No decimal point. No fractions. Just whole numbers like 0, 7, -42, or 1000.
In games, int is everywhere. Scores, lives, ammo counts, the number of enemies on screen, which level the player is on — all of these are whole numbers, and all of them are naturally int variables.
Here is how we declare and initialize an int:
int playerScore = 0;
int lives = 3;
int enemiesRemaining = 20;
In the preceding code, we create three int variables. The first, playerScore, starts at 0. The second, lives, starts at 3. The third, enemiesRemaining, starts at 20. The = symbol is called the assignment operator, and it puts the value on the right into the variable on the left.
You can change the value of an int at any time after it is created, simply by assigning a new value to it:
playerScore = 100; // The player scored some points
lives = lives - 1; // The player lost a life
enemiesRemaining = 19; // One enemy defeated
Notice that on the second line, we use lives on both sides of the =. The computer reads the right-hand side first, figures out the answer (in this case 3 - 1 = 2), and then stores that answer back into lives. This pattern — calculate something using a variable, then store the result back into it — is one you will see constantly in games.
An int can hold positive and negative numbers, but it does have a limit. On most modern systems an int can hold values from about negative two billion to about positive two billion. For almost every game variable you will ever need, that is comfortably enough room. If you ever need more, C++ has larger types, which we will meet in a moment.
float and double
Not every number we want to work with is a whole number. The position of a spaceship on screen might be 342.7 pixels across. A timer might need to count down in fractions of a second. For numbers with a decimal point, we use float or double.
Both float and double store floating-point numbers — so called because the decimal point can "float" to wherever it is needed. The difference between them is precision. A double uses twice as much memory as a float and can therefore represent numbers with roughly twice as many significant digits.
Here is how we declare them:
float shipX = 320.0f;
float shipY = 240.0f;
double preciseTimer = 1.234567890;
In the preceding code, we create two float variables to hold the spaceship's position on screen, and one double to hold a timer value. Notice the f at the end of 320.0f and 240.0f. That little f tells the compiler "this is a float, not a double." Without it, C++ assumes any number with a decimal point is a double, and you will sometimes see a warning from the compiler when you try to store a double in a float. Getting into the habit of adding the f now will save you from confusing warnings later.
So which one should you use? For game programming, float is the usual choice. Graphics libraries, physics engines, and SDL itself mostly use float for positions, speeds, and sizes. double is reserved for situations where precision genuinely matters — scientific calculations, very long timers, or anywhere small rounding errors would accumulate into big problems. For your first games, expect to see float almost everywhere.
There is one thing about floating-point numbers that catches every programmer out at some point. They are not always perfectly precise. A float set to 0.1 might actually be stored as 0.09999999. Most of the time this doesn't matter — you won't notice a spaceship being one ten-millionth of a pixel off. But if you ever compare two float values with == and get unexpected results, floating-point imprecision is almost certainly why. We will see how to handle that properly later in the book.
long and long long
Occasionally, a regular int isn't big enough. If you're writing a game that tracks total lifetime score across every player who has ever played, or you need to represent nanoseconds since the start of the universe, the two-billion-ish limit of int starts to feel cramped.
For those rare cases, C++ provides long and long long. A long is at least as big as an int and sometimes bigger. A long long is guaranteed to be at least 64 bits, which means it can store numbers up to about nine quintillion. If you need more than that, you are probably writing the wrong kind of program.
long bigNumber = 3000000000L;
long long reallyBigNumber = 9000000000000000000LL;
The L and LL suffixes work like the f on float — they tell the compiler exactly which type you mean.
You will rarely need long or long long in the games we'll build in this book, but it's worth knowing they exist. If you ever see one in someone else's code, you'll know what it is.
Mathematical Operators
Storing numbers is useful. Doing math with them is where the fun starts. C++ gives us all the standard arithmetic operations you learned in school, plus a few handy shortcuts.
Arithmetic Operators
The basic operators are the ones you would expect:
int a = 10;
int b = 3;
int sum = a + b; // 13
int difference = a - b; // 7
int product = a * b; // 30
int quotient = a / b; // 3 (not 3.333!)
int remainder = a % b; // 1
In the preceding code, the first four operators are probably familiar. Add, subtract, multiply, divide. The last one, %, is called the modulo operator, and it gives you the remainder after division. 10 % 3 is 1 because 10 divided by 3 is 3 with 1 left over. Modulo turns out to be surprisingly useful — we will use it to wrap coordinates around the screen, to check if a number is even or odd, and for all sorts of other tricks.
Look at the quotient result for a moment. 10 / 3 gave us 3, not 3.333. That's because when you divide two int values, C++ gives you an int result. It throws away the fractional part. This is called integer division, and it catches beginners out constantly. If you want a decimal answer, at least one of the numbers involved needs to be a float or double:
float exactQuotient = 10.0f / 3.0f; // 3.3333333
This is the first of many small C++ gotchas. It won't be the last. The compiler is literal-minded and does exactly what you tell it to — so tell it what you mean.
Compound Assignment Operators
We often want to change a variable based on its current value. We saw this earlier with lives = lives - 1. That pattern is so common that C++ has shortcuts for it:
int score = 100;
score += 10; // Same as: score = score + 10; (now 110)
score -= 5; // Same as: score = score - 5; (now 105)
score *= 2; // Same as: score = score * 2; (now 210)
score /= 3; // Same as: score = score / 3; (now 70)
score %= 8; // Same as: score = score % 8; (now 6)
These are called compound assignment operators. They do exactly the same thing as the longer version. They just take fewer keystrokes and read a little more naturally. Use them where they help and skip them where they don't — whichever is clearer.
Increment and Decrement
Adding or subtracting 1 is so common in games that it gets its own pair of operators:
int lives = 3;
lives++; // Same as: lives += 1; (now 4)
lives--; // Same as: lives -= 1; (now 3)
The ++ and -- operators are called increment and decrement. You will see them constantly in loops, counters, and game logic. They're shorter than += 1 and have become part of the standard C++ rhythm.
There are actually two flavors of each — lives++ and ++lives — and they behave subtly differently when used inside a larger expression. For now, whichever side you put them on, they add or subtract 1 from the variable, and that's all you need to know. We'll come back to the subtlety when it matters.
bool
Not every question in a game has a numeric answer. Sometimes you just want to know yes or no. Is the game paused? Has the player collected the key? Did the bullet hit the enemy?
For these situations we use bool, which stands for Boolean — a type named after the nineteenth-century mathematician George Boole. A bool variable can hold only two possible values: true or false.
bool isGameOver = false;
bool hasKey = false;
bool isJumping = true;
In the preceding code, we create three bool variables to represent three different yes/no states in a game. bool variables are usually named in a way that reads naturally as a question: isAlive, hasKey, canJump, isPaused. When you see if (hasKey) in your code later, it reads like English.
bool is wonderfully simple, but don't let the simplicity fool you. An enormous amount of game logic is just bool variables being flipped on and off in response to things that happen. Collision detection, game state, input handling — all of it is built on Booleans underneath.
We will really put bool through its paces in Chapter 4, when we learn about controlling the flow of code with if statements and loops.
char
The char type stores a single character, such as a letter, a digit, or a punctuation mark. The name is short for "character." Whenever you see a char value written in code, it is surrounded by single quotes:
char grade = 'A';
char initial = 'J';
char symbol = '#';
In the preceding code, we store three single characters in three char variables. Notice the single quotes around 'A', 'J', and '#'. Single quotes are specifically for one character. Double quotes, which we'll see in a moment, are for text strings.
char is less common in everyday game code than int or float, but it shows up in places you might not expect. Keyboard input is often processed one character at a time. Text files are read character by character. If you ever work with low-level text manipulation, char is where you start.
As an interesting aside, a char is actually just a small integer under the hood. Each character has a number assigned to it by a system called ASCII — 'A' is 65, 'B' is 66, 'a' is 97, and so on. That means you can do math on characters. 'A' + 1 gives you 'B'. It feels strange at first, but it can be surprisingly handy for things like cycling through the alphabet or converting between uppercase and lowercase.
std::string
A char holds one character. But most of the time we want to work with whole words, sentences, names, or messages. For that, C++ gives us std::string.
std::string is a little different from the types we have seen so far. int, float, bool, and char are all built directly into the C++ language. std::string is part of the standard library — a huge collection of extra tools that comes bundled with C++. To use it, we need to include its header file at the top of our code:
#include <string>
Once that's done, we can create and use strings just as easily as any other variable.
Declaring and Initializing Strings
Here is how we create a few strings:
std::string playerName = "Alex";
std::string welcomeMessage = "Welcome to the game!";
std::string emptyString = "";
In the preceding code, notice the double quotes around the text values. Double quotes are how C++ recognizes a string. A char uses single quotes, a string uses double quotes — that distinction matters and the compiler is strict about it.
The std:: part in front of string might look a bit odd. It stands for standard, and it is telling the compiler "the string I mean is the one from the standard library." There are ways to drop the std:: and just write string directly, but we'll keep it in for now. It's a little more typing, but it's absolutely clear where the string type comes from, and clarity wins.
Common String Operations
Strings can do a lot of things that plain old numbers can't. Here are a few of the most common operations:
std::string firstName = "Alex";
std::string lastName = "Morgan";
// Join two strings together with +
std::string fullName = firstName + " " + lastName; // "Alex Morgan"
// Find out how long a string is
int nameLength = fullName.length(); // 11
// Compare two strings with ==
bool sameName = (firstName == "Alex"); // true
In the preceding code, we see three useful things. The + operator joins — or concatenates — two strings together. The .length() function tells us how many characters are in the string. And the == operator compares two strings and gives us a bool telling us whether they're the same.
That .length() syntax, with the dot in the middle, is your first glimpse of something bigger. std::string is an object, which is a concept we'll cover properly in Chapter 16 when we get to object-oriented programming. For now, just know that some types come with built-in actions you can perform on them, and you access those actions with a dot. fullName.length() is asking the fullName string to tell you its length.
Displaying Strings with SDL
The whole point of strings in a game is usually to show them on screen. SDL 3 has a text rendering system that takes a string and draws it as pixels the player can actually read. We won't cover the full details here — that's a forward reference to the project chapters, where we'll use SDL to display scores, menus, and messages — but know that anything you can store in a std::string, you can eventually put on screen in your game.
Constants
Sometimes you have a value that should never, ever change while your program is running. The width of the game window. The maximum number of lives. The value of pi. For these, C++ gives us constants.
A constant is a variable whose value is locked in the moment it is created. Try to change it later, and the compiler refuses to compile your code. That refusal is the whole point — it stops you from accidentally changing something that was never meant to change.
const
The most common way to make a constant is with the const keyword:
const int MAX_LIVES = 3;
const int SCREEN_WIDTH = 1920;
const int SCREEN_HEIGHT = 1080;
const float GRAVITY = 9.81f;
In the preceding code, we create four constants. Once declared, none of them can ever be assigned a new value. Try to write MAX_LIVES = 5; later in your code and the compiler will stop you in your tracks.
Notice the naming convention. Constants are traditionally written in UPPER_SNAKE_CASE — all uppercase, with underscores between words. This isn't required by the language, but it's such a strong convention that experienced C++ programmers expect it. When you see SCREEN_WIDTH in code, you immediately know it's a constant, without even looking at the declaration.
constexpr
C++ also has a newer, more modern keyword called constexpr. It is similar to const but stronger — it guarantees that the value is known at compile time, not just that it can't change at runtime. For most purposes in this book, you can use either one and they will behave the way you expect. constexpr is more powerful in advanced situations we will not need for a while.
constexpr int MAX_ENEMIES = 100;
If you're not sure which to use, stick with const for now. We'll come back to constexpr when it matters.
Why Magic Numbers Are Bad
Here's a small piece of code that works perfectly but is a nightmare to maintain:
if (playerLives > 3) {
playerLives = 3; // Cap lives at the maximum
}
if (enemyX > 1920) {
enemyX = 0; // Wrap around the screen
}
What do 3 and 1920 mean? You can probably guess from the surrounding code, but imagine coming back to this in six months. Or imagine the screen resolution changes and 1920 needs to become 1280. Now you have to hunt through your entire codebase to find every 1920 and figure out which ones refer to the screen width and which are just coincidences.
These unexplained numbers scattered through code are called magic numbers, and they are bad news. Compare the preceding code to this version:
const int MAX_LIVES = 3;
const int SCREEN_WIDTH = 1920;
if (playerLives > MAX_LIVES) {
playerLives = MAX_LIVES;
}
if (enemyX > SCREEN_WIDTH) {
enemyX = 0;
}
Now the code reads almost like English, and changing the screen width is a one-line fix. The constants do two jobs at once: they protect values that shouldn't change, and they give those values meaningful names. Use them.
Type Casting
Every now and then, we need to take a value of one type and treat it as a different type. We might have an int that we need to divide precisely, which means turning it into a float first. We might have a float position that we need to round down to the nearest pixel, which means turning it into an int. The process of converting between types is called type casting, or just casting.
Implicit Casting
C++ will sometimes cast values automatically, without you having to ask. This is called implicit casting:
int wholeNumber = 10;
float decimalNumber = wholeNumber; // Implicit cast from int to float
In the preceding code, the int value 10 is quietly converted into the float value 10.0f. The compiler doesn't warn you because no information is lost — a float can easily hold the value 10.
Going the other way is where it gets interesting:
float position = 342.7f;
int pixel = position; // Implicit cast from float to int — compiler warning!
The compiler will probably warn you about this one. When you convert a float to an int, the decimal part is thrown away — 342.7 becomes 342. That's a real loss of information, and the compiler wants to make sure you know.
Explicit Casting with static_cast
When you know what you're doing and want to make the cast explicit, use static_cast:
float position = 342.7f;
int pixel = static_cast<int>(position); // Explicitly asking for an int — no warning
In the preceding code, static_cast<int>(position) is a direct instruction to the compiler: "Take this value and treat it as an int, and yes, I know the fractional part will be lost." The warning goes away because you have clearly stated your intent.
static_cast is the modern, safe, explicit way to cast in C++. You will see it a lot. It's a little wordy, but the wordiness is deliberate — casting is important enough that it should stand out in the code, not hide.
Here's the classic integer-division fix we saw earlier, written properly:
int totalScore = 100;
int numberOfGames = 3;
float averageScore = static_cast<float>(totalScore) / numberOfGames; // 33.333
In the preceding code, we cast totalScore to a float before the division. Because one side of the division is now a float, C++ does floating-point division instead of integer division, and we get the precise answer 33.333 instead of 33.
The auto Keyword
Sometimes the type of a variable is obvious from the value on the right-hand side. In those cases, modern C++ lets us use the auto keyword and have the compiler figure out the type for us:
auto score = 100; // Compiler sees an int literal, makes this an int
auto shipX = 320.0f; // Compiler sees a float literal, makes this a float
auto playerName = std::string("Alex"); // Compiler makes this a std::string
In the preceding code, auto tells the compiler "look at the value I'm assigning and work out the type yourself." It's a small convenience, but it can save keystrokes and make code cleaner in situations where the type is long or obvious from context.
Use auto where it aids readability and skip it where it doesn't. If the type isn't clear from the value, a reader of your code will have to stop and work it out, which defeats the purpose. auto score = 100; is fine. auto thing = getComplicatedThing(); is probably not — the reader has no idea what thing actually is.
We will see auto really earn its keep later in the book when we start working with collections and iterators in Chapter 12.
Scope (First Look)
Here's a question that might already be lurking at the back of your mind. If you can declare variables all over your program, how does the compiler keep track of which ones exist where?
The answer is scope. Every variable in C++ has a scope — a region of code in which it exists and can be used. Outside of that region, the variable effectively doesn't exist.
Scope in C++ is defined by curly braces {}. A variable declared inside a pair of curly braces exists until the matching closing brace, and no further:
{
int localScore = 50;
// localScore can be used here
}
// localScore does not exist here
In the preceding code, localScore is created inside a pair of curly braces. The moment the code reaches the closing brace, localScore is gone. Try to use it after that point and the compiler will tell you the variable is undefined.
This is a very brief first look at scope. We'll come back to it properly in Chapter 8 when we talk about functions, because that's where scope really starts to matter. For now, the takeaway is simple: variables declared inside curly braces only live inside those braces.
C++ Structs
Sometimes a single piece of data isn't really a single thing. A player's position on screen is two values — an X and a Y. A color is three values — red, green, and blue. We could store each one in a separate variable, but they belong together, and treating them as a group makes our code easier to think about.
A struct (short for structure) lets us bundle several variables into one named unit. Here's a simple example:
struct Position {
float x;
float y;
};
In the preceding code, we define a new type called Position that contains two float values, x and y. The variables inside a struct are called members. Notice the semicolon after the closing brace — struct definitions need it, and forgetting it is one of the more common C++ syntax errors.
Once we've defined the struct, we can create variables of that type just like any built-in type:
Position playerPosition;
playerPosition.x = 320.0f;
playerPosition.y = 240.0f;
Position enemyPosition = { 100.0f, 50.0f };
In the preceding code, we create two Position variables. For playerPosition, we set each member individually using the dot operator — playerPosition.x means "the x member of playerPosition." For enemyPosition, we use the curly brace syntax to set both members at once when the variable is created.
Structs are wonderful for game programming. Instead of juggling separate playerX and playerY variables (and enemyX, enemyY, and bulletX, bulletY…) we can group them naturally:
struct Enemy {
float x;
float y;
int health;
bool isAlive;
};
Enemy goblin;
goblin.x = 200.0f;
goblin.y = 150.0f;
goblin.health = 100;
goblin.isAlive = true;
In the preceding code, all the data that describes a single enemy lives in one place. When we eventually have lots of enemies, we'll be able to manage them as a group of Enemy values rather than as a tangled mess of separate variables.
Structs are the simplest taste of a much bigger idea — bundling data and behavior together — that we'll explore properly in Chapter 16 when we cover object-oriented programming. For now, think of a struct as a custom container that holds related variables under one name.
AI Exercise (Optional)
If you'd like to explore variables a little further on your own with an AI assistant, here's an exercise to try. If you don't want to use AI at all, just skip this section — you won't miss any core content.
Open your AI chatbot of choice. Paste in the following prompt:
"I'm a complete beginner learning C++. Please explain the difference between
floatanddoublein more depth than a typical tutorial would. Cover: how much memory each uses, how many digits of precision each gives, and give me one example from game programming where using adoubleinstead of afloatwould actually matter. Don't use bullet points, keep it conversational, and assume I just learned what a variable is."
Notice that the preceding prompt does several things at once. It tells the AI who you are (a beginner), what you want (a deeper explanation), what to cover specifically (memory, precision, a real example), and what to avoid (bullet points, assumed knowledge). That level of detail is what separates a useful AI response from a generic one.
When you get the answer back, read it carefully. Does it actually answer all three of your specific questions? Does the game example make sense to you, or does it use terms you haven't learned yet? If it falls short on any of those fronts, don't be afraid to push back. "That example was too advanced — give me a simpler one" is a perfectly good follow-up prompt.
As we progress through the book, we'll use AI for increasingly ambitious things, but the principle stays the same. Prompt specifically. Read critically. Push back when needed.
Summary
We've just covered the basic building blocks that every C++ program is made of. You now know how to store whole numbers with int, decimals with float and double, yes/no values with bool, single characters with char, and text with std::string. You know how to do math on numbers, how to lock values in place with const, how to cast safely between types with static_cast, and how to let the compiler work out types for you with auto. You've also had a first glimpse of scope, which we'll come back to later.
This might not feel like much yet, because we haven't done anything visual with any of it. That changes in the next chapter, when we'll put these variables to work in our first real SDL project — a small game where the values you set directly control what happens on screen. Variables aren't just boxes for numbers. They're the levers and dials that drive everything your game does.