Ready to practice?
Sign up to access interactive coding exercises and track your progress.
const Variables
Declare variables that cannot be modified after initialization using const.
The problem with changeable values
Consider a game where the player starts with 3 lives. This value appears in multiple places: initializing the player, resetting after game over, displaying the UI. What happens when you decide players should start with 5 lives instead?
If you used the literal 3 everywhere, you now need to find and change every occurrence. Miss one, and you have a bug. Change the wrong 3 (maybe one that represented something else entirely), and you have a different bug.
Named constants solve this problem by giving meaningful names to fixed values. Change the constant's definition once, and the change propagates everywhere it's used.
Constant variables
A constant variable is a variable whose value cannot change after initialization. Despite the oxymoron (how can something be both constant and variable?), this is standard C++ terminology.
Declare a constant by adding the const keyword before or after the type:
const int startingLives{ 3 }; // const before type (conventional)
double const taxRate{ 0.0825 }; // const after type (valid but less common)
Most C++ developers place const before the type because it reads more naturally in English - we say "a constant integer" not "an integer constant."
Place `const` before the type for consistency with common conventions.
Constants must be initialized
Since you cannot assign to a constant after creation, you must provide a value at the point of definition:
int main()
{
const int maxPlayers; // error: no initializer
maxPlayers = 4; // error: cannot assign to const
const int maxPlayers{ 4 }; // correct: initialized at definition
return 0;
}
The initializer doesn't need to be a literal - it can come from any expression, including user input or other variables:
#include <iostream>
int main()
{
std::cout << "Enter difficulty (1-10): ";
int difficulty{};
std::cin >> difficulty;
const int savedDifficulty{ difficulty }; // captures current value
difficulty = 5; // ok: difficulty isn't const
savedDifficulty = 7; // error: savedDifficulty is const
return 0;
}
Once savedDifficulty captures the value from difficulty, it's locked. The original difficulty variable remains modifiable.
Why use constants?
Making variables constant provides three significant benefits:
Prevents accidental modification. The compiler catches any attempt to change a const variable, turning potential runtime bugs into compile-time errors.
const double speedOfLight{ 299792458.0 }; // meters per second
speedOfLight = 300000000.0; // error: caught at compile time
Enables compiler optimizations. When the compiler knows a value won't change, it can substitute the literal value directly, potentially eliminating memory access entirely. We explore this in the next lesson on compile-time optimization.
Simplifies reasoning about code. When debugging, you know a const variable's value is whatever it was initialized to. No need to trace through code looking for where it might have changed.
Every variable that can change is a potential source of bugs. Constants reduce the number of "moving parts" in your program.
Make variables const whenever their values shouldn't change after initialization.
Constant function parameters
Function parameters can also be const:
#include <iostream>
void printScore(const int score)
{
std::cout << "Score: " << score << '\n';
// score = 0; // would be an error
}
int main()
{
printScore(1500);
return 0;
}
However, for parameters passed by value (copies), there's little benefit to making them const. The function receives a copy that gets destroyed when the function returns - who cares if the function modifies its own copy?
Don't use const for by-value function parameters. The added clutter provides minimal benefit.
We'll revisit this advice when we cover references and pointers, where const becomes genuinely important.
Constant return values
You can also make return values const:
const int getMaxHealth()
{
return 100;
}
For fundamental types, this is pointless - the compiler ignores the const qualifier. The return value is a temporary that the caller can use however they want.
Don't use const when returning by value. It provides no benefit and can inhibit certain optimizations.
Naming conventions
Some developers use special naming for constants:
MAX_PLAYERS- all caps with underscores (common in C)kMaxPlayers- prefix 'k' with camel case (common in some C++ codebases)
Since const variables behave like regular variables (except for assignment), we recommend using the same naming convention as other variables: maxPlayers. The const keyword already communicates immutability.
Preprocessor macros: the old way
Before C++ had proper const support, programmers used preprocessor macros:
#define MAX_PLAYERS 4
int main()
{
int players[MAX_PLAYERS]; // preprocessor substitutes: int players[4];
return 0;
}
This approach has serious problems:
No scope boundaries. Once defined, a macro replaces text everywhere in the file:
#include <iostream>
void configureAudio()
{
#define volume 75 // defined here...
}
void adjustVolume(int volume) // ...breaks this parameter name!
{
std::cout << volume << '\n';
}
int main()
{
adjustVolume(50);
return 0;
}
The preprocessor replaces volume in the function parameter with 75, causing a cryptic compilation error.
Invisible to debuggers. Macros are replaced before compilation. The debugger sees 75, not volume, making debugging harder.
No type checking. Macros are text substitution, not typed values. The compiler can't catch type mismatches.
Const variables have none of these problems: they follow normal scoping rules, are visible to debuggers, and are fully type-checked.
Use const variables instead of preprocessor macros for named constants.
Sharing constants across files
Real programs often need the same constants in multiple files - mathematical values like pi, configuration values like maximum connections, or game constants like starting health.
Rather than defining these constants in every file that needs them, C++ provides mechanisms for defining them once and sharing them. We cover this in detail in a later lesson on sharing global constants.
Type qualifiers
The const keyword is a type qualifier - it modifies a type's behavior. When you write const int maxPlayers, the full type is const int, not just int.
C++ has only two type qualifiers: const and volatile. You'll encounter const constantly (pun intended), but volatile is rare - it tells the compiler that a value might change unexpectedly (hardware registers, multi-threaded code), disabling certain optimizations.
Terminology note: Technical documentation often refers to these as cv-qualifiers. A type without qualifiers is cv-unqualified (like int), while a type with qualifiers is cv-qualified (like const int).
Summary
- Constant variables have values that cannot change after initialization
- Declare constants with the const keyword before the type:
const int value{ 42 }; - Constants must be initialized at definition - you cannot assign to them later
- Constants can be initialized from any expression, including variables and user input
- Benefits: prevents accidental changes, enables optimizations, simplifies debugging
- Don't use const for by-value function parameters or return values
- Prefer const variables over preprocessor macros for named constants
- The type includes const:
const intis a different type thanint
Use const liberally. It makes your code safer, potentially faster, and easier to understand. When you see a const variable, you know its value - no need to trace through code looking for modifications.
const Variables - Quiz
Test your understanding of the lesson.
Practice Exercises
Constant Variables
Practice declaring and using const variables for values that shouldn't change.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!