πŸ”’ Private Site

This site is password-protected.

C++ Fundamentals

The bedrock of everything. Master these thoroughly before moving forward.


Table of Contents


Glossary β€” Key Terms at a Glance

Term Meaning
Translation Unit A .cpp file after preprocessing β€” the unit the compiler works on
Preprocessing Expanding #include, #define, #ifdef before compilation
Narrowing Conversion Implicit conversion that loses data (e.g., double β†’ int)
constexpr Value evaluated at compile time β€” zero runtime cost
const Value that cannot be modified after initialization
auto Compiler deduces the type from the initializer expression
Brace Initialization Type x{val}; β€” prevents narrowing, preferred in modern C++
Range-based for for (auto& x : container) β€” iterates without index boilerplate
string_view Non-owning, read-only view of a string β€” no allocation, no copy
Scoped Enum enum class β€” type-safe, no implicit int conversion
Header Guard #ifndef / #define / #endif preventing double inclusion
#pragma once Non-standard but widely supported alternative to header guards

1 β€” Compilation Model

1.1 The Compilation Pipeline

Definition: C++ is a compiled language. Source code goes through a multi-stage pipeline before becoming an executable.

Source (.cpp) β†’ Preprocessor β†’ Compiler β†’ Assembler β†’ Linker β†’ Executable
Stage What Happens
Preprocessor Expands #include, #define, #ifdef directives
Compiler Translates C++ to assembly (one translation unit per .cpp)
Assembler Converts assembly to object code (.o / .obj)
Linker Combines object files, resolves symbols, produces executable

Example β€” Separate Compilation:

// Compile a single file
g++ -std=c++17 -O2 -Wall -Wextra -o program main.cpp

// Separate compilation (for larger projects)
g++ -c main.cpp       // produces main.o
g++ -c utils.cpp      // produces utils.o
g++ main.o utils.o -o program

πŸ” Why separate compilation? In large projects, recompiling every file on each change is slow. Separate compilation recompiles only changed files, then the linker stitches them together.


1.2 Key Compiler Flags

Flag Purpose
-std=c++17 / -std=c++20 Select language standard
-O2 / -O3 Optimization level (O3 = aggressive)
-Wall -Wextra -Wpedantic Enable all warnings (always use!)
-g Include debug symbols
-fsanitize=address,undefined Runtime sanitizers (catch bugs in development)

⚠️ Always compile with -Wall -Wextra during development. Many C++ bugs are caught by warnings alone β€” uninitialized variables, narrowing conversions, unused parameters. Treat warnings as errors in production: -Werror.


2 β€” Data Types

2.1 Fundamental Types

Definition: Fundamental (built-in) types are provided directly by the language. Their size can vary by platform, but typical sizes on 64-bit systems are:

Type Size (typical) Range
bool 1 byte true / false
char 1 byte $-128$ to $127$ or $0$ to $255$
short 2 bytes $-32{,}768$ to $32{,}767$
int 4 bytes $-2^{31}$ to $2^{31}-1$
long long 8 bytes $-2^{63}$ to $2^{63}-1$
float 4 bytes ~7 decimal digits precision
double 8 bytes ~15 decimal digits precision
long double 8–16 bytes ~18–33 decimal digits

2.2 Fixed-Width Types

Key Insight: Fundamental type sizes can vary across platforms. For portable code (especially network protocols, binary file formats, HFT systems), use fixed-width types from <cstdint>.

#include <cstdint>

int8_t   x1;  // exactly 8 bits, signed
uint8_t  x2;  // exactly 8 bits, unsigned
int16_t  x3;  // exactly 16 bits
int32_t  x4;  // exactly 32 bits
int64_t  x5;  // exactly 64 bits
size_t   x6;  // unsigned, for sizes/indices (platform word-size)

2.3 Type Conversions β€” Danger Zone

⚠️ Implicit narrowing conversions are a major source of bugs in C++. The compiler may silently lose data.

Example β€” Narrowing bugs & prevention:

// DANGEROUS: implicit narrowing β€” compiles silently!
int a = 3.14;          // a = 3, fractional part lost
unsigned int b = -1;   // b = 4294967295 (wraps around!)

