C++ Fundamentals
The bedrock of everything. Master these thoroughly before moving forward.
Table of Contents
1 β Compilation Model
2 β Data Types
3 β Variables & Initialization
- 3.1 Four Ways to Initialize
- 3.2 Why Brace Initialization?
- 3.3 auto β Type Deduction
- 3.4 const and constexpr
4 β Control Flow
5 β Functions
- 5.1 Declaration & Definition
- 5.2 Pass by Value vs Reference
- 5.3 Function Overloading
- 5.4 Inline Functions
6 β Arrays & Strings
7 β Input / Output
8 β Enums, Structs & Preprocessor
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.14means 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 isstd::vector<int> v{10}creates a vector with one element10, not ten elements β usev(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, passingconst std::string&to a function that only reads the string is efficient β but constructing astringfrom aconst char*still allocates.string_viewavoids 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::ifstreamandstd::ofstreamautomatically 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
- Always use brace initialization
{}β catches narrowing bugs at compile time - Use
constandconstexprliberally β makes intent clear, enables optimizations - Pass by
const&for non-trivial types β avoids unnecessary copies - Prefer
std::arrayover C arrays β same performance, type-safe - Prefer
std::stringoverchar*β memory-safe, convenient - Enable all warnings β
-Wall -Wextra -Wpedantic - Use
++inoti++in loops β avoids an unnecessary copy for iterator types