Skip to main content

C++ Fundamentals

Before diving into complex topics, we must establish a solid foundation. C++ is a compiled language, which means your human-readable code is transformed into machine-readable instructions before it can run. This is different from interpreted languages like Python, where code is executed line-by-line.

1. The Compilation Pipeline

Understanding how C++ code runs is crucial for debugging and optimization. It’s not magic; it’s a multi-step process.

The Steps Explained

  1. Preprocessing: This happens before compilation. It handles directives starting with # (like #include). It essentially copy-pastes code into your file.
  2. Compilation: The compiler translates your C++ code into Assembly, a low-level human-readable language specific to your CPU architecture.
  3. Assembly: The assembler converts assembly code into Machine Code (binary), resulting in an “Object File” (.o or .obj).
  4. Linking: Finally, the linker combines your object files with external Libraries (like the C++ Standard Library) to create the final executable.

2. Anatomy of a C++ Program

Let’s dissect a standard C++ program line-by-line to understand what’s happening.
// Preprocessor directive: Include the Input/Output stream library
#include <iostream>

// Main function: The entry point of every C++ program
int main() {
    // std::cout is the output stream
    // << is the insertion operator (think of it as pushing data to the console)
    std::cout << "Hello, World!" << std::endl;

    // Return 0 indicates successful execution to the Operating System
    return 0;
}

Key Components

  • #include <iostream>: Tells the preprocessor to copy the contents of the iostream file here. This gives us access to input/output functionality.
  • int main(): The function called by the OS when you run the program. It must return an integer (usually 0 for success, non-zero for error).
  • std::: This is a namespace. It prevents name collisions. Think of it like a surname; std::cout is “cout from the Standard family”.
  • cout: Character Output. Represents the console/terminal.
  • endl: Inserts a newline character (\n) and flushes the buffer (forces output to appear immediately).

3. Variables & Data Types

C++ is statically typed, meaning variable types are checked at compile time. This catches many errors before you even run the program.

Fundamental Types

TypeSize (Typical)DescriptionRange
int4 bytesInteger (Whole numbers)-2B to +2B
double8 bytesDouble-precision float (Decimals)~15 decimal digits
float4 bytesSingle-precision float~7 decimal digits
char1 byteASCII character-128 to 127
bool1 byteBoolean (Logic)true or false
void0 bytesEmpty/No typeN/A

Initialization Styles

C++ offers multiple ways to initialize variables. Brace initialization (Uniform Initialization) is preferred in modern C++ because it prevents data loss.
int a = 10;         // C-style assignment. Simple, but allows narrowing.
int b(20);          // Constructor style. Used often in classes.
int c{30};          // Brace initialization (Preferred). Safe.

// Why Brace Initialization?
// int x = 3.14;    // Compiles (truncates to 3 silently)
// int y{3.14};     // Error: Narrowing conversion not allowed! (Safer)

Type Deduction (auto)

Introduced in C++11, auto lets the compiler deduce the type from the initializer. This is useful for complex types but should be used judiciously.
auto x = 42;        // int
auto y = 3.14;      // double
auto name = "Dev";  // const char*
Use auto when the type is obvious (e.g., auto i = 0) or when types are complex (e.g., iterators). Do not use it if it obscures readability.

4. Input & Output (I/O)

The <iostream> library provides streams for input and output. Think of streams as a flow of data bytes.
#include <iostream>
#include <string>

int main() {
    int age;
    std::string name;

    std::cout << "Enter your name: ";
    // std::cin stops at whitespace! So "John Doe" would just get "John".
    // Use std::getline for full lines.
    std::getline(std::cin, name);

    std::cout << "Enter your age: ";
    std::cin >> age; // Reads an integer from the console

    std::cout << "Welcome, " << name << " (" << age << ")" << std::endl;
    return 0;
}

5. Control Flow

Control flow dictates the order in which statements are executed.

Branching

Decisions are made using if, else if, and else.
if (score > 90) {
    std::cout << "A";
} else if (score > 80) {
    std::cout << "B";
} else {
    std::cout << "C";
}

// Ternary Operator: A concise if-else for assigning values
std::string result = (score >= 50) ? "Pass" : "Fail";

Switch Statement

Useful for checking a single variable against multiple discrete values (integers or enums).
switch (option) {
    case 1:
        std::cout << "Option 1";
        break; // Don't forget break! Otherwise it "falls through" to case 2.
    case 2:
        std::cout << "Option 2";
        break;
    default:
        std::cout << "Invalid";
}

Loops

1. For Loop (Standard) Used when you know how many times you want to iterate.
for (int i = 0; i < 5; ++i) {
    std::cout << i << " ";
}
2. Range-Based For Loop (Modern C++) The best way to iterate over containers (arrays, vectors). It reads “for each element in collection”.
std::vector<int> numbers = {1, 2, 3, 4, 5};

// "for each num in numbers"
for (int num : numbers) {
    std::cout << num << " ";
}

// Use reference (&) to avoid copying large objects
// Use const to ensure you don't accidentally modify them
for (const auto& num : numbers) {
    std::cout << num << " ";
}
3. While Loop Used when you want to loop until a condition becomes false.
while (isRunning) {
    // Game loop logic
}

6. Functions

Functions break code into reusable blocks. They make code readable and maintainable.

Declaration vs. Definition

In larger projects, we separate the declaration (telling the compiler a function exists) from the definition (the actual code).
// Declaration (Prototype) - Usually goes in a Header file (.h)
int add(int a, int b);

int main() {
    std::cout << add(5, 3);
}

// Definition - Usually goes in a Source file (.cpp)
int add(int a, int b) {
    return a + b;
}

Parameter Passing

How you pass data to functions matters for performance.
  1. Pass by Value: Copies the data. Safe, but slow for large objects.
  2. Pass by Reference: Passes the memory address. Fast, but allows modification.
  3. Pass by Const Reference: Fast and read-only. (Preferred for complex types).
// Pass by Value (Primitive types are fine)
void printAge(int age) { ... }

// Pass by Reference (Allows modification of the original variable)
void increment(int& value) {
    value++;
}

// Pass by Const Reference (Read-only, no copy)
// Use this for strings, vectors, and custom classes
void printMessage(const std::string& msg) {
    std::cout << msg;
}

Function Overloading

You can have multiple functions with the same name but different parameters. The compiler figures out which one to call based on the arguments.
void print(int i) { std::cout << "Int: " << i; }
void print(double d) { std::cout << "Double: " << d; }

Summary

  • Compilation: Preprocessing -> Compilation -> Linking.
  • Types: Statically typed. Use auto judiciously.
  • Initialization: Prefer brace initialization {} for safety.
  • Loops: Prefer range-based for loops for collections.
  • Functions: Use const type& for large objects to avoid copying.
Next, we will dive into the most notorious and powerful feature of C++: Pointers and Memory Management.