// SAFE: brace initialization prevents narrowing
int a{3.14};           // COMPILE ERROR β€” narrowing detected βœ“
unsigned int b{-1};    // COMPILE ERROR βœ“

// Explicit cast β€” when you intentionally want conversion
double d = 3.14;
int i = static_cast<int>(d);  // i = 3, intentional and readable

πŸ” Why This Matters: In financial systems, silently losing the fractional part of 3.14 means losing $0.14 per transaction. Brace initialization catches this at compile time.


3 β€” Variables & Initialization

3.1 Four Ways to Initialize

Definition: C++ offers four initialization syntaxes. Brace initialization {} is preferred in modern C++ because it prevents narrowing and works uniformly.

int a;          // default β€” UNINITIALIZED for built-in types (garbage value!)
int b = 5;      // copy initialization
int c(5);       // direct initialization
int d{5};       // brace/uniform initialization (PREFERRED βœ“)

⚠️ Uninitialized variables: int a; does not zero-initialize for built-in types. a contains whatever garbage was in that memory location. This is undefined behavior if read before assignment.


3.2 Why Brace Initialization?

int x{};        // zero-initialized (x = 0) βœ“
double d{3.14}; 
int arr[]{1, 2, 3, 4, 5};
std::vector<int> v{1, 2, 3};

// Prevents narrowing conversions β€” compiler catches bugs
int bad{3.5};   // ERROR: narrowing double β†’ int

πŸ” Why {} is the best default: It works for all types, it zero-initializes when empty, and it catches narrowing at compile time. The only edge case is std::vector<int> v{10} creates a vector with one element 10, not ten elements β€” use v(10) for that.


3.3 auto β€” Type Deduction

Definition: auto tells the compiler to deduce the variable’s type from its initializer. It reduces verbosity without losing type safety.

auto x = 42;           // int
auto y = 3.14;         // double
auto s = std::string("hello");  // std::string
auto& ref = x;         // int& (reference)
const auto& cref = x;  // const int&

// Especially useful with complex types
auto it = myMap.begin();  // instead of std::map<std::string, int>::iterator

Best Practice: Use auto when the type is obvious from the right side or when the type name is long. Don’t use it when the type isn’t clear β€” readability comes first.


3.4 const and constexpr

Keyword Meaning When Evaluated
const Cannot be modified after initialization Runtime (may also be compile-time)
constexpr Must be computable at compile time Compile time β€” zero runtime cost
const int MAX_SIZE = 100;           // runtime constant
constexpr int TABLE_SIZE = 256;     // compile-time constant
constexpr double PI = 3.14159265358979;

// constexpr functions β€” evaluated at compile time if possible
constexpr int factorial(int n) {
    return (n <= 1) ? 1 : n * factorial(n - 1);
}
constexpr int f5 = factorial(5);    // computed at compile time = 120

πŸ” Why constexpr? It moves computation from runtime to compile time. In HFT, every nanosecond matters β€” precomputing lookup tables at compile time means zero cost at runtime.


4 β€” Control Flow

4.1 Conditionals

// if-else
if (x > 0) {
    // positive
} else if (x < 0) {
    // negative
} else {
    // zero
}

// C++17: if with initializer (limits scope of variable)
if (auto it = m.find(key); it != m.end()) {
    // use it->second β€” 'it' only exists in this scope
}

// switch
switch (day) {
    case 1: std::cout << "Monday"; break;
    case 2: std::cout << "Tuesday"; break;
    default: std::cout << "Other"; break;
}

// Ternary
int abs_val = (x >= 0) ? x : -x;

4.2 Loops

// for loop
for (int i = 0; i < n; ++i) {
    // prefer ++i over i++ for non-primitive types (avoids temporary copy)
}

// range-based for (C++11) β€” PREFERRED for iterating containers
std::vector<int> v = {1, 2, 3, 4, 5};
for (int x : v) { /* copy of each element */ }
for (int& x : v) { /* reference β€” can modify */ }
for (const int& x : v) { /* const ref β€” no copy, no modify */ }
for (const auto& x : v) { /* even better β€” type deduced */ }

