C++ - the complete reference
From your first "Hello, World!" to smart pointers, templates, and the STL - a single-page walkthrough of the C++ language, with diagrams, code you can actually run, and a cheat sheet at the end for quick lookups.
Introduction to C++
C++ was created by Bjarne Stroustrup in 1985 as an extension of C, adding classes and object-oriented features. It compiles to native machine code, which is why it still powers game engines, operating systems, browsers, and trading systems where performance is non-negotiable.
It is a statically typed, compiled, multi-paradigm language: you can write procedural code, object-oriented code, or generic template code in the same file.
- C++ is a superset of C — most valid C compiles as C++.
- Compiled languages trade a build step for raw execution speed.
- Standardized by ISO; current widely-used revisions are C++17, C++20, C++23.
Features of C++
C++ blends low-level control (manual memory, pointer arithmetic) with high-level abstractions (classes, templates, STL). This dual nature is its whole identity.
- Object-oriented: classes, inheritance, polymorphism.
- Fast: compiles directly to machine code, no VM overhead.
- Portable: same source runs on any platform with a conforming compiler.
- Rich standard library: containers, algorithms, iterators (the STL).
- Fine-grained memory control via pointers, new/delete, and RAII.
Structure of a C++ Program
Every C++ program needs at least one function named main — execution always starts there. Headers bring in declarations you need; the compiler stitches everything together at build time.
#include <iostream> // header for cin/cout
using namespace std; // avoid typing std:: everywhere
int main() {
cout << "Hello, World!" << endl;
return 0; // 0 means "exited successfully"
}- #include pulls in a header before compilation.
- main() is the mandatory entry point.
- return 0 signals success to the operating system.
Input & Output
C++ streams model I/O as a flow of data. cin reads from standard input, cout writes to standard output, cerr writes unbuffered errors, and clog writes buffered log messages.
int age;
cout << "Enter your age: ";
cin >> age;
cout << "You are " << age << " years old.\n";- << inserts into a stream, >> extracts from one.
- cin >> stops at whitespace — use getline() for full lines.
- cerr is unbuffered, so error messages appear immediately.
Variables
A variable is a named, typed storage location. C++ requires every variable to have a declared type before use, and that type cannot change afterward.
int score = 0;
double price = 19.99;
char grade = 'A';
bool passed = true;- Names are case-sensitive and cannot start with a digit.
- Prefer initializing at declaration to avoid garbage values.
- Scope (block, function, global, class) controls a variable's lifetime.
Data Types
Built-in types fall into a few families: integer types, floating-point types, character types, and bool. Sizes are platform-dependent but guaranteed minimums exist.
int i = 42; // typically 4 bytes
short s = 10; // >= 2 bytes
long l = 100000L; // >= 4 bytes
float f = 3.14f; // ~7 digit precision
double d = 3.14159; // ~15 digit precision
char c = 'X'; // 1 byte
bool b = false; // true / false
void* p = nullptr; // typeless pointer- Use sizeof(type) to check the exact size on your platform.
- Prefer double over float unless memory is tight.
- auto lets the compiler deduce the type from the initializer.
Operators
Operators combine into expressions following precedence and associativity rules, the same way arithmetic order-of-operations works on paper.
a + b, a - b, a * b, a / b, a % b // arithmetic
a == b, a != b, a < b, a >= b // relational
a && b, a || b, !a // logical
a & b, a | b, a ^ b, ~a, a<<1, a>>1 // bitwise
a = b, a += b, a -= b // assignment
a > b ? a : b // ternary- % (modulo) only works on integer types.
- && and || short-circuit — the right side may never evaluate.
- Bitwise operators are distinct from logical ones (& vs &&).
Type Conversion
Implicit conversion happens automatically when types mix (int to double, for example). Explicit conversion — casting — is when you tell the compiler exactly what you want.
int x = 10;
double y = x; // implicit: int -> double
double pi = 3.14159;
int truncated = (int)pi; // C-style cast
int safer = static_cast<int>(pi); // preferred C++ cast- Prefer static_cast, dynamic_cast, const_cast over C-style casts.
- Narrowing conversions (double -> int) can silently lose data.
- dynamic_cast is checked at runtime and used with polymorphic types.
Constants
A constant is a value that cannot change after initialization. Prefer const or constexpr over the older #define macro, since they respect scope and type.
const double PI = 3.14159;
constexpr int MAX_USERS = 100; // evaluated at compile time- const is checked by the type system; #define is a blind text substitution.
- constexpr guarantees the value is known at compile time.
- Literal suffixes like 10L, 3.14f, 'a' fix a literal's type.
Control Statements
Control statements decide which code runs, based on a condition. if/else branches on a boolean expression; switch branches on a single value against several cases.
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else {
grade = 'C';
}
switch (day) {
case 1: cout << "Mon"; break;
case 2: cout << "Tue"; break;
default: cout << "Other";
}- Forgetting break in a switch falls through to the next case.
- switch only compares against constant integral/enum values.
- Every if should have braces, even for one-line bodies — safer to maintain.
Loops
Loops repeat a block of code. Choose for when you know the iteration count, while when the condition drives it, and do-while when the body must run at least once.
for (int i = 0; i < 5; i++) cout << i;
int n = 5;
while (n > 0) { cout << n; n--; }
do { cout << "runs once"; } while (false);
for (int x : {1, 2, 3}) cout << x; // range-based for- break exits a loop immediately; continue skips to the next iteration.
- Range-based for avoids off-by-one indexing errors.
- Infinite loops (for(;;)) are legal — used with an internal break.
Arrays
An array is a fixed-size, contiguous block of elements of the same type. Indexing starts at 0, and C++ does not check bounds for you.
int scores[5] = {90, 85, 77, 60, 95};
cout << scores[0]; // 90
int grid[2][3] = {{1,2,3}, {4,5,6}}; // 2D array
cout << grid[1][2]; // 6- Array size is fixed at compile time (unless dynamically allocated).
- Out-of-bounds access is undefined behavior — no automatic error.
- Prefer std::array or std::vector in modern C++ for safety.
Strings
C-style strings are just char arrays terminated by \0. std::string, from the standard library, manages its own memory and offers a much richer, safer API.
char cstr[] = "Hi"; // C-style, null-terminated
#include <string>
std::string s = "Hello";
s += ", World!";
cout << s.length(); // 13
cout << s.substr(0, 5); // "Hello"- Always prefer std::string in new code.
- s.length() and s.size() are equivalent.
- std::string overloads + for concatenation and == for comparison.
Functions
A function packages a task under a name so it can be reused. Parameters can have default values, and two functions can share a name if their parameter lists differ (overloading).
int add(int a, int b = 10) { // default argument
return a + b;
}
double add(double a, double b) { // overload
return a + b;
}
add(5); // 15 (uses default)
add(2.5, 3.5); // calls the double version- A function needs a declaration (prototype) before it is used, or a definition above the call site.
- Overload resolution picks the best-matching signature at compile time.
- Pass large objects by const reference to avoid copying.
Recursion
A recursive function calls itself with a smaller version of the problem, and stops at a base case. Each call adds a frame to the call stack, so unbounded recursion overflows it.
int factorial(int n) {
if (n <= 1) return 1; // base case
return n * factorial(n-1); // recursive case
}- Every recursive function needs at least one base case.
- Deep recursion risks a stack overflow — loops are safer for large n.
- Some recursive problems (tree traversal, divide-and-conquer) are far clearer than their loop equivalent.
Pointers
A pointer stores the memory address of another variable. & takes an address, * dereferences a pointer to reach the value it points to.
int x = 10;
int* p = &x; // p holds the address of x
cout << *p; // 10, dereferenced value
*p = 20; // changes x through the pointer
cout << x; // 20
int* q = nullptr; // points to nothing- A pointer can be reassigned to point elsewhere; a reference cannot.
- Always initialize pointers — an uninitialized pointer is dangerous.
- Dereferencing nullptr or a dangling pointer is undefined behavior.
References
A reference is an alias for an existing variable — a second name for the same memory. Unlike a pointer, it must be bound at creation and can never be null or reseated.
int x = 10;
int& ref = x; // ref IS x, just another name
ref = 20; // x becomes 20
void increment(int& n) { n++; } // pass by reference
increment(x); // x becomes 21, no copy made- References cannot be null — they always refer to a valid object.
- Pass-by-reference avoids copying and lets a function modify the caller's variable.
- const int& lets you avoid a copy while preventing modification.
Dynamic Memory
new allocates memory on the heap at runtime; delete frees it. Unlike stack variables, heap memory persists until you explicitly release it — forgetting to do so is a memory leak.
int* p = new int(42); // heap allocation
cout << *p;
delete p; // release it
p = nullptr; // avoid a dangling pointer
int* arr = new int[10]; // array on the heap
delete[] arr; // must use delete[] for arrays- Every new must be paired with exactly one delete (or delete[]).
- Forgetting delete leaks memory; calling delete twice is undefined behavior.
- Modern C++ prefers smart pointers over raw new/delete (see section 32).
Structures
A struct groups related variables of different types under one name. In C++ (unlike C), a struct can also have functions, constructors, and inheritance — the only default difference from a class is that struct members are public by default.
struct Point {
int x;
int y;
};
Point p1 = {3, 4};
cout << p1.x << ", " << p1.y;- struct members default to public; class members default to private.
- Use struct for simple data bundles, class when encapsulation matters.
- Structs can be nested inside other structs or classes.
Enumerations
An enum gives readable names to a set of integer constants. enum class (a "scoped enum") is the modern, safer version — its values don't leak into the surrounding scope and don't implicitly convert to int.
enum Color { RED, GREEN, BLUE }; // classic
Color c = RED;
enum class Direction { North, South, East, West }; // scoped
Direction d = Direction::North;- Plain enum values start at 0 unless assigned otherwise.
- enum class prevents accidental comparisons between unrelated enums.
- Prefer enum class in new code for type safety.
Object-Oriented Programming
OOP models a program as a collection of interacting objects, each bundling data (attributes) with behavior (methods). A class is the blueprint; an object is an instance built from it.
class Car {
public:
string brand;
void honk() { cout << brand << " says beep!"; }
};
Car myCar;
myCar.brand = "Toyota";
myCar.honk();- The four pillars: encapsulation, abstraction, inheritance, polymorphism.
- A class defines a type; an object is a concrete value of that type.
- Members are accessed with the dot operator on an object, or -> on a pointer.
Constructors
A constructor is a special function that runs automatically when an object is created, typically to set up initial state. It shares the class name and has no return type.
class Point {
public:
int x, y;
Point() : x(0), y(0) {} // default constructor
Point(int a, int b) : x(a), y(b) {} // parameterized
Point(const Point& other) = default; // copy constructor
};
Point p1; // calls default constructor
Point p2(3, 4); // calls parameterized constructor
Point p3 = p2; // calls copy constructor- A member initializer list (: x(a), y(b)) is preferred over assigning in the body.
- The compiler generates a default and copy constructor if you define none.
- A constructor can be overloaded just like any other function.
Destructors
A destructor runs automatically when an object goes out of scope or is deleted, and is the natural place to release resources the object owns (memory, files, locks) — the basis of the RAII pattern.
class FileHandler {
public:
FileHandler() { cout << "Opening file\n"; }
~FileHandler() { cout << "Closing file\n"; } // destructor
};
{
FileHandler f; // constructor runs
} // f goes out of scope here -> destructor runs automatically- A destructor has no parameters and cannot be overloaded.
- RAII (Resource Acquisition Is Initialization) ties resource lifetime to object lifetime.
- Mark destructors virtual in a base class if you plan to delete derived objects through a base pointer.
Inheritance
Inheritance lets a derived class reuse and extend a base class's members. It models an "is-a" relationship: a Dog is an Animal.
class Animal {
public:
void eat() { cout << "eating\n"; }
};
class Dog : public Animal { // Dog inherits from Animal
public:
void bark() { cout << "woof\n"; }
};
Dog d;
d.eat(); // inherited
d.bark(); // its own- public inheritance keeps access levels as-is; protected/private tighten them.
- Types: single, multilevel, multiple, hierarchical, hybrid.
- C++ allows multiple inheritance, which other languages often avoid due to ambiguity.
Polymorphism
Polymorphism means "many forms": the same interface behaves differently depending on the actual object. Compile-time polymorphism is function/operator overloading; runtime polymorphism uses virtual functions resolved through a base pointer or reference.
class Shape {
public:
virtual double area() const { return 0; }
};
class Circle : public Shape {
double r;
public:
Circle(double r) : r(r) {}
double area() const override { return 3.14159 * r * r; }
};
Shape* s = new Circle(2.0);
cout << s->area(); // calls Circle's version at runtime- virtual enables runtime dispatch; override documents the intent and is checked by the compiler.
- Without virtual, the base class version always runs, even for a derived object.
- Runtime polymorphism needs access through a pointer or reference, not a plain object.
Abstraction
Abstraction hides implementation detail behind a simple interface. An abstract class — one with at least one pure virtual function — defines "what" without saying "how", forcing derived classes to provide the implementation.
class Shape {
public:
virtual double area() const = 0; // pure virtual -> abstract class
};
// Shape s; // ERROR: cannot instantiate an abstract class
class Square : public Shape {
double side;
public:
Square(double s) : side(s) {}
double area() const override { return side * side; }
};- An abstract class cannot be instantiated directly.
- A derived class must override every pure virtual function to become concrete.
- Abstraction defines a contract; encapsulation protects the data behind it.
Encapsulation
Encapsulation bundles data with the methods that operate on it, and restricts direct access to that data using access specifiers, exposing a controlled interface instead.
class BankAccount {
private:
double balance = 0;
public:
void deposit(double amt) {
if (amt > 0) balance += amt; // validated access
}
double getBalance() const { return balance; }
};- private hides members from outside the class; public exposes them; protected exposes them to derived classes only.
- Getters/setters let you validate or change internal representation later without breaking callers.
- Encapsulation reduces the surface area for bugs by limiting who can touch what.
File Handling
The <fstream> header provides ifstream for reading files, ofstream for writing, and fstream for both. Streams close automatically when they go out of scope, thanks to RAII.
#include <fstream>
ofstream out("data.txt");
out << "Hello, file!";
out.close();
ifstream in("data.txt");
string line;
while (getline(in, line)) cout << line << "\n";
in.close();- Always check if(file) or file.is_open() before reading/writing.
- Streams flush and close automatically when destroyed, but closing explicitly is clearer.
- Open in ios::binary mode for non-text data.
Exception Handling
Exceptions separate error-handling code from normal logic. throw raises an exception, a try block wraps code that might fail, and catch handles a specific exception type.
#include <stdexcept>
double divide(double a, double b) {
if (b == 0) throw std::runtime_error("division by zero");
return a / b;
}
try {
cout << divide(10, 0);
} catch (const std::runtime_error& e) {
cout << "Error: " << e.what();
}- Catch by const reference to avoid slicing derived exception types.
- A catch(...) block catches anything, useful as a last resort.
- Exceptions unwind the stack, calling destructors along the way — RAII keeps this safe.
Templates
Templates let you write a function or class once and have the compiler generate a version for whatever type you use it with — the foundation of generic programming and the STL itself.
template <typename T>
T maxVal(T a, T b) {
return (a > b) ? a : b;
}
maxVal(3, 7); // T = int
maxVal(2.5, 1.1); // T = double
template <typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T get() const { return value; }
};- Templates are resolved at compile time — no runtime overhead.
- A template only compiles for a given type when it is actually used with that type.
- Class templates power containers like std::vector<T>.
STL (Standard Template Library)
The STL is a library of generic containers, iterators, and algorithms. Containers store data, iterators traverse it uniformly, and algorithms (sort, find, accumulate...) operate on any container through iterators.
#include <vector>
#include <algorithm>
vector<int> nums = {5, 2, 8, 1};
sort(nums.begin(), nums.end()); // 1 2 5 8
for (int n : nums) cout << n << " ";
auto it = find(nums.begin(), nums.end(), 8);
if (it != nums.end()) cout << "found";- Common containers: vector, list, deque, map, set, unordered_map, stack, queue.
- Algorithms work generically across containers via iterators.
- vector is the default choice unless you need a specific container's tradeoffs.
Smart Pointers
Smart pointers, from <memory>, wrap a raw pointer and automatically delete it when no longer needed — eliminating most manual new/delete bugs.
#include <memory>
unique_ptr<int> u = make_unique<int>(5); // sole owner
// u2 = u; // ERROR: cannot copy a unique_ptr
shared_ptr<int> s1 = make_shared<int>(10); // shared ownership
shared_ptr<int> s2 = s1; // ref count = 2
weak_ptr<int> w = s1; // observes without owning- unique_ptr: exclusive ownership, cannot be copied, only moved.
- shared_ptr: reference-counted shared ownership, freed when the count hits zero.
- weak_ptr breaks reference cycles between shared_ptrs.
Lambda Functions
A lambda is an anonymous, inline function, useful for short callbacks passed to STL algorithms. The [] capture list decides which surrounding variables it can access.
auto add = [](int a, int b) { return a + b; };
cout << add(2, 3); // 5
int threshold = 5;
auto above = [threshold](int x) { return x > threshold; }; // capture by value
vector<int> nums = {1, 6, 3, 9};
count_if(nums.begin(), nums.end(), above);- [] captures nothing, [=] captures all by value, [&] captures all by reference.
- Lambdas can be stored in a variable with auto or a std::function.
- They are heavily used as predicates for sort, find_if, count_if, and more.
Namespaces
A namespace groups related names to prevent collisions between identically-named identifiers from different libraries.
namespace math {
int square(int x) { return x * x; }
}
cout << math::square(4); // 16
using namespace math;
cout << square(5); // 25, now unqualified- std is the namespace holding the entire standard library.
- using namespace std; is convenient but risky in headers — it can cause name clashes.
- Nested namespaces are written namespace a::b { ... } since C++17.
Preprocessor Directives
Directives run before compilation proper, on the raw text of the source file. #include inserts a file's contents; #define creates a macro; conditional directives include or exclude code blocks.
#include <iostream> // insert a header's contents
#define MAX 100 // text-substitution macro
#ifndef HEADER_H // include guard
#define HEADER_H
// declarations here
#endif- Macros are blind text substitution — no type checking, so prefer const/constexpr/inline functions.
- Include guards (or #pragma once) stop a header from being processed twice.
- Directives start with # and don't end with a semicolon.
Best Practices
Idiomatic, modern C++ ("C++ Core Guidelines" style) avoids the sharp edges of the language rather than fighting them.
- Prefer smart pointers and containers over raw new/delete.
- Mark anything that doesn't modify state const, including member functions.
- Initialize every variable at the point of declaration.
- Pass small types by value, large types by const reference.
- Follow the Rule of Zero/Five: let compiler-generated special members handle resources, or define all five if you must manage one manually.
- Prefer range-based for and STL algorithms over manual index loops.
- Compile with warnings treated seriously (-Wall -Wextra) — most bugs show up there first.
- Avoid using namespace std; in header files.
Common Interview Questions
A quick-fire set of questions that come up often in C++ interviews, useful as a final self-check.
- What is the difference between a pointer and a reference? — A reference cannot be null or reseated; a pointer can be reassigned and can be null.
- What is a virtual function, and why does it matter? — It enables runtime polymorphism, letting a base pointer call the derived class's override.
- What is the difference between struct and class? — Only the default access level: public for struct, private for class.
- What is RAII? — Tying a resource's lifetime to an object's lifetime, so it's released automatically when the object is destroyed.
- What happens if you don't define a destructor? — The compiler generates a default one, which is fine unless your class manually owns a resource.
- What is the Rule of Three/Five? — If you define one of destructor, copy constructor, or copy assignment, you likely need all three (plus move constructor/assignment for Five).
- What is a memory leak? — Allocated heap memory that is never freed because the last pointer to it is lost.
- What is the difference between shallow copy and deep copy? — A shallow copy duplicates pointers (sharing the pointed-to data); a deep copy duplicates the underlying data too.
- What is function overloading vs overriding? — Overloading picks a function by signature at compile time; overriding replaces a virtual base function at runtime.
- What is a dangling pointer? — A pointer that still holds the address of memory that has already been freed.