C++ Syntax & Variables

Types, const, references vs pointers, control flow, functions, and function overloading in C++.

Basic types

C++ is statically typed — every variable's type is fixed at compile time.

C++
int age = 25;              // integer, typically 32-bit
double price = 19.99;      // 64-bit floating point
float ratio = 3.14f;       // 32-bit floating point
char grade = 'A';          // single character
bool isActive = true;      // true / false

For when the exact width matters (network protocols, binary file formats, embedded targets), prefer the fixed-width types from <cstdint> over plain int/long, whose sizes are only guaranteed to be at least a certain width and vary by platform:

C++
#include <cstdint>

int32_t userId = 1001;     // exactly 32 bits, everywhere
uint8_t flags = 0;         // exactly 8 bits, unsigned
int64_t fileSize = 4'294'967'296; // exactly 64 bits — digit separators improve readability

auto — type deduction

auto asks the compiler to deduce the type from the initializer. It's still static typing — the type is fixed at compile time, just not spelled out by you:

C++
auto count = 10;        // deduced as int
auto name = std::string("Ada"); // deduced as std::string
auto price = 19.99;     // deduced as double

Use auto when the type is obvious from context or verbose to spell out (iterators, lambda types); prefer an explicit type when it makes the code more readable for someone skimming it.

const and constexpr

C++
const double TAX_RATE = 0.15;   // cannot be reassigned after initialization
constexpr int MAX_USERS = 100;  // value known and fixed at COMPILE time

const means "I promise not to modify this" — the value could still, in principle, be computed at runtime. constexpr is a stronger guarantee: the value must be computable at compile time, which lets the compiler use it for things like array sizes and enables more aggressive optimization.

References vs pointers — the core distinction

This is one of the most important things to get right in C++. Both let you refer to another variable's storage instead of copying it, but they behave very differently.

C++
int score = 90;

int& scoreRef = score;   // reference: an alias for score itself
int* scorePtr = &score;  // pointer: a variable holding score's address

scoreRef = 95;            // modifies score directly — no special syntax needed
*scorePtr = 100;          // modifies score through the pointer — must dereference with *

std::cout << score << "\n"; // 100
Reference (int&) Pointer (int*)
Can be null No — must be bound to a real object at creation Yes — nullptr is a valid pointer value
Can be reassigned to refer elsewhere No — bound for its whole lifetime Yes — a pointer variable can be pointed at something else
Syntax to access the value Same as the original variable (no * needed) Requires dereferencing with *
Needs & to obtain Only at binding time Any time, to get the address of a variable
Typical use Function parameters, "give me an alias, this can't be null" Optional/nullable references, dynamic data structures, pointer arithmetic

A good rule of thumb: default to references for function parameters ("take this by reference, it always refers to something valid"), and reach for a pointer (or better, a smart pointer — covered in the memory management page) only when you specifically need nullability, reseating, or ownership semantics.

C++
void increment(int& value) {  // reference parameter — modifies the caller's variable
    value++;
}

void maybeIncrement(int* value) { // pointer parameter — caller can legally pass nullptr
    if (value != nullptr) {
        (*value)++;
    }
}

int main() {
    int x = 5;
    increment(x);
    std::cout << x << "\n"; // 6

    maybeIncrement(&x);
    std::cout << x << "\n"; // 7
    maybeIncrement(nullptr); // safe — the null check inside handles it
}

Control flow

C++
int n = 7;

if (n % 2 == 0) {
    std::cout << "even\n";
} else if (n < 0) {
    std::cout << "negative\n";
} else {
    std::cout << "odd\n";
}

switch (n) {
    case 1:
        std::cout << "one\n";
        break;
    default:
        std::cout << "something else\n";
}

for (int i = 0; i < 3; i++) {
    std::cout << i << " ";           // 0 1 2
}

std::vector<int> nums = {10, 20, 30};
for (int n : nums) {                  // range-based for (C++11+)
    std::cout << n << " ";            // 10 20 30
}

int i = 0;
while (i < 3) {
    std::cout << i << " ";
    i++;
}

Prefer the range-based for whenever you're just iterating every element of a container — it's shorter and can't go out of bounds by accident.

Functions and overloading

C++
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {  // overload: same name, different parameter types
    return a + b;
}

int add(int a, int b, int c) {    // overload: different number of parameters
    return a + b + c;
}

int main() {
    std::cout << add(2, 3) << "\n";       // 5 — calls int version
    std::cout << add(2.5, 3.5) << "\n";   // 6 — calls double version
    std::cout << add(1, 2, 3) << "\n";    // 6 — calls three-argument version
}

Function overloading lets several functions share a name as long as the compiler can tell them apart by their parameter list (type, count, or order) — it's resolved entirely at compile time based on the argument types at each call site, not at runtime.

Default arguments reduce the need for some overloads:

C++
double calculatePrice(double base, double discount = 0.0) {
    return base - (base * discount);
}

calculatePrice(100.0);        // 100.0 — discount defaults to 0.0
calculatePrice(100.0, 0.2);   // 80.0

Common mistakes

  • Confusing references and pointers — writing int* ref = &value; when a plain int& ref = value; reference would be simpler and safer, because it can never be null or left dangling by reassignment.
  • Forgetting that a reference must be initialized when declared and can never be rebound afterwards — int& r; alone is a compile error.
  • Relying on int having a specific bit width across platforms instead of using <cstdint> types where the exact size actually matters.

Interview questions

Q: What is the fundamental difference between a reference and a pointer? A reference is an alias bound permanently to one object at creation, can never be null, and is used with the same syntax as the original variable. A pointer is a separate variable holding an address, can be null, can be reassigned to point elsewhere, and requires explicit dereferencing (*) to access the pointed-to value.

Q: What's the difference between const and constexpr? const guarantees the value won't change after initialization, but that value could still be determined at runtime (e.g., from user input). constexpr guarantees the value is known at compile time, which allows the compiler to use it in contexts requiring a compile-time constant (array bounds, template arguments) and enables further optimization.