// while
while (condition) { /* ... */ }

// do-while (executes at least once)
do { /* ... */ } while (condition);

Best Practice: Use for (const auto& x : container) as the default for reading. Use for (auto& x : container) when you need to modify elements.


5 β€” Functions

5.1 Declaration & Definition

// Declaration (prototype) β€” tells the compiler the function exists
int add(int a, int b);

// Definition β€” provides the implementation
int add(int a, int b) {
    return a + b;
}

// Default arguments
void print(const std::string& msg, int times = 1) {
    for (int i = 0; i < times; ++i)
        std::cout << msg << "\n";
}

5.2 Pass by Value vs Reference

How you pass arguments determines performance and whether the function can modify the original.

void byValue(int x);              // copies x β€” fine for small types
void byRef(int& x);               // modifies original
void byConstRef(const int& x);    // reads original, no copy, no modify
void byPointer(int* x);           // passes address
Scenario How to Pass
Built-in types (int, double, char) By value β€” small, copying is cheap
Large objects (strings, vectors, structs) By const& β€” avoids expensive copy
Need to modify the original By reference &
May be null By pointer *
void process(const std::string& s);    // GOOD: no copy
void process(std::string s);           // BAD: copies entire string on every call

⚠️ Passing a std::string by value copies the entire string contents. For a 1MB string, that’s 1MB copied per call!


5.3 Function Overloading

int    max(int a, int b)       { return (a > b) ? a : b; }
double max(double a, double b) { return (a > b) ? a : b; }
// Compiler chooses the correct version based on argument types at compile time

5.4 Inline Functions

inline int square(int x) { return x * x; }
// Hint to compiler: substitute the function body at the call site
// Modern compilers inline automatically β€” use for short functions in headers

6 β€” Arrays & Strings

6.1 C-Style Arrays

⚠️ C-style arrays have fundamental problems. Know them for legacy code, but prefer std::array for new code.

int arr[5] = {1, 2, 3, 4, 5};
int matrix[3][4] = {};             // zero-initialized

// PROBLEM: arrays decay to pointers when passed to functions
void func(int arr[]) {
    sizeof(arr);  // gives pointer size (8 bytes), NOT array size!
}

6.2 std::array

Definition: std::array<T, N> is a fixed-size, stack-allocated container that knows its own size and supports full STL compatibility.

#include <array>
std::array<int, 5> arr = {1, 2, 3, 4, 5};
arr.size();    // 5 β€” knows its own size!
arr.at(2);     // bounds-checked access (throws on out-of-range)
arr[2];        // unchecked (faster, but no safety)

Key Insight: std::array has zero overhead β€” same performance as a C array, but with size awareness, bounds checking via .at(), and STL algorithm compatibility.


6.3 std::string & string_view

#include <string>
std::string s = "Hello";
s += " World";                  // concatenation
s.size();                       // 11
s.substr(0, 5);                 // "Hello"
s.find("World");                // 6
s.empty();                      // false

// String ↔ number conversions
int n = std::stoi("42");
double d = std::stod("3.14");
std::string ns = std::to_string(42);

C++17: string_view β€” Non-owning, zero-copy string reference:

#include <string_view>
void process(std::string_view sv) {
    // reads the string without copying or allocating
    // can be constructed from std::string, const char*, or substring
}

std::string msg = "Hello World";
process(msg);               // no copy
process("literal");         // no copy
process(msg.substr(0, 5));  // creates temp string (use string_view::substr instead)

πŸ” Why string_view? In a trading system, passing const std::string& to a function that only reads the string is efficient β€” but constructing a string from a const char* still allocates. string_view avoids ALL allocations.


7 β€” Input / Output

7.1 Console I/O

#include <iostream>

// Output β€” prefer "\n" over std::endl (endl flushes the buffer = slow)
std::cout << "Value: " << x << "\n";

// Input
int n;
std::cin >> n;

// Read entire line (including spaces)
std::string line;
std::getline(std::cin, line);

// Fast I/O (essential for competitive programming)
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);

7.2 File I/O

#include <fstream>

// Writing
std::ofstream out("output.txt");
out << "Hello File\n";
out.close();

