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.
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:
#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:
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
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.
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.
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
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
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:
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 plainint& 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
inthaving 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.