C++, End to End
The Absolute Basics
A C++ program is a set of text files the compiler turns into a single executable in two separate stages, and understanding that split saves you a lot of confusion later when an error message doesn't make sense. First, each .cpp file (a translation unit) gets compiled on its own into an object file. Then a separate program, the linker, stitches all the object files together into one executable, resolving which function definition goes with which function call across files.
Execution always starts in a function named main. Every C++ program needs exactly one. It returns an int: 0 conventionally means success, anything else means failure, and the operating system or calling script can check that return value.
A few things to notice in that example. #include <iostream> isn't a C++ statement, it's a preprocessor directive (the preprocessor gets its own chapter later), and it pulls in the declarations needed for std::cout. The std:: prefix means "look inside the std namespace," which is where almost everything the standard library gives you lives. And return 0; is technically optional in main specifically (the compiler inserts an implicit return 0; if you fall off the end), but write it anyway. Relying on that special case reads as an accident, not a decision.
Statements and the structure of a program
A statement is the smallest complete instruction: a variable declaration, an expression followed by a semicolon, a function call, a loop, a conditional. Statements run top to bottom inside a function unless something (a loop, a branch, a function call) changes that order. Curly braces { } group statements into a block, and a block is itself something you can use almost anywhere a single statement is expected.
Whitespace, for the most part, doesn't matter to the compiler. Indentation is entirely for humans. That's exactly why consistent indentation matters so much in practice: nothing enforces it, so a badly indented file actively lies about its own structure.
Comments
// A single-line comment: everything after // on this line is ignored.
/* A multi-line comment.
Everything between the start and end markers is ignored,
including line breaks. */
int x = 5; // trailing comments are fine tooComments should explain why, not what. "increment i" above i++; tells you nothing the code doesn't already say. A comment explaining why a loop starts at 1 instead of 0, or why a particular edge case is handled the way it is, earns its place.
Variables and initialization
A variable is a named piece of storage with a type. Declaring one reserves the storage; it doesn't necessarily give it a value.
int a; // uninitialized: contains garbage, reading it is undefined behavior
int b = 5; // copy initialization
int c(5); // direct initialization
int d{5}; // direct list initialization (preferred in modern C++)
int e{}; // value initialization: zero for a fundamental typeUse brace initialization, int d{5};, as your default. It does one thing the older forms don't: it refuses narrowing conversions at compile time.
That compile error is a feature. A silent truncation like the first line is exactly the kind of bug that survives code review and shows up as "weird" behavior three months later.
Try it yourself: delete the `int b{3.7};` line above so it compiles, run it, then bring it back and read the actual compiler error. Get used to what a narrowing-conversion error looks like now, you'll see it again.
Basic input and output
std::cout writes to standard output, std::cin reads from standard input, and both use the << / >> operators (overloaded for this purpose, which is a preview of operator overloading, covered much later). Chain them to output or read multiple things in one statement.
That example uses using namespace std; to drop the std:: prefix everywhere. Don't do this in real code, especially not in a header file where it leaks into every file that includes it. It's fine in a 10-line teaching example; it's a real liability once a codebase has thousands of names in scope and two libraries happen to define something with the same name.
Compiling a program, end to end
Four distinct stages happen when you run a single compile command, even though most compilers hide the seams:
- Preprocessing. #include, #define, and other # directives are resolved textually. Comments are stripped. The output is one long, expanded, pure-C++ text stream.
- Compilation proper. The preprocessed text is parsed, type-checked, and turned into an object file (machine code plus a symbol table of names it defines and names it still needs) for that one translation unit only.
- Linking. The linker takes every object file plus any libraries, matches each undefined symbol to a definition somewhere, and produces one executable. "undefined reference" errors happen here, not during compilation, which is why they can look confusing: the code compiled fine, the definition just never turned up anywhere the linker looked.
- Loading. When you run the executable, the operating system maps it into memory and starts executing at the entry point (which calls your main, after some runtime setup).
Try it yourself: if you have g++ locally, run `g++ -c main.cpp` to stop after the compile stage (you'll get main.o but no executable), then `g++ main.o -o main` to run the linker separately. Seeing the two stages as two commands makes "undefined reference" errors much less mysterious the first time you hit one for real.
Next: functions, and how a program stops being one file.