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 int and 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 age with that memory address for the rest of its scope.
  • The value 25 is 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 int or return).
  • 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 age is recorded in the compiler’s symbol table, mapped to that memory location, valid only within its enclosing scope.
  • 3. Initialization — the literal 25 is converted to its 32-bit binary form and copied into those 4 bytes.
  • 4. Use — every later reference to age compiles into a read or write of that exact memory address.
  • 5. Destruction — when the enclosing { } block ends, the stack space is reclaimed and age ceases 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, not sc) 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 const for 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), and unitPrice (double). Compute and print the total cost (quantity * unitPrice).
  • Exercise 2: Write a program that declares an int variable set to 7, then declares a second int variable 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 double named PI set to 3.14159 and a variable radius. Compute the area of a circle (PI * radius * radius) and print it. Then try to reassign PI to 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 const for values that must not change, and favor brace initialization to catch narrowing errors at compile time.