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 actually 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.