C++ Variables
A variable in C++ is a named piece of memory that holds a value your program can read and change while it runs. Every variable has a type, which tells the compiler how many bytes to reserve and what kind of data those bytes represent, and a name that you choose so you can refer to that memory later in your code. Variables are the most basic building block of any C++ program — without them you couldn’t store user input, keep a running total, or remember anything from one line of code to the next.
This lesson covers how variables actually work under the hood, the full syntax for declaring and initializing them, several worked examples, and the mistakes that trip up almost every beginner.
Overview: How Variables Work
C++ is a statically typed language, which means every variable’s type is fixed at compile time and cannot change afterward. When you write a line like int age = 25;, several things happen before your program ever runs a single instruction:
- The compiler sees the type
intand knows it needs to reserve a fixed amount of memory for it — typically 4 bytes on most modern systems. - The compiler reserves that space, usually on the stack (a region of memory used for local variables and function calls), and associates the name
agewith that memory address for the rest of its scope. - The value
25is converted into its binary representation and written into those 4 bytes. - From that point on, whenever you use
age, the compiler generates code that reads or writes the bytes at that specific memory address — you never work with the raw address yourself, but it is there.
This is very different from a sheet of paper or a whiteboard: the memory a variable occupies is not created out of nowhere and doesn’t automatically clean itself. A local variable’s memory is reserved when its declaration is reached and automatically released when the surrounding block { } ends — this is called its scope, and it matters a great deal for how variables behave (see the scope example below).
Because the type is fixed, the compiler can catch many mistakes before your program ever runs. If you try to store text in an int, or use a variable that was never declared, the compiler rejects the program outright rather than letting it fail unpredictably later.
Syntax
The general form for creating a variable is:
type name; // declaration only (no value yet)
type name = value; // declaration + initialization
type name1 = v1, name2 = v2; // multiple variables of the same type
- type — the kind of data the variable holds (
int,double,char,bool,std::string, etc.) - name — an identifier you choose. It must start with a letter or underscore, contain only letters, digits, and underscores, and cannot be a reserved keyword (like
intorreturn). - value — the initial data stored in the variable. C++ also supports brace initialization,
int age{25};, which is stricter about preventing accidental data loss.
Some of the most common built-in types and their typical sizes on a modern 64-bit system:
| Type | Holds | Typical Size | Example |
|---|---|---|---|
int |
Whole numbers | 4 bytes | int count = 10; |
double |
Decimal numbers | 8 bytes | double price = 9.99; |
char |
A single character | 1 byte | char grade = 'A'; |
bool |
true or false |
1 byte | bool isReady = true; |
std::string |
Text | varies | string name = "Sam"; |
Note that sizes are not guaranteed by the C++ standard — they depend on the compiler and platform — but the values above are the norm on virtually every desktop system today.
Examples
Example 1: Declaring and printing different variable types
#include <iostream>
#include <string>
using namespace std;
int main() {
int age = 25;
double price = 19.99;
char grade = 'A';
bool isPassing = true;
string name = "Maria";
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
cout << "Price: $" << price << endl;
cout << "Grade: " << grade << endl;
cout << "Passing: " << isPassing << endl;
return 0;
}
Output:
Name: Maria
Age: 25
Price: $19.99
Grade: A
Passing: 1
Each variable reserves its own memory according to its type. Notice that bool prints as 1 for true (and would print 0 for false) because cout shows the underlying integer representation unless told otherwise with boolalpha.
Example 2: Changing values and measuring size
#include <iostream>
using namespace std;
int main() {
int score = 50;
cout << "Initial score: " << score << endl;
score = score + 10;
cout << "After bonus: " << score << endl;
int bonus = 5, penalty = 2;
score = score + bonus - penalty;
cout << "Final score: " << score << endl;
cout << "Size of int: " << sizeof(score) << " bytes" << endl;
return 0;
}
Output:
Initial score: 50
After bonus: 60
Final score: 63
Size of int: 4 bytes
This example shows that a variable’s value can change even though its type and memory location cannot. Each assignment simply overwrites the bits stored at score‘s address. sizeof confirms exactly how many bytes the compiler reserved for it.
Example 3: Scope and shadowing with a constant
#include <iostream>
using namespace std;
int main() {
const double TAX_RATE = 0.08;
double price = 25.00;
{
double price = 40.00;
double total = price + (price * TAX_RATE);
cout << "Inner price: " << price << endl;
cout << "Inner total: " << total << endl;
}
double total = price + (price * TAX_RATE);
cout << "Outer price: " << price << endl;
cout << "Outer total: " << total << endl;
return 0;
}
Output:
Inner price: 40
Inner total: 43.2
Outer price: 25
Outer total: 27
The inner { } block declares its own price, which shadows (temporarily hides) the outer one for as long as the block lasts. Once the block ends, its price and total are destroyed and the outer price is unaffected. TAX_RATE is declared const, meaning any attempt to reassign it would fail to compile — a useful safeguard for values that should never change.
Under the Hood: Step by Step
Walking through int age = 25; inside a function:
- 1. Reservation — the compiler allocates 4 bytes on the stack frame for the current function call.
- 2. Naming — the identifier
ageis recorded in the compiler’s symbol table, mapped to that memory location, valid only within its enclosing scope. - 3. Initialization — the literal
25is converted to its 32-bit binary form and copied into those 4 bytes. - 4. Use — every later reference to
agecompiles into a read or write of that exact memory address. - 5. Destruction — when the enclosing
{ }block ends, the stack space is reclaimed andageceases to exist; using its name afterward would be a compile error.
This lifecycle is why a variable declared inside an if block or a loop body cannot be used outside of it, and why two different blocks can safely reuse the same variable name without conflict.
Common Mistakes
Mistake 1: Using a variable before initializing it
int total;
cout << total << endl; // undefined: prints whatever garbage bits were already in memory
Declaring a variable without an initializer reserves memory but does not clear it. Reading it before assigning a value produces undefined behavior — it might print 0, might print a large random number, and might even differ between runs. Always initialize:
int total = 0;
cout << total << endl; // reliably prints 0
Mistake 2: Redeclaring a variable in the same scope
int count = 5;
int count = 10; // compile error: redefinition of 'count'
Once a name is declared in a scope, it cannot be declared again in that same scope — the compiler will refuse to build the program. If you want to change the value, just assign to the existing variable instead of declaring it again:
int count = 5;
count = 10; // fine: this is assignment, not a new declaration
Mistake 3: Silent truncation from mismatched types
int wholeNumber = 9.8; // compiles, but silently truncates to 9, discarding the .8
Assigning a double to an int with the classic = syntax is legal but throws away the fractional part without warning. Prefer brace initialization, which catches this at compile time:
int wholeNumber{9.8}; // compile error (or warning): narrowing conversion, forces you to notice
Best Practices
- Always initialize a variable at the point you declare it — never leave it holding garbage.
- Use descriptive names (
studentCount, notsc) so the code reads clearly without extra comments. - Declare variables as close as possible to where they’re first used, not all at the top of a function.
- Use
constfor any value that should never change after initialization, such as tax rates or array sizes. - Prefer brace initialization (
int x{5};) over the older=form when you want the compiler to catch accidental narrowing conversions. - Give each variable the narrowest scope it needs — don’t declare it in an outer block if it’s only used inside an inner one.
- Avoid reusing a single variable for two unrelated purposes; declare a new, clearly named variable instead.
Practice Exercises
- Exercise 1: Declare variables for a product’s
name(string),quantity(int), andunitPrice(double). Compute and print the total cost (quantity * unitPrice). - Exercise 2: Write a program that declares an
intvariable set to7, then declares a secondintvariable inside a nested{ }block with the same name but a different value. Print the variable inside and outside the block, and explain in a comment why the two outputs differ. - Exercise 3: Declare a
const doublenamedPIset to3.14159and a variableradius. Compute the area of a circle (PI * radius * radius) and print it. Then try to reassignPIto a new value and observe the compiler error.
Summary
- A variable is a named region of memory whose type determines its size and what kind of data it can hold.
- Declaring a variable reserves memory; initializing it gives that memory a starting value; assignment later overwrites the value without changing the type.
- Local variables live on the stack and are automatically destroyed when their enclosing scope ends.
- Inner blocks can shadow (temporarily hide) an outer variable of the same name.
- Using an uninitialized variable, redeclaring a name in the same scope, and silently truncating types via mismatched assignment are the most common beginner mistakes.
- Use
constfor values that must not change, and favor brace initialization to catch narrowing errors at compile time.
