What is C++?
Introduction
C++ is a compiled, general-purpose programming language created by Bjarne Stroustrup in 1985 as an extension of the C language. It adds object-orientation, generic programming, low-level memory control and a massive standard library.
C++ is one of the most widely used programming languages in the world and is known for its speed, efficiency, and flexibility.
It is commonly used in:
- Game development
- Operating systems
- Embedded systems
- Desktop applications
- Compilers and browsers
- High-performance software
History of C++
C++ was developed by Bjarne Stroustrup at Bell Labs.
- 1979 – Development of “C with Classes” began.
- 1985 – The language was officially named C++.
- 1998 – First ISO standard (C++98).
- 2011 – Modern C++ introduced with C++11.
- 2020 – C++20 added modules, ranges, concepts, and more.
Why Learn C++?
- Excellent performance and speed
- Strong understanding of memory and computer systems
- Widely used in industry and competitive programming
- Powerful object-oriented programming support
- Huge ecosystem and standard library
- Foundation for learning advanced programming concepts
Key Characteristics
- Compiled – source code is turned into native machine code.
- Statically typed – type checking is done at compile time.
- Supports multiple paradigms – procedural, OOP, generic, functional.
- Performance-critical – fine-grained control over memory and resources.
Understanding Compiled Languages
C++ programs must be compiled before they can run.
The compilation process converts human-readable source code into machine code that the computer can execute.
Compilation process
- Write source code (
.cppfiles) - Compile the code using a compiler
- Generate an executable program
- Run the executable
Structure of a C++ Program
Most C++ programs contain:
- Header files
- The
main()function - Statements and expressions
- Output/input operations
Hello World – Your First C++ Program
Save the following as hello.cpp, compile it, and run it.
#include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; }
Understanding the Code
| Code | Purpose |
|---|---|
#include <iostream> |
Imports the standard input/output library |
main() |
The starting point of every C++ program |
std::cout |
Prints output to the console |
return 0; |
Indicates successful program execution |
main() function.
Compiling and Running the Program
Compile and run using GCC:
g++ -std=c++20 hello.cpp -o hello
./hello
The program output will be:
Hello, World!
Choosing a Toolchain
For learning you can pick any of these:
- GCC – the GNU Compiler Collection (Linux/macOS/WSL).
- Clang – LLVM-based compiler with excellent error messages.
- MSVC – Microsoft Visual C++ compiler for Windows.
- Online IDEs – useful for learning without installation.
Popular Online Compilers
- Compiler Explorer
- OnlineGDB
- Replit
- Programiz Compiler
Verifying Installation
g++ --version
clang++ --version
cl
VS Code with the C/C++ extension or JetBrains CLion provide syntax highlighting, debugging, IntelliSense, and project management tools.
Compiling with Flags
Compiler flags allow you to control warnings, optimizations, debugging, and language standards.
Common useful flags
-Wall -Wextra -Wpedantic– enable most warnings.-O2– optimisation level.-std=c++20– select the language standard.-g– include debug symbols for debugging tools.
g++ -Wall -Wextra -std=c++20 -O2 hello.cpp -o hello
Comments in C++
Comments are notes written inside the source code for explanation and readability.
// Single-line comment /* Multi-line comment */ int main() { return 0; }
C++ File Extensions
| Extension | Description |
|---|---|
.cpp |
C++ source file |
.h |
Header file |
.hpp |
C++ header file |
.exe |
Executable file (Windows) |
⚙️ Try It Yourself - C++ Compiler
Practice C++ programming right here in your browser! Modify the code below and click "Compile & Run" to see the results instantly.
💡 Practice Tips:
- Try changing the output message in the Hello World program
- Experiment with different data types like
int,double, andstring - Add variables and practice arithmetic operations
- Try creating multiple output statements using
std::cout - Use Ctrl+Enter to quickly compile and run your code
Real-world Applications of C++
- Game engines like Unreal Engine
- Browsers such as Chrome and Firefox
- Databases like MySQL
- Operating systems and drivers
- Financial and trading systems
- Embedded and robotics systems
Install a C++ compiler (GCC, Clang or MSVC), write the Hello World program, compile it, and verify the output.
Modify the Hello World program so it prints:
Hello, C++!
Welcome to programming.
Create a simple project with two source files:
main.cpputils.cpp
Compile them together into a single executable program.
Setting up the Compiler
Why Do You Need a Compiler?
C++ is a compiled programming language. This means the source code you write must be converted into machine code before the computer can execute it.
A compiler translates your C++ code into an executable program.
Popular C++ Compilers
| Compiler | Platform | Description |
|---|---|---|
| GCC | Linux / Windows / macOS | Open-source GNU Compiler Collection |
| Clang | Linux / macOS / Windows | LLVM-based compiler with excellent diagnostics |
| MSVC | Windows | Microsoft Visual C++ compiler included with Visual Studio |
Choosing an IDE or Code Editor
You can write C++ code in any text editor, but modern IDEs provide features like syntax highlighting, debugging, and auto-completion.
Popular options
- VS Code – lightweight and beginner-friendly
- Visual Studio – powerful IDE for Windows
- CLion – professional JetBrains IDE
- Code::Blocks – simple IDE for beginners
- Dev-C++ – lightweight Windows IDE
VS Code with the C/C++ extension and GCC compiler is one of the most popular setups for beginners.
Installing GCC on Windows
On Windows, GCC is commonly installed using MinGW-w64.
Installation steps
- Download MinGW-w64 installer
- Install the compiler
- Add the compiler path to the system PATH variable
- Restart the terminal
Verify installation
g++ --version
If installed correctly, the compiler version will appear.
Installing GCC on Linux
Most Linux distributions provide GCC through package managers.
Ubuntu / Debian
sudo apt update
sudo apt install g++
Fedora
sudo dnf install gcc-c++
Arch Linux
sudo pacman -S gcc
Installing Clang on macOS
macOS users can install Clang using Xcode Command Line Tools.
xcode-select --install
Verify installation:
clang++ --version
Installing Visual Studio (MSVC)
Windows users can also use Microsoft's official C++ compiler.
Steps
- Download Visual Studio Community Edition
- Select the “Desktop development with C++” workload
- Complete installation
MSVC includes:
- C++ compiler
- Debugger
- Build tools
- Windows SDK
Creating Your First Project
Create a folder called cpp-project and inside it create a file named
main.cpp.
#include <iostream> int main() { std::cout << "Compiler setup successful!" << std::endl; return 0; }
Compiling the Program
Open the terminal inside your project folder and run:
g++ main.cpp -o main
This command:
- Compiles
main.cpp - Creates an executable named
main
Running the Program
Linux / macOS
./main
Windows
main.exe
Expected output:
Compiler setup successful!
Understanding Compiler Errors
Compilers display errors when the code contains mistakes.
Example error
#include <iostream> int main() { std::cout << "Hello" return 0; }
This program is missing a semicolon (;) after the output statement.
Useful Compiler Flags
| Flag | Purpose |
|---|---|
-Wall |
Enable common warnings |
-Wextra |
Enable extra warnings |
-std=c++20 |
Use C++20 standard |
-g |
Generate debug information |
-O2 |
Enable optimization |
Compiling with Flags Example
g++ -Wall -Wextra -std=c++20 main.cpp -o main
Online Compilers
If you do not want to install software immediately, you can use online compilers.
- OnlineGDB
- Replit
- Compiler Explorer
- Programiz
Online compilers are useful for practice and quick testing without local installation.
⚙️ Try It Yourself - Compiler Test
Modify the program below and test your compiler setup directly in the browser.
💡 Practice Tips:
- Change the output text
- Add multiple
std::coutstatements - Experiment with syntax errors and observe compiler messages
- Practice compiling with different flags
- Try creating multiple source files
Install a C++ compiler on your operating system and verify the installation using
g++ --version or clang++ --version.
Create a project folder with a main.cpp file, compile it manually using
terminal commands, and run the executable.
Compile a program using:
g++ -Wall -Wextra -std=c++20 main.cpp -o main
Observe any warnings generated by the compiler.
Variables & Data Types
Introduction to Variables
Variables are named containers used to store data in memory. Every program uses variables to keep track of values such as numbers, characters, prices, marks, temperatures, or user input.
In C++, every variable must have:
- A type — defines what kind of data can be stored
- A name — used to access the value later
- An optional initial value
Example:
int age = 20; double salary = 45000.50; char grade = 'A';
Here:
agestores an integer valuesalarystores a decimal valuegradestores a single character
Fundamental Types
C++ provides several built-in primitive data types. Choosing the correct type helps optimise memory usage and improves program performance.
| Type | Size (typical) | Example |
|---|---|---|
bool |
1 byte | bool ok = true; |
char |
1 byte | char c = 'A'; |
int |
4 bytes | int i = 42; |
long / long long |
8 bytes | long long big = 123456789LL; |
float |
4 bytes | float f = 3.14f; |
double |
8 bytes | double d = 2.71828; |
Understanding Integer Types
Integer types are used for storing whole numbers without decimal points. Different integer types exist because programs may need to store very small or very large values.
short→ smaller range, uses less memoryint→ most commonly used integer typelongandlong long→ used for large numeric values
Example use cases:
| Scenario | Recommended Type |
|---|---|
| Age of a student | int |
| Population of a country | long long |
| Game score | int |
Floating-Point Types
Floating-point types are used for decimal numbers.
float→ lower precision, uses less memorydouble→ higher precision, commonly preferred
Examples:
float temperature = 36.5f; double pi = 3.1415926535;
The suffix f tells the compiler that the value is a float.
Character and Boolean Types
The char type stores a single character enclosed in single quotes.
char letter = 'G'; char symbol = '#';
The bool type stores logical values:
bool isLoggedIn = true; bool gameOver = false;
Signed vs Unsigned
Appending u makes an integer type unsigned (unsigned int or
uint32_t). Unsigned types cannot represent negative values, which can be useful for
bit-wise operations.
Signed integers can store both positive and negative numbers:
int temperature = -10; unsigned int score = 100;
If you try to store a negative number in an unsigned variable, the result may not be what you expect due to overflow.
Type Modifiers
Keywords that affect size and sign:
shortlong,long longsigned(default forint)unsigned
These modifiers allow programmers to control memory usage and numeric range more precisely.
Variable Naming Rules
C++ variable names must follow certain rules:
- Names can contain letters, digits, and underscores
- Names cannot begin with a number
- Spaces are not allowed
- C++ keywords cannot be used as variable names
- Variable names are case-sensitive
| Valid Names | Invalid Names |
|---|---|
totalMarks |
2marks |
user_name |
user name |
price1 |
float |
Variable Declaration & Initialisation
Declaration tells the compiler about the variable type and name. Initialisation assigns the first value to the variable.
int count = 0; // initialise to zero double price = 19.99; char grade = 'A'; bool isReady = true; unsigned int mask = 0xFF; long bigNum = 123456789L;
Multiple Variable Declarations
You can declare multiple variables of the same type in one line:
int x = 10, y = 20, z = 30;
However, for readability, many programmers prefer declaring one variable per line.
The sizeof Operator
The sizeof operator returns the number of bytes occupied by a type or variable in memory.
cout << sizeof(int); cout << sizeof(double);
This is useful when working with memory management, arrays, and system-level programming.
Write a program that declares one variable of each primitive type, prints their size using
sizeof,
and then swaps the values of two integer variables without using a temporary variable.
Create a simple student information program using variables for:
- Student name initial
- Age
- Marks
- Percentage
- Pass status
Print all values using cout.
Control Flow
Introduction to Control Flow
Control flow determines the order in which program instructions are executed. By default, C++ executes code line by line from top to bottom. Control statements allow programs to make decisions, repeat tasks, and react differently depending on conditions.
Control flow is mainly divided into:
- Decision-making statements →
if,else,switch - Loops →
for,while,do-while - Jump statements →
break,continue,return
Boolean Conditions
Control flow statements work using conditions that evaluate to either true or
false.
Examples of comparison operators:
| Operator | Meaning | Example |
|---|---|---|
== |
Equal to | a == b |
!= |
Not equal to | a != b |
> |
Greater than | x > 10 |
< |
Less than | x < 5 |
>= |
Greater than or equal | marks >= 40 |
<= |
Less than or equal | age <= 18 |
If / else
The if statement executes a block of code only if the condition is true.
int score = 85; if (score >= 90) { std::cout << "Grade A" << std::endl; } else if (score >= 80) { std::cout << "Grade B" << std::endl; } else { std::cout << "Needs improvement" << std::endl; }
In this example:
- If score is 90 or above → Grade A
- If score is between 80 and 89 → Grade B
- Otherwise → Needs improvement
Nested If Statements
An if statement can exist inside another if statement. This is called nesting.
int age = 20; bool hasID = true; if (age >= 18) { if (hasID) { std::cout << "Entry allowed"; } }
Logical Operators
Logical operators combine multiple conditions.
| Operator | Meaning | Example |
|---|---|---|
&& |
Logical AND | age > 18 && hasID |
|| |
Logical OR | marks > 40 || sportsQuota |
! |
Logical NOT | !isGameOver |
Switch Statement
The switch statement is used when a variable can have multiple possible fixed values.
char choice = 'b'; switch (choice) { case 'a': case 'b': case 'c': std::cout << "Option a–c selected" << std::endl; break; default: std::cout << "Other option" << std::endl; }
The break statement prevents execution from continuing into the next case.
Fallthrough in Switch
If break is omitted, execution continues into the next case. This behaviour is called
fallthrough.
int day = 1; switch (day) { case 1: std::cout << "Monday"; case 2: std::cout << " Tuesday"; }
Output:
Monday Tuesday
Loops
Loops repeat a block of code multiple times until a condition becomes false.
for (init; condition; increment)while (condition)do { … } while (condition);- Range-based
for (auto &v : container)
For Loop
The for loop is commonly used when the number of iterations is known.
int sum = 0; for (int i = 1; i <= 5; ++i) { sum += i; } std::cout << "Sum = " << sum << std::endl;
This loop runs 5 times and calculates the total sum from 1 to 5.
While Loop
The while loop runs as long as the condition remains true.
int i = 1; while (i <= 5) { std::cout << i << " "; ++i; }
Do-While Loop
The do-while loop executes the code block at least once before checking the condition.
int num = 1; do { std::cout << num << " "; ++num; } while (num <= 5);
Range-Based For Loop
C++11 introduced range-based loops for easier iteration through containers such as arrays and vectors.
int numbers[] = {1, 2, 3, 4}; for (int n : numbers) { std::cout << n << " "; }
Break and Continue
The break statement immediately terminates a loop, while continue skips the
current iteration.
for (int i = 1; i <= 10; ++i) { if (i == 5) { continue; } if (i == 8) { break; } std::cout << i << " "; }
Infinite Loops
An infinite loop never stops unless interrupted manually or with a control statement.
while (true) { // runs forever }
Infinite loops are useful in game engines, servers, and event-driven applications.
Write a program that prints the first 20 Fibonacci numbers using a while loop.
Create a number guessing program where the user keeps entering values until the correct number is guessed.
Print the multiplication table of a number entered by the user using a for loop.
Functions
Introduction to Functions
Functions are reusable blocks of code designed to perform a specific task. Instead of writing the same logic multiple times, programmers create functions and call them whenever needed.
Functions improve:
- Code reusability
- Readability
- Program organisation
- Debugging and maintenance
Examples of common functions:
- Calculating totals
- Checking passwords
- Printing menus
- Sorting data
- Performing mathematical operations
Function Syntax
General form: return_type name(parameter_list) { body }
A function typically contains:
| Part | Description |
|---|---|
return_type |
Type of value returned by the function |
name |
Name used to call the function |
parameter_list |
Input values accepted by the function |
body |
Code executed when the function is called |
int add(int a, int b) { return a + b; } double average(const std::vector<int>& values) { int sum = 0; for (int v : values) sum += v; return static_cast<double>(sum) / values.size(); }
Calling Functions
Functions only execute when they are called.
int result = add(5, 3); std::cout << result;
Here, the values 5 and 3 are passed to the function as arguments.
Parameters vs Arguments
These two terms are often confused by beginners.
| Term | Meaning |
|---|---|
| Parameter | Variable defined in the function declaration |
| Argument | Actual value passed to the function |
int multiply(int a, int b) { return a * b; } multiply(4, 6);
In this example:
aandbare parameters4and6are arguments
Void Functions
A function that does not return any value uses the void keyword.
void greet() { std::cout << "Welcome to C++"; }
Return Statement
The return statement sends a value back to the caller.
double square(double x) { return x * x; }
Once return executes, the function immediately ends.
Function Declaration and Definition
Functions can be declared before main() and defined later. This is called a function
prototype.
int sum(int, int); int main() { sum(2, 3); } int sum(int a, int b) { return a + b; }
Function Overloading
Function overloading allows multiple functions to have the same name but different parameter types or counts.
int max(int a, int b) { return (a > b) ? a : b; } double max(double a, double b) { return (a > b) ? a : b; }
The compiler selects the correct function based on the argument types.
Default Arguments
Default arguments supply a value that is used when the caller omits that argument.
void greet(const std::string& name = "World") { std::cout << "Hello, " << name << "!\n"; }
Calling greet() prints:
Hello, World!
Pass by Value
By default, C++ passes arguments by value, meaning a copy of the variable is sent to the function.
void changeValue(int x) { x = 100; } int num = 10; changeValue(num);
The original value of num remains unchanged because only a copy was modified.
Pass by Reference
References allow functions to modify the original variable directly.
void increment(int& value) { ++value; }
Reference parameters are efficient for large objects because they avoid unnecessary copying.
Recursive Functions
A recursive function calls itself repeatedly until a stopping condition is reached.
int factorial(int n) { if (n == 0) { return 1; } return n * factorial(n - 1); }
Recursive functions must always include a base condition to prevent infinite recursion.
Inline Functions
The inline keyword suggests that the compiler replace the function call with the actual code
to reduce overhead.
inline int cube(int x) { return x * x * x; }
Function Best Practices
- Keep functions small and focused
- Use meaningful function names
- Avoid duplicate logic
- Use comments where necessary
- Prefer passing large objects by reference
Implement a factorial function (recursive) for int
and an overloaded version that works with unsigned long long.
Create functions for:
- Finding the square of a number
- Checking whether a number is even or odd
- Finding the largest of three numbers
Create a menu-driven calculator using functions for addition, subtraction, multiplication, and division.
Object-Oriented Programming
Introduction to Object-Oriented Programming
Object-Oriented Programming (OOP) is a programming paradigm that organizes code into objects. An object combines both data and the functions that operate on that data. OOP helps developers build applications that are easier to maintain, reuse, and scale.
Modern software systems such as games, banking apps, operating systems, and web applications heavily rely on OOP concepts. In C++, OOP provides powerful tools for structuring large programs into reusable components.
The four major principles of OOP are:
- Encapsulation → Protecting data inside classes
- Abstraction → Hiding implementation details
- Inheritance → Reusing existing classes
- Polymorphism → One interface, multiple behaviors
What is a Class?
A class acts as a blueprint for creating objects. It defines what data an object stores and what actions it can perform.
For example, a Person class may store a person's name and age, while also
providing functions such as displaying information or updating the age.
Class Declaration
class Person { private: std::string name; int age; public: Person(const std::string& name, int age) : name(name), age(age) {} const std::string& getName() const { return name; } int getAge() const { return age; } void birthday() { ++age; } };
Understanding Access Specifiers
Access specifiers control which parts of a class can be accessed from outside the class.
private→ Accessible only inside the classpublic→ Accessible from anywhereprotected→ Accessible in derived classes
private to protect them from accidental modification.
This practice is known as encapsulation.
Constructors
A constructor is a special member function automatically called when an object is created. Constructors are mainly used to initialize object data.
Person p("Alice", 25); std::cout << p.getName() << " is " << p.getAge() << " years old" << std::endl;
Member Functions
Member functions define the behavior of a class. In the Person class,
functions like getName(), getAge(), and
birthday() operate on the object's internal data.
The keyword const after a function means that the function does not modify
the object.
Inheritance
Inheritance allows one class to acquire properties and behaviors from another class. This promotes code reuse and creates logical relationships between classes.
A derived class inherits members from a base class. In the following example,
Employee inherits from Person.
class Employee : public Person { private: std::string department; public: Employee(const std::string& name, int age, const std::string& dept) : Person(name, age), department(dept) {} const std::string& getDept() const { return department; } };
Types of Inheritance
- Single Inheritance → One derived class inherits from one base class
- Multilevel Inheritance → A derived class becomes a base class for another class
- Multiple Inheritance → One class inherits from multiple base classes
- Hierarchical Inheritance → Multiple classes inherit from one base class
Polymorphism
Polymorphism allows the same function name to behave differently depending on the object. This is one of the most powerful features of OOP.
There are two major types of polymorphism in C++:
- Compile-time Polymorphism → Function overloading and operator overloading
- Run-time Polymorphism → Virtual functions and method overriding
Polymorphism (virtual functions)
class Shape { public: virtual double area() const = 0; virtual ~Shape() {} }; class Circle : public Shape { double radius; public: Circle(double r) : radius(r) {} double area() const override { return 3.14159 * radius * radius; } }; int main() { std::vector<std::unique_ptr<Shape>> shapes; shapes.push_back(std::make_unique<Circle>(2.5)); for (const auto& s : shapes) { std::cout << "Area = " << s->area() << std::endl; } }
Abstract Classes
The Shape class in the previous example is an abstract class because it contains
a pure virtual function:
virtual double area() const = 0;
Abstract classes cannot be instantiated directly. They serve as templates for derived classes.
Benefits of OOP
- Improves code organization
- Encourages code reuse
- Makes applications easier to maintain
- Helps manage large projects efficiently
- Provides better security through encapsulation
Common Beginner Mistakes
- Forgetting to make functions virtual when overriding behavior
- Using public data members everywhere
- Not understanding constructor initialization lists
- Confusing objects with classes
- Forgetting to use
overridein derived classes
Model a simple library system: a base class Item with id and
title,
then derive Book and Magazine. Override a virtual printInfo()
method for each derived class.
Create a base class Vehicle with a virtual method
startEngine(). Derive classes such as
Car, Bike, and Truck,
then override the method in each derived class.
🎯 Related Skills
Standard Library Containers
Why use containers?
They provide generic, memory‑safe collections with well‑defined complexity guarantees.
Sequence containers
std::vector<T>– dynamic array, contiguous memory.std::list<T>– doubly‑linked list.std::deque<T>– double‑ended queue.std::forward_list<T>– singly‑linked list (C++11+).
Associative containers
std::set<T>– ordered unique keys.std::map<Key, Value>– ordered key/value pairs.std::unordered_set<T>– hash‑based set (C++11+).std::unordered_map<Key, Value>– hash table.
Basic usage examples
#include <vector> #include <iostream> int main() { std::vector<int> numbers = { 1, 2, 3 }; numbers.push_back(4); for (int n : numbers) { std::cout << n << ' '; } std::cout << std::endl; }
#include <map> #include <string> #include <iostream> int main() { std::map<std::string, int> scores; scores["Alice"] = 95; scores["Bob"] = 87; for (const auto& kv : scores) { std::cout << kv.first << ": " << kv.second << std::endl; } }
Create a program that reads a list of words from the console, stores them in a
std::unordered_set, and then reports how many unique words were entered.
Templates & Generics
What are Templates?
Templates are one of the most powerful features in C++. They allow you to write code once and reuse it
with different data types. Instead of creating separate functions for int,
float, or std::string, templates let the compiler generate the correct code
automatically.
This concept is called generic programming. The goal is to write flexible and reusable code while maintaining high performance and type safety.
Why Templates Matter
- Reduce code duplication
- Create reusable and scalable components
- Provide strong type safety
- Enable high-performance generic programming
- Power the entire Standard Template Library (STL)
Function templates
Function templates allow functions to work with multiple data types without rewriting logic.
template<typename T> void swapValues(T& a, T& b) { T tmp = a; a = b; b = tmp; } int main() { int x = 5, y = 10; swapValues(x, y); // works for ints std::string s1 = "foo", s2 = "bar"; swapValues(s1, s2); // works for std::string }
The keyword typename T declares a placeholder type called T. Whenever the
function is called, the compiler automatically replaces T with the actual type being used.
KeyType, ValueType, or Iterator.
How Template Type Deduction Works
Most of the time, the compiler can automatically determine template types based on the function arguments. This process is called template type deduction.
template<typename T> T square(T value) { return value * value; } int result = square(5); double pi = square(3.14);
Here, the compiler automatically detects whether T should become
int or double.
Multiple Template Parameters
Templates can accept multiple types simultaneously. This is useful when working with pairs, maps, or generic utility classes.
template<typename T1, typename T2> class Pair { public: T1 first; T2 second; Pair(T1 a, T2 b) : first(a), second(b) {} };
This allows a single class to hold two completely different data types.
Class templates
Class templates allow entire classes to become generic. Many STL containers such as
std::vector, std::array, and std::map are implemented using
templates.
template<typename T, size_t N> class SimpleVector { T data[N]; public: constexpr size_t size() const { return N; } T& operator[](size_t i) { return data[i]; } const T& operator[](size_t i) const { return data[i]; } };
This template accepts two parameters:
T→ the type of elements storedN→ the fixed size of the container
Using Class Templates
Once defined, templates can be instantiated with different types.
SimpleVector<int, 5> numbers; SimpleVector<double, 3> values; numbers[0] = 42; values[0] = 3.14;
The compiler generates separate versions of the class for each type combination.
Non-Type Template Parameters
Templates are not limited to data types. They can also accept compile-time constant values such as integers or sizes.
template<typename T, int Size> void printArray(T (&arr)[Size]) { for(int i = 0; i < Size; ++i) std::cout << arr[i] << " "; }
Here, Size becomes part of the template definition itself.
Template specialization
Provide a custom implementation for a particular type.
template<>
struct std::hash<MyType> {
size_t operator()(const MyType& obj) const noexcept { … }
};
Template specialization is useful when a specific type requires optimized or unique behavior.
Full vs Partial Specialization
C++ supports different forms of specialization:
- Full specialization: Replaces the implementation for one exact type.
- Partial specialization: Replaces only part of a template pattern.
template<typename T> class Printer { public: void print() { std::cout << "Generic printer"; } }; template<> class Printer<bool> { public: void print() { std::cout << "Boolean printer"; } };
Templates in the STL
The Standard Template Library heavily depends on templates. Common containers are all generic:
std::vector<T>std::map<Key, Value>std::set<T>std::array<T, N>
This allows the STL to work with virtually any user-defined or built-in type.
Common Template Errors
- Forgetting to place template definitions in header files
- Using unsupported operations for a generic type
- Creating extremely complex compiler error messages
- Mixing incompatible template parameter types
Write a generic Stack<T> class with push, pop and
top operations.
Then specialise it for bool so that it stores bits compactly (you can use
std::vector<bool> for simplicity).
Create a function template called maxValue that returns the largest of two values.
Test it using integers, floating-point numbers, and strings.
Create a class template called Pair<T1, T2> that stores two values of different
types and displays them using a member function.
Modern C++ (C++11 +)
Introduction to Modern C++
C++11 introduced one of the largest upgrades in the history of the language. Since then, newer standards like C++14, C++17, and C++20 have continued improving performance, readability, and developer productivity.
Modern C++ focuses on safer memory management, cleaner syntax, stronger type safety, and high-performance abstractions without sacrificing low-level control.
Auto type deduction
The auto keyword allows the compiler to automatically determine a variable's type from its
initializer.
auto x = 42; // int
auto y = 3.14; // double
auto z = std::vector<int>{1,2,3}; // deduced type
This reduces verbosity and makes code easier to read, especially when working with complex template types.
auto when the type is obvious or excessively long, but avoid overusing it when it makes
code harder to understand.
Type Inference with References
auto can also work with references and const qualifiers.
int value = 10; auto a = value; // copy auto& b = value; // reference b = 20; std::cout << value; // prints 20
Using references with auto prevents unnecessary copying and improves performance.
Range-based for loop
Range-based loops simplify iteration over containers and arrays.
std::vector<int> v = {1,2,3};
for (auto& n : v) {
n *= 2;
}
This loop automatically iterates through every element in the container.
auto n→ creates a copy of each elementauto& n→ references the original elementconst auto& n→ prevents modification
Initializer Lists
Modern C++ introduced uniform initialization syntax using curly braces.
int x{10}; std::vector<int> numbers{1, 2, 3, 4};
Brace initialization helps avoid certain narrowing conversion bugs and creates more consistent syntax across the language.
Smart pointers
One of the biggest improvements in modern C++ is automatic memory management using smart pointers.
#include <memory> #include <iostream> struct Node { int value; std::unique_ptr<Node> next; Node(int v) : value(v) {} }; int main() { auto head = std::make_unique<Node>(1); head->next = std::make_unique<Node>(2); std::cout << head->value << ", " << head->next->value << std::endl; }
std::unique_ptr automatically deletes the object when it goes out of scope, preventing
memory leaks.
Types of Smart Pointers
std::unique_ptr→ exclusive ownershipstd::shared_ptr→ shared ownership using reference countingstd::weak_ptr→ non-owning reference used with shared pointers
Move semantics
Move semantics allow resources to be transferred instead of copied, dramatically improving performance when working with large objects.
std::vector<int> data = {1,2,3,4};
std::vector<int> movedData = std::move(data);
After the move operation, ownership of the internal resources transfers to
movedData.
Lambda expressions
Lambdas are anonymous inline functions introduced in C++11. They are commonly used with STL algorithms.
auto add = [](int a, int b) {
return a + b;
};
std::cout << add(3,4) << '\n';
The syntax may look unusual at first:
[]→ capture list(int a, int b)→ parameters{ }→ function body
Lambda Capture
Lambdas can capture variables from the surrounding scope.
int multiplier = 5; auto multiply = [multiplier](int x) { return x * multiplier; }; std::cout << multiply(3);
Capturing variables allows lambdas to behave similarly to lightweight function objects.
constexpr
constexpr enables compile-time constants and functions.
constexpr int square(int x) {
return x*x;
}
static_assert(square(5) == 25);
If the compiler can evaluate the expression during compilation, it will do so automatically.
nullptr
Modern C++ introduced nullptr to replace the older NULL macro.
int* ptr = nullptr; if(ptr == nullptr) { std::cout << "Pointer is null"; }
nullptr provides better type safety compared to the older integer-based NULL value.
Strongly Typed Enums
C++11 introduced scoped enumerations using enum class.
enum class Color { Red, Green, Blue }; Color c = Color::Red;
Unlike traditional enums, scoped enums prevent accidental implicit conversions.
Using STL Algorithms with Lambdas
Modern C++ works extremely well with STL algorithms.
std::vector<int> values = {5,2,8,1}; std::sort(values.begin(), values.end(), [](int a, int b) { return a > b; });
This sorts the vector in descending order using a lambda comparator.
Benefits of Modern C++
- Cleaner and shorter syntax
- Safer memory management
- Improved performance through move semantics
- Powerful generic programming support
- Better compatibility with STL algorithms
Common Beginner Mistakes
- Overusing
autowhen types become unclear - Confusing copies with references in range-based loops
- Incorrect lambda capture usage
- Using raw pointers instead of smart pointers
- Forgetting that moved-from objects should not be relied upon
Write a program that uses a std::vector<int> of random numbers, then sorts them
with std::sort using a lambda comparator that orders them in descending order.
Create a lambda function that multiplies two integers and stores it in a variable using
auto. Call the lambda with different values.
Create a std::unique_ptr to a dynamically allocated object and demonstrate automatic
cleanup when it goes out of scope.
Exception Handling
try / catch / finally
C++ has try, catch and throw. The finally pattern is
emulated with RAII.
#include <stdexcept> #include <iostream> double divide(double a, double b) { if (b == 0.0) throw std::runtime_error("Division by zero"); return a / b; } int main() { try { std::cout << divide(10, 2) << std::endl; std::cout << divide(5, 0) << std::endl; } catch (const std::runtime_error& e) { std::cerr << "Error: " << e.what() << std::endl; } }
Custom exception types
class InvalidAgeException : public std::runtime_error {
public:
InvalidAgeException(const std::string& msg) : std::runtime_error(msg) {}
};
Implement a BankAccount class with deposit, withdraw and
balance.
Throw a custom InsufficientFundsException when a withdrawal would result in a negative
balance and handle it in main.
Concurrency
Thread basics
C++11 introduced std::thread, std::mutex and related primitives.
#include <thread> #include <iostream> void worker(int id) { for (int i = 0; i < 5; ++i) { std::cout << "Thread " << id << ": " << i << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(200)); } } int main() { std::thread t1(worker, 1); std::thread t2(worker, 2); t1.join(); t2.join(); }
Mutex & lock_guard
std::mutex m;
{
std::lock_guard<std::mutex> lock(m);
// critical section
}
Thread pool (high‑level)
Standard C++ does not ship a thread pool, but you can build one with std::async or use a
third‑party library.
#include <future> #include <iostream> int fib(int n) { if (n <= 1) return n; return fib(n-1) + fib(n-2); } int main() { auto fut = std::async(std::launch::async, fib, 30); std::cout << "Computing…" << std::endl; int result = fut.get(); // blocks until fib finishes std::cout << "Result = " << result << std::endl; }
Write a producer‑consumer example using a std::queue protected by a
std::mutex and a std::condition_variable. One thread should push numbers
1‑100 into the queue, the other should pop and print them.
Capstone Project – Console To‑Do List
Implement a **single‑file** C++ console program that lets the user manage a simple To‑Do list.
Feature checklist
- Store tasks as objects (
struct Task { int id; std::string text; bool done; }). - Add, toggle (complete/incomplete), delete, and list tasks.
- Persist the list to a text file (
tasks.txt) on exit and load it on start. - Use
std::vector,std::fstream,std::getline, and basic RAII. - Handle I/O errors with exceptions.
- Optional: colour the “done” tasks using ANSI escape codes.
Starter skeleton
#include <iostream> #include <vector> #include <fstream> #include <sstream> #include <string> struct Task { int id; std::string text; bool done; Task(int i, std::string t) : id(i), text(std::move(t)), done(false) {} std::string serialize() const { return std::to_string(id) + "," + text + "," + (done ? "1" : "0"); } static Task deserialize(const std::string& line) { std::istringstream ss(line); std::string part; int i; std::getline(ss, part, ','); i = std::stoi(part); std::getline(ss, part, ','); std::string txt = part; std::getline(ss, part, ','); bool d = (part == "1"); Task t(i, txt); t.done = d; return t; } }; class TodoApp { static constexpr const char* FILE_NAME = "tasks.txt"; std::vector<Task> tasks; int nextId = 1; void load() { std::ifstream in(FILE_NAME); if (!in) return; std::string line; while (std::getline(in, line)) { Task t = Task::deserialize(line); tasks.push_back(t); if (t.id >= nextId) nextId = t.id + 1; } } void save() { std::ofstream out(FILE_NAME, std::ios::trunc); for (const auto& t : tasks) { out << t.serialize() << \n; } } void printMenu() { std::cout << "\n--- To‑Do List ---\n"; for (const auto& t : tasks) { std::cout << t.id << ". [" << (t.done ? "x" : " ") << "] " << t.text << \n; } std::cout << "\n(a)dd, (t)oggle, (d)elete, (q)uit: "; } void addTask(const std::string& txt) { tasks.push_back(Task(nextId++, txt)); } void toggleTask(int id) { for (auto& t : tasks) { if (t.id == id) { t.done = !t.done; break; } } } void deleteTask(int id) { tasks.erase(std::remove_if(tasks.begin(), tasks.end(), [id](const Task& t){ return t.id == id; }), tasks.end()); } public: TodoApp() { load(); } ~TodoApp() { save(); } void run() { while (true) { printMenu(); std::string cmd; std::getline(std::cin, cmd); if (cmd.empty()) continue; char c = std::tolower(cmd[0]); if (c == 'q') break; switch (c) { case 'a': std::cout << "Task description: "; std::getline(std::cin, cmd); addTask(cmd); break; case 't': std::cout << "ID to toggle: "; std::getline(std::cin, cmd); toggleTask(std::stoi(cmd)); break; case 'd': std::cout << "ID to delete: "; std::getline(std::cin, cmd); deleteTask(std::stoi(cmd)); break; default: std::cout << "Unknown command\n"; } } } }; int main() { TodoApp app; app.run(); return 0; }
What you’ll learn
- Classes, structs and encapsulation.
- Standard containers (
vector) and algorithms. - File I/O with
fstreamand error handling. - Basic interactive console UI.
- RAII – resources are automatically released.
After this capstone you’ll be ready to move on to:
- GUI programming (Qt, wxWidgets, SFML).
- Modern C++ libraries (Boost, fmt, ranges).
- Build systems (CMake, Meson).
- Testing frameworks (GoogleTest, Catch2).
Extend the app with a priority field (enum LOW/MEDIUM/HIGH) and allow sorting by priority. Add colour output (ANSI escape codes) so completed tasks appear in grey.
Smart Pointers
Content coming soon...
Move Semantics & Rvalue References
Content coming soon...
Lambda Expressions
Content coming soon...
File I/O & Streams
Content coming soon...
STL Algorithms
Content coming soon...
Design Patterns in C++
Content coming soon...
Build Systems & CMake
Content coming soon...
Final Project — C++ Application
Content coming soon...