C++ Primer, Chapter 2

Variables and Basic Types

Every C++ program starts here: how the machine remembers things, from raw bits in memory to the type system that keeps you safe.

Prerequisites: Any programming experience. That's it.
9
Chapters
5+
Simulations

Chapter 0: Why Types Matter

You write int x = 42; and the compiler allocates 4 bytes of memory, stores the binary pattern for 42, and remembers that those bytes should be interpreted as a signed integer. Change the type to double and the same number 42 gets stored as 8 bytes in IEEE 754 floating-point format — a completely different bit pattern.

The type of a variable determines three things: how many bytes it occupies, how those bits are interpreted, and what operations are legal. Get the type wrong, and you get silent data corruption. A char can only hold values up to 127 (or 255 unsigned). Shove 300 into it, and the compiler will silently truncate — no warning, no error, just a wrong answer.

The core insight: Types are not just labels. They are contracts between you and the machine about how to interpret raw memory. C++ gives you fine-grained control over these contracts — and that power comes with responsibility.
Bits in Memory

Type a number and see how it's stored as different types. Notice how the same value uses different bit patterns and different amounts of memory.

Value 42
Why does the type of a variable matter, beyond just its name?

Chapter 1: Primitive Types

C++ inherits a set of built-in types (also called primitive types or arithmetic types) from C. These are the atomic building blocks: integers, floating-point numbers, characters, and booleans. Every other type in C++ is ultimately built from these.

Integer Types

The integer family comes in several sizes. The language guarantees minimum widths but not exact ones, which is why sizeof(int) can vary between platforms. On most modern 64-bit systems:

TypeTypical SizeRange
char1 byte-128 to 127
short2 bytes-32,768 to 32,767
int4 bytes-2.1B to 2.1B
long8 bytes±9.2 × 1018
long long8 bytes±9.2 × 1018

Each integer type can be signed (default, holds negatives) or unsigned (non-negative only, doubles the positive range). An unsigned char holds 0–255 instead of -128–127.

Floating-Point Types

TypeSizePrecision
float4 bytes~7 decimal digits
double8 bytes~15 decimal digits
long double16 bytes~18 decimal digits
Lippman's advice: Use int for integer arithmetic, double for floating-point. Avoid unsigned unless you need bit manipulation. Avoid float — the precision savings rarely justify the bugs.
On a typical 64-bit system, how many bytes does an int occupy?

Chapter 2: Variables — Naming Memory

A variable provides a named piece of storage. When you write int counter = 0;, three things happen: the compiler allocates 4 bytes on the stack, writes zero into those bytes, and records that the name counter refers to that location.

Definition vs Declaration

A definition allocates storage: int x = 5;. A declaration merely says "this name exists somewhere": extern int x;. You can declare a variable many times, but define it only once. This distinction becomes critical in multi-file programs.

Initialization

C++ has four ways to initialize a variable, and they are not all equivalent:

c++
int a = 0;       // copy initialization
int b(0);        // direct initialization
int c{0};        // list (brace) initialization — C++11
int d = {0};     // copy list initialization — C++11

List initialization (with braces) is the safest: the compiler will refuse to narrow. Writing int x{3.14}; is a compile error because 3.14 would lose precision. The other forms silently truncate.

Default initialization: Variables defined inside a function with no initializer are uninitialized — their value is whatever garbage happens to be in memory. Variables defined outside any function are zero-initialized. Always initialize your variables.
Stack Variable Lifecycle

Click "Step" to execute each line. Watch variables appear and disappear on the stack.

Line 1
What is the value of an uninitialized local int variable?

Chapter 3: Const — Promises You Won't Change

The const qualifier is a promise to the compiler: "I will not modify this value after initialization." It sounds simple, but const interacts with references and pointers in subtle ways that trip up even experienced C++ programmers.

Top-Level vs Low-Level Const

This is the key distinction. Top-level const means the object itself can't be changed. Low-level const means the thing being pointed or referred to can't be changed through this handle.

c++
int i = 42;
const int ci = 42;         // top-level: ci itself is const
const int *p = &i;         // low-level: *p is const, p is not
int *const q = &i;         // top-level: q itself is const
const int *const r = &i;  // both levels

The rule of thumb: const before the * applies to what's pointed to (low-level). const after the * applies to the pointer itself (top-level).

constexpr

C++11 introduced constexpr to mark values that can be computed at compile time. A constexpr variable is implicitly const, but it also guarantees the value is known before the program runs.

c++
constexpr int size = 20;              // evaluated at compile time
constexpr int limit = size + 1;       // also compile time
int arr[limit];                        // OK: limit is a constant expression
Key insight: const says "I promise not to change this." constexpr says "this can be computed at compile time." Every constexpr is const, but not every const is constexpr.
In const int *p = &x;, what is const — the pointer or the data?

Chapter 4: References — Aliases for Objects

A reference is an alternative name for an existing object. When you write int &r = x;, r becomes another name for x. It's not a copy — changing r changes x. Think of it as a sticky note placed on a box: the note isn't a new box, it's just another label on the same one.

