What is this?
Every C++ journey starts with a simple program that prints text to the screen. You write the code in a .cpp file, compile it with a compiler (like g++), and run the output binary. The #include <iostream> directive imports the Input/Output stream library, and std::cout is the standard output object used to print.
#include <iostream> int main() { std::cout << "Hello QuickRef\n"; return 0; }
# Compile: g++ takes source file, -o sets output name $ g++ hello.cpp -o hello # Run the compiled binary $ ./hello Hello QuickRef
main() is the entry point of every C++ program. It must return an int. Returning 0 signals success to the operating system. The \n inside the string is a newline character.
What is a Variable?
A variable is a named container that stores a value in memory. Every variable has a data type that tells the compiler what kind of data it holds (numbers, text, true/false, etc.). You declare a variable by writing: type name = value;
int number = 5; // Integer — whole numbers float f = 0.95; // Float — decimal, ~7 digits precision doublePI = 3.14159; // Double — decimal, ~15 digits precision char yes = 'Y'; // Char — single character (use single quotes) std::string s = "ME"; // String — text (use double quotes) bool isRight = true; // Bool — true or false only // Constants: value can never be changed after declaration const floatRATE = 0.8; // Convention: UPPER_CASE for constants // Modern C++11 brace-initialization syntax int age {25}; std::cout << age; // Prints: 25
| Data Type | Size | Range / Notes |
|---|---|---|
| int | 4 bytes | -2,147,483,648 to 2,147,483,647 |
| float | 4 bytes | ~7 significant decimal digits |
| double | 8 bytes | ~15 significant decimal digits |
| char | 1 byte | -128 to 127 (stores one character) |
| bool | 1 byte | true (1) or false (0) |
| void | N/A | Represents no value / no type |
| wchar_t | 2 or 4 bytes | Wide character (Unicode support) |
Use double over float by default — it is more precise and modern CPUs handle both at the same speed. Use float only when memory is a tight constraint (e.g., game development, embedded systems).
Input & Utility Functions
std::cin reads user input from the keyboard and stores it in a variable. The >> operator extracts (reads) from the input stream. std::swap() is a standard utility that exchanges the values of two variables in one line.
int num; std::cout << "Type a number: "; std::cin >> num; // Reads integer from keyboard into num std::cout << "You entered " << num;
int a = 5, b = 10; std::swap(a, b); // Exchanges values of a and b // Output: a=10, b=5 std::cout << "a=" << a << ", b=" << b;
std::cout and std::cin use the extraction (>>) and insertion (<<) operators. You can chain multiple outputs: cout << a << " " << b;
What are Comments?
Comments are notes for human readers — the compiler completely ignores them. Use them to explain why you wrote something, or to temporarily disable code. C++ has two styles: single-line (//) and multi-line (/* */).
// This is a single-line comment — everything after // is ignored /* This is a multi-line comment. It can span many lines. Useful for longer explanations. */ int x = 5; // inline comment — after the code on the same line
Good code is self-documenting. Write comments to explain the reason behind a decision, not just what the code does. Avoid: // add 1 to i. Prefer: // skip the header row.
What is a Reference?
A reference is an alias — another name for an existing variable. Both the original and the reference point to the same memory address, so changing one changes the other. References are declared using the & symbol after the type.
int i = 1; int& ri = i; // ri is a reference (alias) to i // ri and i now share the SAME memory location ri = 2; // Changes ri → also changes i (same memory!) std::cout << "i=" << i; // Prints: i=2 i = 3; // Changes i → also changes ri std::cout << "ri=" << ri; // Prints: ri=3
References must be initialized when declared — you cannot have a reference that points to nothing. This is different from pointers which can be null.
What is a Namespace?
A namespace is a named scope that groups related identifiers (functions, classes, variables) to avoid name conflicts. The C++ standard library lives in the std namespace — that's why you write std::cout. You can bring names into scope with using namespace, or create your own namespace with namespace myName { ... }.
#include <iostream> namespace ns1 { int val() { return 5; } // function inside namespace ns1 } int main() { std::cout << ns1::val(); // Access using :: scope operator → prints 5 }
#include <iostream> namespace ns1 { int val() { return 5; } } using namespace ns1; // Now val() is directly accessible using namespace std; // Now cout, cin etc. work without std:: int main() { cout << val(); // No std:: or ns1:: prefix needed — prints 5 }
using namespace std; is convenient for small programs but can cause name conflicts in large codebases. In professional code, prefer explicit std:: prefixes.
What is an Array?
An array is a fixed-size collection of elements of the same type, stored in contiguous memory. In C++11+, prefer std::array over C-style arrays — it's safer and has built-in size awareness. Elements are accessed by zero-based index: arr[0] is the first element.
#include <array> // Step 1: Declare empty array of 3 integers, then assign std::array<int, 3> marks; marks[0] = 92; marks[1] = 97; marks[2] = 98; // Step 2: Declare AND initialize at once (cleaner) std::array<int, 3> scores = {92, 97, 98}; // Step 3: Partial init — missing elements default to 0 std::array<int, 3> partial = {92, 97}; std::cout << partial[2]; // Outputs: 0 (default initialized)
The size of a std::array is fixed at compile time. For dynamic resizing, use std::vector. Array indices start at 0, so a 3-element array has indices 0, 1, and 2.
Reading and Writing Elements
You can read and write individual array elements using bracket notation arr[index]. You can also read user input directly into an array element using std::cin.
// Memory layout: [92][97][98][99][98][94] // index: 0 1 2 3 4 5 std::array<int, 6> marks = {92, 97, 98, 99, 98, 94}; std::cout << marks[0]; // Read first element → 92 marks[1] = 99; // Write: change 2nd element to 99 std::cin >> marks[2]; // Read from user into 3rd element
Iterating Over Arrays
To print all elements, loop through the array. C++ offers two main styles: the range-based for loop (modern, cleaner) and the traditional index-based for loop (gives you the index).
char ref[5] = {'R', 'e', 'f'}; // Method 1: Range-based for loop (C++11, recommended) for (const int &n : ref) { std::cout << std::string(1, n); // Convert char to string and print } // Method 2: Traditional index-based for loop for (int i = 0; i < sizeof(ref); ++i) { std::cout << ref[i]; // Access by index }
Use const int &n in range-based loops to avoid copying large objects. The & means "reference" (no copy), and const means you won't modify it.
2D Arrays — Arrays of Arrays
A 2D array is like a grid or table with rows and columns. You access elements using two indices: arr[row][col]. You need a nested loop (loop inside a loop) to traverse all elements.
// Grid layout: // j→ 0 1 2 3 4 5 // i=0: 1 2 3 4 5 6 // i=1: 6 5 4 3 2 1 int x[2][6] = { {1, 2, 3, 4, 5, 6}, // Row 0 {6, 5, 4, 3, 2, 1} // Row 1 }; for (int i = 0; i < 2; ++i) { // Loop over rows for (int j = 0; j < 6; ++j) { // Loop over columns std::cout << x[i][j] << " "; } } // Outputs: 1 2 3 4 5 6 6 5 4 3 2 1
Conditional Branching
An if statement executes a block of code only when a condition is true. Add else to run code when it's false. Chain multiple conditions with else if. Only the first matching branch runs.
// Basic if-else int number = 16; if (number % 2 == 0) { std::cout << "even"; // Runs when condition is true } else { std::cout << "odd"; // Runs when all conditions above are false } // Outputs: even // Chained else-if ladder int score = 99; if (score == 100) std::cout << "Superb"; else if (score >= 90) std::cout << "Excellent"; // ← This runs (99≥90) else if (score >= 80) std::cout << "Very Good"; else if (score >= 70) std::cout << "Good"; else if (score >= 60) std::cout << "OK"; else std::cout << "What?";
C++ Operators
Operators are symbols that perform operations on values. C++ has four key categories: Relational (compare values), Assignment (modify and assign), Logical (combine boolean conditions), and Bitwise (work on individual bits).
Compact if-else in One Line
The ternary operator condition ? valueIfTrue : valueIfFalse is a shorthand for a simple if-else that returns a value. It evaluates the condition and picks one of two expressions.
// Ternary syntax: condition ? expr_if_true : expr_if_false int x = 3, y = 5, max; max = (x > y) ? x : y; // If x>y use x, else use y std::cout << max; // Outputs: 5 // Equivalent if-else version (same result): if (x > y) { max = x; } else { max = y; } std::cout << max; // Outputs: 5
Multi-way Branching
A switch statement compares a variable against multiple exact values (cases). It's more readable than long if-else chains when testing a single variable against known constants. break stops execution from falling through to the next case. default runs if no case matches.
int num = 2; switch (num) { case 0: std::cout << "Zero"; break; // MUST break to stop falling through! case 1: std::cout << "One"; break; case 2: std::cout << "Two"; // ← This runs (num == 2) break; case 3: std::cout << "Three"; break; default: std::cout << "What?"; // Runs if no case matched break; }
Forgetting break causes fall-through — execution continues into the next case even if it doesn't match. This is sometimes intentional, but usually a bug.
Condition-First Loop
A while loop checks the condition before each iteration. If the condition is false from the start, the body never executes. Use when you don't know upfront how many times to loop.
int i = 0; while (i < 6) { std::cout << i++; // Print i, THEN increment (post-increment) } // Outputs: 012345
Execute-First Loop
A do-while loop executes the body first, then checks the condition. The body always runs at least once, even if the condition is immediately false. Useful for menus and input validation.
int i = 1; do { std::cout << i++; // Runs FIRST, condition checked after } while (i <= 5); // Note the semicolon here! // Outputs: 12345
Counter-Controlled Loop
The for loop packs init, condition, and increment into one line: for(init; condition; update). Perfect when you know exactly how many iterations you need. All three parts are optional.
// Standard for loop for (int i = 0; i < 10; i++) { std::cout << i << "\n"; } // Multiple variables — i counts up, j counts down simultaneously for (int i = 0, j = 2; i < 3; i++, j--) { std::cout << "i=" << i << ",j=" << j << ";"; } // Outputs: i=0,j=2;i=1,j=1;i=2,j=0;
Modern Loop Syntax
The range-based for loop iterates over every element of a container without needing an index. It's cleaner and less error-prone. std::for_each applies a function to each element — pairs nicely with lambda expressions.
// Loop over an initializer list directly for (int n : {1, 2, 3, 4, 5}) { std::cout << n << " "; } // Outputs: 1 2 3 4 5 // Loop over a string character by character std::string hello = "QuickRef.ME"; for (char c : hello) { std::cout << c << " "; } // Outputs: Q u i c k R e f . M E
#include <iostream> #include <array> #include <algorithm> // for std::for_each int main() { // Lambda: [](params){ body } — anonymous inline function auto print = [](int num) { std::cout << num << std::endl; }; std::array<int, 4> arr = {1, 2, 3, 4}; std::for_each(arr.begin(), arr.end(), print); // Applies print() to each element: prints 1, 2, 3, 4 return 0; }
Controlling Loop Flow
break immediately exits the entire loop. continue skips the rest of the current iteration and jumps to the next one. Both are valuable for flow control inside loops.
for (int i = 0; i < 10; i++) { if (i % 2 == 0) { continue; // Skip this iteration if i is even } std::cout << i; // Only odd numbers reach here } // Outputs: 13579
int password, times = 0; while (password != 1234) { if (times++ >= 3) { std::cout << "Locked!\n"; break; // Exit the while loop entirely } std::cout << "Password: "; std::cin >> password; }
Loops That Never Stop
An infinite loop runs forever unless broken out of with break or return. They are intentional in server programs, game loops, and event listeners. Accidentally creating one is a common bug — make sure your loop condition eventually becomes false.
// Method 1: while(true) — most readable while (true) { std::cout << "infinite loop"; } // Method 2: for(;;) — classic C style for (;;) { std::cout << "infinite loop"; } // Method 3: Condition that's always true (i++ always > 0) for (int i = 1; i > 0; i++) { std::cout << "infinite loop"; }
What is a Function?
A function is a named, reusable block of code. You declare it (tell the compiler it exists), define it (write what it does), and call it (execute it). Functions avoid code repetition and make programs modular and testable.
#include <iostream> void hello(); // DECLARATION (prototype) — tells compiler: hello exists // void = returns nothing, () = takes no arguments int main() { hello(); // CALL — execute the function return 0; } void hello() { // DEFINITION — the actual code std::cout << "Hello QuickRef!\n"; }
// return_type function_name(param_type param, ...) { ... } int add(int a, int b) { // Takes two ints, returns an int return a + b; // return sends value back to caller } int main() { std::cout << add(10, 20); // Prints: 30 }
Same Name, Different Parameters
Function overloading allows multiple functions to have the same name as long as their parameter lists differ (different number or types of parameters). The compiler automatically picks the right version based on the arguments you pass.
// Three versions of fun() — same name, different signatures void fun(std::string a, std::string b) { std::cout << a + " " + b; // Called when 2 strings passed } void fun(std::string a) { std::cout << a; // Called when 1 string passed } void fun(int a) { std::cout << a; // Called when int passed } // Compiler picks correct version automatically: fun("Hello", "World"); // → version 1 (2 strings) fun("Hi"); // → version 2 (1 string) fun(42); // → version 3 (int)
Standard Library Functions
C++ ships with a rich standard library. The <cmath> header provides math functions like sqrt(), pow(), abs(), sin(), cos(), etc. Include the appropriate header and use the function — no need to implement it yourself.
#include <iostream> #include <cmath> // Import the math library int main() { std::cout << std::sqrt(9); // √9 = 3 std::cout << std::pow(2, 10); // 2¹⁰ = 1024 std::cout << std::abs(-7); // |-7| = 7 std::cout << std::floor(3.9); // ⌊3.9⌋ = 3 std::cout << std::ceil(3.1); // ⌈3.1⌉ = 4 }
What is a Class?
A class is a blueprint for creating objects. It bundles data (attributes/member variables) and behavior (methods/member functions) together. Think of a class as the design, and an object as a real instance built from that design.
class MyClass { public: // Access specifier: public = accessible from outside int myNum; // Attribute: integer variable std::string myString; // Attribute: string variable }; // ← Don't forget the semicolon after the closing brace! // Create an object (instance) of MyClass MyClass myObj; // Set attribute values using dot notation myObj.myNum = 15; myObj.myString = "Hello"; // Read attribute values std::cout << myObj.myNum << std::endl; // 15 std::cout << myObj.myString << std::endl; // Hello
Automatic Initialization
A constructor is a special method with the same name as the class and no return type. It automatically runs when an object is created. Use it to set default values and initialize resources.
class MyClass { public: int myNum; std::string myString; MyClass() { // Constructor — same name as class, no return type myNum = 0; // Initialize to defaults myString = ""; } }; MyClass myObj; // Constructor runs automatically here std::cout << myObj.myNum; // 0 (set by constructor) std::cout << myObj.myString; // "" (set by constructor)
Automatic Cleanup
A destructor is called automatically when an object goes out of scope or is deleted. Its name is the class name prefixed with ~. Use it to free resources (memory, file handles, network connections) the object acquired during its lifetime.
class MyClass { public: int myNum; std::string myString; MyClass() { myNum = 0; myString = ""; } // Constructor ~MyClass() { // Destructor (note ~) std::cout << "Object destroyed." << std::endl; } }; MyClass myObj; // Constructor called here // ... use myObj ... // Destructor called automatically when myObj goes out of scope
Functions Inside a Class
Methods (member functions) are functions defined inside a class that can access the class's attributes. Call them with dot notation: obj.method().
class MyClass { public: int myNum; std::string myString; void myMethod() { // Method defined inside the class std::cout << "Hello World!" << std::endl; } }; MyClass myObj; myObj.myMethod(); // Call the method → prints: Hello World!
Controlling Access to Class Members
Access modifiers control who can read or write class members. public: anyone can access. private: only the class itself. protected: the class and its derived (child) classes. This is the foundation of encapsulation — hiding internal details.
class MyClass { public: int x; // Accessible from anywhere private: int y; // Only accessible within this class protected: int z; // Accessible within this class and subclasses }; MyClass myObj; myObj.x = 25; // OK — public myObj.y = 50; // ERROR — private, not accessible outside myObj.z = 75; // ERROR — protected, not accessible outside
Controlled Access to Private Data
Getters and setters are public methods that provide controlled access to private attributes. Setters can validate input before setting. Getters return the value without exposing the raw variable.
class MyClass { private: int myNum; // Private: can't access directly public: void setMyNum(int num) { // Setter: validates then stores myNum = num; } int getMyNum() { // Getter: returns private value return myNum; } }; MyClass myObj; myObj.setMyNum(15); // Set via setter (indirect write) std::cout << myObj.getMyNum(); // Get via getter → 15
Reusing Code via Inheritance
Inheritance lets a derived class (child) inherit attributes and methods from a base class (parent). Use class Derived : public Base. The child gets all public and protected members of the parent, and can add its own.
class Vehicle { // BASE class (parent) public: std::string brand = "Ford"; void honk() { std::cout << "Tuut, tuut!" << std::endl; } }; class Car : public Vehicle { // DERIVED class (child) — inherits Vehicle public: std::string model = "Mustang"; // Car's own attribute }; Car myCar; myCar.honk(); // Inherited from Vehicle → "Tuut, tuut!" std::cout << myCar.brand + " " + myCar.model; // Ford Mustang
Including Headers
The #include directive tells the preprocessor to paste the contents of another file into your source file before compilation. Angle brackets <> are used for standard library headers; quotes "" are used for your own local headers.
#include <iostream> // Standard library header (system path) #include "myfile.h" // Your own header (current directory first)
Preprocessor Constants & Macros
#define creates a macro — the preprocessor does a text substitution everywhere the macro name appears. #undef removes a previously defined macro. Unlike const, defines have no type checking.
#defineFOO // Define FOO as a flag (no value) #defineFOO "hello" // Define FOO as the text "hello" #undefFOO // Remove the FOO macro
Compile Different Code Conditionally
Preprocessor conditionals let you include or exclude blocks of code at compile time — before the compiler even sees them. Commonly used for debug-only code, platform-specific code, and feature flags.
// #ifdef: "if DEBUG macro is defined" #ifdefDEBUG console.log('hi'); // Only compiled in debug build #elif definedVERBOSE // ... compiled if VERBOSE defined but not DEBUG #else // ... compiled if neither DEBUG nor VERBOSE #endif // Always end with #endif
Compile-Time Messages
#error halts compilation and displays a message — useful for catching unsupported configurations. #warning shows a warning but continues compiling. These run before your code ever executes.
#ifVERSION == 2.0 #errorUnsupported // STOPS compilation with this error message #warningNot really supported // Shows warning but continues #endif
Advanced Macro Tricks
Macros can take parameters like functions. The ## operator concatenates tokens (joins two identifiers together). The # operator stringifies — converts a token into a string literal. Both happen at compile time.
// Parameterized macro: like a function but text-substituted #define DEG(x) ((x) * 57.29) // Converts radians to degrees // Token concatenation with ## #define DST(name) name##_s name##_t DST(object); // Expands to: object_s object_t; // Stringification with # — wraps in double quotes #define STR(name) #name char * a = STR(object); // Expands to: char * a = "object"; // __FILE__ and __LINE__ — built-in macros #define LOG(msg) console.log(__FILE__, __LINE__, msg) // Expands to: console.log("file.txt", 3, "hey")
Complete Directive Reference
Here is every preprocessor directive available in standard C++. All of them start with # and are processed before compilation begins.
| Directive | Purpose |
|---|---|
| #if | Conditional if expression is true |
| #elif | Else-if for preprocessor conditionals |
| #else | Else branch for preprocessor |
| #endif | Ends an #if / #ifdef / #ifndef block |
| #ifdef | If macro IS defined |
| #ifndef | If macro is NOT defined (include guards) |
| #define | Define a macro or constant |
| #undef | Undefine (remove) a macro |
| #include | Include a header file |
| #line | Set line number for error reporting |
| #error | Emit error and stop compilation |
| #pragma | Compiler-specific directives (e.g., once) |
| #defined | Operator: check if macro is defined |
| export / import / module | C++20 module system |