C++ Course
C++ Crash Course
The Essential Reference Guide — Modern C++20
1. Hello World & Program Structure
Every C++ program has a main() function — the entry point. Headers are included with #include and the std namespace provides standard library features.
#include <iostream>
#include <string>
int main() {
std::cout << "Hello, World!" << std::endl;
// using namespace std; avoids the std:: prefix
using namespace std;
string name = "Ayman";
cout << "Hello, " << name << endl;
return 0; // 0 = success
}C++ vs Java/C#
Unlike Java or C#, C++ compiles directly to machine code — no virtual machine. This gives maximum performance but requires you to manage memory manually (unless you use smart pointers).
2. Variables & Data Types
C++ is statically typed. Use auto for type inference (C++11+).
// Fundamental types
int age = 25;
double gpa = 3.85;
float price = 9.99f;
char grade = 'A';
bool active = true;
long long big = 9'000'000'000LL; // digit separator C++14
// auto — compiler infers the type
auto score = 95; // int
auto name = std::string{"Ayman"};
// const — value cannot change
const double PI = 3.14159;
// constexpr — evaluated at compile time
constexpr int MAX_SIZE = 100;
// std::string
std::string msg = "Hello, C++!";
std::cout << msg.length() << std::endl; // 113. Pointers & References
Pointers and references are the most important — and most misunderstood — feature of C++. A pointer stores a memory address. A reference is an alias for an existing variable.
int x = 42;
// Pointer — stores the address of x
int* ptr = &x; // & = address-of operator
std::cout << ptr << "\n"; // prints memory address
std::cout << *ptr << "\n"; // * = dereference → 42
*ptr = 99; // modifies x through pointer
// Null pointer (modern C++)
int* p = nullptr;
// Reference — an alias, cannot be rebound
int& ref = x;
ref = 100; // same as x = 100
// Pointer arithmetic
int arr[] = {10, 20, 30};
int* p2 = arr;
std::cout << *(p2 + 1); // 20 — second element
// Dynamic allocation
int* heap = new int(55); // allocate on heap
delete heap; // MUST free — memory leak if omitted
heap = nullptr; // good habit after delete⚠ Memory Safety
Every new must be matched with a delete. In modern C++ prefer std::unique_ptr or std::shared_ptr (Section 10) — they delete automatically.
4. Control Flow
int score = 85;
// if / else if / else
if (score >= 90) std::cout << "A\n";
else if (score >= 80) std::cout << "B\n";
else std::cout << "F\n";
// switch
switch (score / 10) {
case 10: case 9: std::cout << "A"; break;
case 8: std::cout << "B"; break;
default: std::cout << "F"; break;
}
// Ternary
std::string result = (score >= 60) ? "Pass" : "Fail";
// Loops
for (int i = 0; i < 5; i++) std::cout << i << " ";
int count = 0;
while (count < 3) { std::cout << count++; }
// Range-based for (C++11)
std::vector<int> nums = {1, 2, 3, 4};
for (const auto& n : nums) std::cout << n << " ";5. Functions
// Pass by value — copy is made
int square(int n) { return n * n; }
// Pass by reference — modifies original
void doubleIt(int& n) { n *= 2; }
// Pass by const reference — read-only, no copy (efficient for large types)
void print(const std::string& msg) {
std::cout << msg << std::endl;
}
// Default parameters
double power(double base, int exp = 2) {
double result = 1;
for (int i = 0; i < exp; i++) result *= base;
return result;
}
// Function overloading — same name, different parameters
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
// Inline — suggests compiler to expand at call site
inline int max2(int a, int b) { return (a > b) ? a : b; }Best Practice
Prefer const T& over T for large parameters — passing by const reference avoids copying without allowing modification.
6. Arrays & STL Containers
#include <vector>
#include <map>
#include <set>
#include <array>
// C-style array (avoid in modern C++)
int raw[3] = {10, 20, 30};
// std::array — fixed-size, safe (C++11)
std::array<int, 3> arr = {10, 20, 30};
std::cout << arr.size(); // 3
// std::vector — dynamic array (most common)
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.pop_back();
std::cout << v[0]; // 1
std::cout << v.size(); // 3
// std::map — sorted key-value pairs
std::map<std::string, int> grades;
grades["Ayman"] = 95;
grades["Sara"] = 88;
for (const auto& [name, grade] : grades) // structured bindings C++17
std::cout << name << ": " << grade << "\n";
// std::set — unique sorted values
std::set<int> unique = {3, 1, 4, 1, 5}; // stores {1,3,4,5}7. OOP & Classes
class Student {
private:
std::string name;
int grade;
public:
// Constructor
Student(const std::string& n, int g)
: name(n), grade(g) {} // initializer list (preferred)
// Destructor — called when object is destroyed
~Student() {}
// Getters (const — do not modify object)
std::string getName() const { return name; }
int getGrade() const { return grade; }
// Method
std::string letterGrade() const {
if (grade >= 90) return "A";
if (grade >= 80) return "B";
if (grade >= 70) return "C";
return "F";
}
// Operator overloading
bool operator<(const Student& other) const {
return grade < other.grade;
}
};
// Usage
Student s("Ayman", 92);
std::cout << s.getName() << ": " << s.letterGrade(); // Ayman: A8. Inheritance & Polymorphism
// Base class
class Shape {
public:
virtual double area() const = 0; // pure virtual = abstract
virtual ~Shape() {} // virtual destructor — essential
virtual std::string describe() const {
return "Shape with area: " + std::to_string(area());
}
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override {
return 3.14159 * radius * radius;
}
};
class Rectangle : public Shape {
double w, h;
public:
Rectangle(double w, double h) : w(w), h(h) {}
double area() const override { return w * h; }
};
// Polymorphism via base pointer
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(4.0, 6.0));
for (const auto& s : shapes)
std::cout << s->area() << "\n";override keyword
Always use override when overriding virtual functions — the compiler will error if the signature doesn't match the base, catching subtle bugs.
9. Templates
Templates allow writing type-generic code — the foundation of the STL. The compiler generates a concrete version for each type used.
// Function template
template<typename T>
T maxVal(T a, T b) { return (a > b) ? a : b; }
std::cout << maxVal(3, 7); // 7 (int)
std::cout << maxVal(3.14, 2.71); // 3.14 (double)
std::cout << maxVal(std::string{"a"}, std::string{"z"}); // z
// Class template
template<typename T>
class Box {
T value;
public:
Box(T v) : value(v) {}
T get() const { return value; }
};
Box<int> iBox(42);
Box<std::string> sBox("Hello");
std::cout << iBox.get(); // 42
// Template with constraint (C++20 concepts)
template<std::integral T> // only integer types
T factorial(T n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}10. Smart Pointers
Smart pointers manage memory automatically — no manual delete needed. They follow RAII: resource is freed when the pointer goes out of scope.
#include <memory>
// unique_ptr — sole owner, cannot be copied
std::unique_ptr<int> uptr = std::make_unique<int>(42);
std::cout << *uptr; // 42
// Automatically deleted when uptr goes out of scope
// shared_ptr — reference counted, can be shared
std::shared_ptr<int> sp1 = std::make_shared<int>(99);
std::shared_ptr<int> sp2 = sp1; // both point to same int
std::cout << sp1.use_count(); // 2 — two owners
// Deleted when last shared_ptr is destroyed
// weak_ptr — observes without owning (breaks circular refs)
std::weak_ptr<int> wp = sp1;
if (auto locked = wp.lock()) {
std::cout << *locked; // safe access
}
// Smart pointer with custom class
auto student = std::make_unique<Student>("Ayman", 92);
std::cout << student->getName(); // use -> to access membersRule of Thumb
Default to unique_ptr. Use shared_ptr only when you genuinely need shared ownership. Never use raw new/delete in modern C++ unless working with legacy code.
11. Lambda & STL Algorithms
#include <algorithm>
#include <numeric>
std::vector<int> v = {5, 2, 8, 1, 9, 3};
// Lambda syntax: [capture](params) { body }
auto isEven = [](int n) { return n % 2 == 0; };
// Sort ascending
std::sort(v.begin(), v.end());
// Sort descending with lambda
std::sort(v.begin(), v.end(), [](int a, int b){ return a > b; });
// Count even numbers
auto cnt = std::count_if(v.begin(), v.end(), isEven);
// Find first element > 5
auto it = std::find_if(v.begin(), v.end(),
[](int n){ return n > 5; });
// Transform — square each element into new vector
std::vector<int> squares;
std::transform(v.begin(), v.end(),
std::back_inserter(squares),
[](int n){ return n * n; });
// Accumulate (sum)
int total = std::accumulate(v.begin(), v.end(), 0);
// Capture by reference in lambda
int threshold = 4;
auto aboveThreshold = [&threshold](int n){ return n > threshold; };12. Modern C++20 Features
Concepts — type constraints
#include <concepts>
template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
template<Numeric T>
T add(T a, T b) { return a + b; } // only compiles for numeric typesRanges — cleaner algorithms
#include <ranges>
std::vector<int> v = {1, 2, 3, 4, 5, 6};
// Ranges: filter + transform pipeline (no iterators needed)
auto result = v
| std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; });
// result = {4, 16, 36} — lazy evaluatedCoroutines & std::span
#include <span>
// std::span — non-owning view over contiguous data (C++20)
void printAll(std::span<const int> data) {
for (int x : data) std::cout << x << " ";
}
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
printAll(arr); // works with both array and vector
printAll(vec);