References must be initialized when they are defined, and once bound, they cannot be rebound to a different object. This makes them much simpler (and safer) than pointers.

c++
int x = 10;
int &ref = x;    // ref is an alias for x
ref = 20;         // changes x to 20
// int &bad;       // ERROR: references must be initialized
// int &bad = 42;  // ERROR: can't bind non-const ref to literal

Const References

A reference to const (often called a "const reference") can bind to a literal, an expression, or an object of a different type — things a plain reference cannot do:

c++
const int &r1 = 42;         // OK: binds to a literal
const int &r2 = x * 2;      // OK: binds to a temporary
const double &r3 = x;       // OK: binds to a temporary double
Why pass by reference? When you pass a large object to a function by value, C++ copies the entire thing. Passing by const& avoids the copy while promising not to modify the original. This is the most common parameter-passing idiom in C++.
Reference vs Copy

Watch how modifying a reference affects the original, while modifying a copy does not.

What happens when you modify a value through a reference?

Chapter 5: Pointers — Addresses as Values

A pointer holds the memory address of another object. Unlike a reference, a pointer is an object in its own right: it occupies memory (typically 8 bytes on a 64-bit system), it can be reassigned to point to different objects, and it can be null.

c++
int x = 42;
int *p = &x;      // p holds the address of x
*p = 99;           // dereference: changes x to 99
p = nullptr;       // p now points to nothing

The Dereference Operator

The * operator (dereference) follows the pointer to the object it points to. The & operator (address-of) gets the address of an object. They are inverses: *(&x) gives back x.

Null Pointers

A null pointer does not point to any object. In modern C++, always use nullptr (not NULL or 0). Dereferencing a null pointer is undefined behavior — the program may crash, produce garbage, or appear to work fine until the worst possible moment.

Pointers vs References: References are safer (can't be null, can't be rebound) but less flexible. Pointers are more powerful (can be null, can point to different objects) but more dangerous. When you can use a reference, prefer it. Use pointers when you need nullability or reassignment.
Pointer Diagram

Click operations to see how pointer operations change the memory layout.

What does *p = 99; do when p points to x?

Chapter 6: Memory Map Explorer

Now let's put it all together. Every C++ program has its memory divided into regions: the stack (local variables, function call frames), the heap (dynamically allocated memory), the data segment (globals and statics), and the code segment (the program instructions).

The simulation below lets you step through a small C++ program and see exactly where each variable lives, what it contains, and which pointers and references connect them.

Memory Map Simulator

Step through the code. The stack grows downward. Pointers are shown as arrows. Watch how const, references, and pointers interact in memory.

Ready
What you're seeing: Each colored box is a memory cell. The address is shown on the left. Variables are labeled on top. Arrows from pointers show what they point to. Gray cells are uninitialized garbage.

Chapter 7: Type Deduction — auto and decltype

C++11 introduced auto and decltype to let the compiler figure out types for you. This isn't laziness — it makes code more maintainable and prevents errors when types are complex.

auto

auto asks the compiler to deduce the type from the initializer:

c++
auto x = 42;         // int
auto pi = 3.14;      // double
auto s = "hello";    // const char*  (surprise!)
auto v = vec.begin();// vector<int>::iterator

auto drops top-level const and references. If you need them, say so explicitly:

c++
const int ci = 42;
auto a = ci;          // int, NOT const int (top-level const dropped)
const auto b = ci;    // const int
auto &c = ci;         // const int& (low-level const preserved)

decltype

decltype returns the exact type of an expression, including references and const. It does not evaluate the expression — it just inspects its type.

c++
int x = 5;
decltype(x) y = x;     // int (x is a variable)
decltype((x)) z = x;   // int& (parenthesized = lvalue expression)
The parentheses trap: decltype(x) gives the declared type of variable x. But decltype((x)) treats x as an expression, and since it's an lvalue, the result is a reference. This subtle difference has bitten many a C++ programmer.
Given const int ci = 0; auto x = ci; — what is the type of x?

Chapter 8: Beyond — Connections

This chapter covered the foundation of everything in C++. Every class, every template, every algorithm ultimately manipulates objects with types stored in memory. The concepts here — especially pointers, references, and const — will recur in every subsequent chapter.

What We Learned vs What Comes Next

This ChapterBuilds Toward
Primitive typesClass types (Ch 7), template parameters (Ch 16)
constconst member functions, constexpr (Ch 7)
ReferencesPass by reference (Ch 6), range-for, move references (Ch 13)
PointersDynamic memory (Ch 12), smart pointers, polymorphism (Ch 15)
auto / decltypeLambda captures, template type deduction (Ch 16)
Lippman's parting wisdom: "Understanding the interactions among types — especially compound types such as references and pointers — can be tricky. Most problems come from ignoring these interactions."

Continue Reading

Next up: Chapter 3: Strings, Vectors, and Arrays — where we use these basic types to build the first real data structures.