// Reading line by line
std::ifstream in("input.txt");
if (!in.is_open()) {
    std::cerr << "Failed to open file\n";
    return;
}
std::string line;
while (std::getline(in, line)) {
    std::cout << line << "\n";
}
in.close();

πŸ” RAII with file streams: std::ifstream and std::ofstream automatically close the file when the object is destroyed (end of scope). Explicitly calling .close() is optional but good practice.


7.3 Formatted Output

#include <iomanip>
std::cout << std::fixed << std::setprecision(2) << 3.14159;  // "3.14"
std::cout << std::setw(10) << std::right << 42;               // "        42"
std::cout << std::hex << 255;                                  // "ff"

// C++20: std::format (printf-like, type-safe)
#include <format>
std::cout << std::format("Hello {}, you are {} years old\n", name, age);

8 β€” Enums, Structs & Preprocessor

8.1 Enums

Scoped enums (enum class) are type-safe and don’t pollute the enclosing namespace. Always prefer them over old-style enum.

// Old-style enum (avoid β€” pollutes namespace, implicit int conversion)
enum Color { RED, GREEN, BLUE };

// Scoped enum (C++11) β€” PREFERRED
enum class Direction : uint8_t {
    North, South, East, West
};
Direction d = Direction::North;
// int x = d;  // ERROR β€” no implicit conversion (type-safe!) βœ“
int x = static_cast<int>(d);  // explicit conversion if needed

8.2 Structs

struct Point {
    double x;
    double y;
    
    double distance_to(const Point& other) const {
        double dx = x - other.x;
        double dy = y - other.y;
        return std::sqrt(dx * dx + dy * dy);
    }
};

Point p1{3.0, 4.0};                // aggregate initialization
Point p2{.x = 1.0, .y = 2.0};     // C++20 designated initializers
auto [px, py] = p1;                // C++17 structured bindings β†’ px=3.0, py=4.0

8.3 Preprocessor

#include <header>       // system header
#include "myheader.h"   // local header

#define PI 3.14159      // macro constant (prefer constexpr instead)
#define MAX(a,b) ((a)>(b)?(a):(b))  // macro function (prefer inline/template)

// Header guards β€” prevent double inclusion
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ... declarations ...
#endif

// Or simply (widely supported, not technically standard):
#pragma once

// Conditional compilation
#ifdef DEBUG
    std::cout << "Debug mode\n";
#endif

Modern C++ reduces the need for the preprocessor. Use constexpr instead of #define for constants. Use inline functions or templates instead of macro functions. Use if constexpr instead of #ifdef where possible.


Practice Questions

Q1. Explain the four stages of C++ compilation. What does each stage produce as output?

Q2. What is the difference between const and constexpr? Give an example where const would work but constexpr would not.

Q3. Write a constexpr function that computes the $n$-th Fibonacci number. Why is constexpr useful here?

Q4. What are the four ways to initialize a variable in C++? Why is brace initialization preferred?

Q5. Explain the danger of this code: unsigned int x = -1;. What value does x hold and why?

Q6. When should you pass a function argument by value, by reference, by const reference, and by pointer? Give a concrete example for each.

Q7. Explain the difference between std::string and std::string_view. When would you use each?

Q8. Why should you prefer std::array over C-style arrays? What problem does β€œarray decay” cause?

Q9. What is the advantage of scoped enums (enum class) over traditional enums? Demonstrate with code.

Q10. A teammate writes std::endl in every output statement instead of "\n". Why is this a performance issue? When is std::endl actually needed?


Key Takeaways

  1. Always use brace initialization {} β€” catches narrowing bugs at compile time
  2. Use const and constexpr liberally β€” makes intent clear, enables optimizations
  3. Pass by const& for non-trivial types β€” avoids unnecessary copies
  4. Prefer std::array over C arrays β€” same performance, type-safe
  5. Prefer std::string over char* β€” memory-safe, convenient
  6. Enable all warnings β€” -Wall -Wextra -Wpedantic
  7. Use ++i not i++ in loops β€” avoids an unnecessary copy for iterator types

← Back to C++ Notes