C++ Programming Crash Course

Master C++ from Basics to Advanced Concepts

Lesson 1: C++ Basics

Introduction to C++

C++ is a powerful, high-performance programming language that supports procedural, object-oriented, and generic programming. It's widely used in game development, systems programming, embedded systems, and high-performance applications.

Why C++? C++ provides low-level memory manipulation while offering high-level abstractions, making it ideal for performance-critical applications.

Your First C++ Program

#include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }
Structure Breakdown:
  • #include <iostream> - Includes input/output library
  • using namespace std; - Uses standard namespace
  • int main() - Entry point of the program
  • cout - Output to console
  • return 0; - Indicates successful execution

Variables and Data Types

C++ is a strongly-typed language, meaning you must declare variable types explicitly.

// Integer types int age = 25; // Standard integer short smallNum = 100; // Smaller range long bigNum = 1000000L; // Larger range long long hugeNum = 1000000000LL; // Floating-point types float price = 19.99f; // Single precision double pi = 3.14159265359; // Double precision // Character and boolean char grade = 'A'; // Single character bool isStudent = true; // Boolean (true/false) // String (requires <string> library) string name = "Alice"; // Constants const int MAX_SCORE = 100; const double TAX_RATE = 0.08;

Input and Output

#include <iostream> #include <string> using namespace std; int main() { string name; int age; // Input cout << "Enter your name: "; getline(cin, name); // Read entire line with spaces cout << "Enter your age: "; cin >> age; // Output cout << "Hello, " << name << "!" << endl; cout << "You are " << age << " years old." << endl; return 0; }

Operators

// Arithmetic operators int a = 10, b = 3; int sum = a + b; // 13 int diff = a - b; // 7 int product = a * b; // 30 int quotient = a / b; // 3 (integer division) int remainder = a % b; // 1 (modulus) // Increment and decrement int x = 5; x++; // x is now 6 (post-increment) ++x; // x is now 7 (pre-increment) x--; // x is now 6 (post-decrement) // Comparison operators bool result = (10 == 10); // true (equal to) result = (10 != 5); // true (not equal to) result = (10 > 5); // true (greater than) result = (10 < 5); // false (less than) result = (10 >= 10); // true (greater or equal) // Logical operators bool p = true, q = false; bool andResult = p && q; // false (AND) bool orResult = p || q; // true (OR) bool notResult = !p; // false (NOT)

Type Casting

// Implicit casting (automatic) int num = 10; double decimal = num; // int to double (10.0) // Explicit casting (manual) double pi = 3.14159; int intPi = (int)pi; // C-style cast: 3 int intPi2 = int(pi); // Functional cast: 3 // Static cast (C++ style - recommended) double value = 5.7; int rounded = static_cast<int>(value); // 5 // Example with division int a = 7, b = 2; double result = (double)a / b; // 3.5 (not 3)
Common Pitfall: Integer division truncates the decimal part. To get a decimal result, cast at least one operand to double: (double)a / b

Comments

// Single-line comment /* Multi-line comment Can span multiple lines */ int x = 5; // Inline comment

Basic Syntax Rules

  • Every statement ends with a semicolon ;
  • C++ is case-sensitive (age and Age are different)
  • Variable names must start with a letter or underscore
  • Use curly braces {} to define code blocks
  • Indentation is not required but strongly recommended for readability
Best Practice: Use meaningful variable names like studentCount instead of sc. Follow camelCase or snake_case consistently.

Test Your Knowledge - Lesson 1

1. Which header file is required for input/output operations in C++?

2. What is the result of 7 / 2 in C++ (both integers)?

3. Which keyword is used to declare a constant in C++?

Lesson 2: Control Flow

If/Else Statements

Conditional statements allow your program to make decisions based on conditions.

// Simple if statement int age = 18; if (age >= 18) { cout << "You are an adult" << endl; } // If-else statement int score = 75; if (score >= 60) { cout << "You passed!" << endl; } else { cout << "You failed" << endl; } // If-else if-else ladder int grade = 85; if (grade >= 90) { cout << "Grade: A" << endl; } else if (grade >= 80) { cout << "Grade: B" << endl; } else if (grade >= 70) { cout << "Grade: C" << endl; } else if (grade >= 60) { cout << "Grade: D" << endl; } else { cout << "Grade: F" << endl; } // Nested if statements int x = 10, y = 20; if (x > 0) { if (y > 0) { cout << "Both are positive" << endl; } }

Ternary Operator

// Ternary operator: condition ? value_if_true : value_if_false int age = 20; string status = (age >= 18) ? "Adult" : "Minor"; cout << status << endl; // Output: Adult // Can be used in expressions int a = 5, b = 10; int max = (a > b) ? a : b; cout << "Max: " << max << endl; // Output: Max: 10

Switch Statement

int day = 3; switch (day) { case 1: cout << "Monday" << endl; break; case 2: cout << "Tuesday" << endl; break; case 3: cout << "Wednesday" << endl; break; case 4: cout << "Thursday" << endl; break; case 5: cout << "Friday" << endl; break; case 6: case 7: cout << "Weekend" << endl; break; default: cout << "Invalid day" << endl; } // Character switch char operation = '+'; int a = 10, b = 5; switch (operation) { case '+': cout << a + b << endl; break; case '-': cout << a - b << endl; break; case '*': cout << a * b << endl; break; case '/': cout << a / b << endl; break; default: cout << "Invalid operation" << endl; }
Important: Always include break statements in switch cases to prevent fall-through behavior, unless intentional.

For Loops

// Basic for loop for (int i = 0; i < 5; i++) { cout << i << " "; // Output: 0 1 2 3 4 } cout << endl; // Loop with different step for (int i = 0; i < 10; i += 2) { cout << i << " "; // Output: 0 2 4 6 8 } cout << endl; // Countdown loop for (int i = 5; i > 0; i--) { cout << i << " "; // Output: 5 4 3 2 1 } cout << endl; // Nested loops for (int i = 1; i <= 3; i++) { for (int j = 1; j <= 3; j++) { cout << i * j << " "; } cout << endl; } // Range-based for loop (C++11) int numbers[] = {1, 2, 3, 4, 5}; for (int num : numbers) { cout << num << " "; } cout << endl;

While Loops

// Basic while loop int count = 0; while (count < 5) { cout << count << " "; count++; } cout << endl; // Output: 0 1 2 3 4 // While loop with condition int sum = 0; int i = 1; while (i <= 10) { sum += i; i++; } cout << "Sum: " << sum << endl; // Sum: 55 // User input loop int number; cout << "Enter positive numbers (0 to stop):" << endl; while (true) { cin >> number; if (number == 0) break; cout << "You entered: " << number << endl; }

Do-While Loops

// Do-while executes at least once int num = 0; do { cout << num << " "; num++; } while (num < 5); cout << endl; // Output: 0 1 2 3 4 // Menu example int choice; do { cout << "\n--- Menu ---" << endl; cout << "1. Option 1" << endl; cout << "2. Option 2" << endl; cout << "3. Exit" << endl; cout << "Enter choice: "; cin >> choice; switch (choice) { case 1: cout << "You selected Option 1" << endl; break; case 2: cout << "You selected Option 2" << endl; break; case 3: cout << "Exiting..." << endl; break; default: cout << "Invalid choice" << endl; } } while (choice != 3);

Break and Continue

// Break - exit the loop for (int i = 0; i < 10; i++) { if (i == 5) { break; // Exit loop when i is 5 } cout << i << " "; } cout << endl; // Output: 0 1 2 3 4 // Continue - skip current iteration for (int i = 0; i < 10; i++) { if (i % 2 == 0) { continue; // Skip even numbers } cout << i << " "; } cout << endl; // Output: 1 3 5 7 9 // Finding prime number int n = 29; bool isPrime = true; for (int i = 2; i < n; i++) { if (n % i == 0) { isPrime = false; break; // No need to check further } } cout << n << (isPrime ? " is prime" : " is not prime") << endl;

Functions

// Function declaration void greet(); // Function prototype // Function definition void greet() { cout << "Hello, World!" << endl; } // Function with parameters void greetPerson(string name) { cout << "Hello, " << name << "!" << endl; } // Function with return value int add(int a, int b) { return a + b; } // Function with default parameters int multiply(int a, int b = 2) { return a * b; } // Main function int main() { greet(); // Hello, World! greetPerson("Alice"); // Hello, Alice! int sum = add(5, 3); cout << "Sum: " << sum << endl; // Sum: 8 cout << multiply(5) << endl; // 10 (uses default) cout << multiply(5, 3) << endl; // 15 return 0; }

Function Overloading

// Multiple functions with same name but different parameters int add(int a, int b) { return a + b; } double add(double a, double b) { return a + b; } int add(int a, int b, int c) { return a + b + c; } int main() { cout << add(5, 3) << endl; // Calls int version: 8 cout << add(2.5, 3.7) << endl; // Calls double version: 6.2 cout << add(1, 2, 3) << endl; // Calls 3-parameter version: 6 return 0; }
Best Practice: Functions should perform a single, well-defined task. Use descriptive names that indicate what the function does.

Test Your Knowledge - Lesson 2

1. What is the output of this code?
for (int i = 1; i < 4; i++) { cout << i * 2 << " "; }

2. Which statement prevents fall-through in a switch statement?

3. What is the main difference between while and do-while loops?

Lesson 3: Arrays and Strings

Arrays

Arrays are collections of elements of the same type stored in contiguous memory locations.

// Array declaration and initialization int numbers[5]; // Declares array of 5 integers // Initialize with values int scores[5] = {85, 90, 78, 92, 88}; // Size can be omitted if initialized int values[] = {1, 2, 3, 4, 5}; // Size is 5 // Accessing elements (0-indexed) cout << scores[0] << endl; // 85 (first element) cout << scores[4] << endl; // 88 (last element) // Modifying elements scores[2] = 95; // Looping through array for (int i = 0; i < 5; i++) { cout << scores[i] << " "; } cout << endl; // Range-based for loop (C++11) for (int score : scores) { cout << score << " "; } cout << endl;
Warning: Array indices start at 0. Accessing out-of-bounds indices (negative or >= size) causes undefined behavior and may crash your program.

Multidimensional Arrays

// 2D array (matrix) int matrix[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} }; // Accessing elements cout << matrix[0][0] << endl; // 1 (first row, first column) cout << matrix[1][2] << endl; // 6 (second row, third column) // Nested loop to print matrix for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { cout << matrix[i][j] << " "; } cout << endl; } // 3D array int cube[2][3][4]; // 2 layers, 3 rows, 4 columns

Array Operations

// Finding max element int arr[] = {45, 23, 67, 12, 89, 34}; int size = sizeof(arr) / sizeof(arr[0]); // Calculate array size int maxVal = arr[0]; for (int i = 1; i < size; i++) { if (arr[i] > maxVal) { maxVal = arr[i]; } } cout << "Maximum: " << maxVal << endl; // 89 // Sum of array elements int sum = 0; for (int i = 0; i < size; i++) { sum += arr[i]; } cout << "Sum: " << sum << endl; // Average double average = (double)sum / size; cout << "Average: " << average << endl; // Reversing an array int start = 0, end = size - 1; while (start < end) { int temp = arr[start]; arr[start] = arr[end]; arr[end] = temp; start++; end--; }

C-Style Strings

#include <cstring> // For C-string functions // C-style strings (character arrays) char name[20] = "Alice"; char greeting[] = "Hello"; // String functions cout << "Length: " << strlen(name) << endl; // 5 char dest[20]; strcpy(dest, name); // Copy string strcat(dest, " Smith"); // Concatenate cout << dest << endl; // Alice Smith int result = strcmp("abc", "abd"); // Compare strings // Returns: <0 if first < second, 0 if equal, >0 if first > second
Security Warning: C-style strings are prone to buffer overflows. Always ensure the destination array is large enough when using strcpy and strcat.

C++ String Class

The C++ string class is safer and more convenient than C-style strings.

#include <string> using namespace std; // Creating strings string name = "Alice"; string greeting = "Hello"; string empty; // String operations string fullName = name + " Smith"; // Concatenation cout << fullName << endl; // String methods cout << name.length() << endl; // 5 (length) cout << name.size() << endl; // 5 (same as length) cout << name.empty() << endl; // false // Accessing characters char first = name[0]; // 'A' char last = name[name.length()-1]; // 'e' // Modifying strings name[0] = 'a'; // "alice" name += " Smith"; // Append cout << name << endl; // "alice Smith" // Substring string text = "Hello, World!"; string sub = text.substr(0, 5); // "Hello" string world = text.substr(7, 5); // "World" // Finding substrings size_t pos = text.find("World"); // Returns position (7) if (pos != string::npos) { cout << "Found at: " << pos << endl; } // Replace text.replace(7, 5, "C++"); // "Hello, C++!" // Insert and erase text.insert(5, " there"); // Insert at position 5 text.erase(0, 6); // Erase 6 chars from position 0

String Input/Output

// Reading strings string name; // cin stops at whitespace cin >> name; // Reads only first word // getline reads entire line getline(cin, name); // Reading with delimiter getline(cin, name, ','); // Read until comma // Converting between types string numStr = "123"; int num = stoi(numStr); // String to int double d = stod("3.14"); // String to double string str = to_string(456); // Int to string cout << str << endl; // "456"

String Comparison

string s1 = "apple"; string s2 = "banana"; // Using operators if (s1 == s2) { cout << "Equal" << endl; } if (s1 < s2) { cout << s1 << " comes before " << s2 << endl; } // Using compare method int result = s1.compare(s2); if (result == 0) { cout << "Equal" << endl; } else if (result < 0) { cout << s1 << " is less than " << s2 << endl; } else { cout << s1 << " is greater than " << s2 << endl; }
Best Practice: Prefer C++ string class over C-style strings. It's safer, easier to use, and provides many useful methods.

Character Functions

#include <cctype> char ch = 'a'; // Character testing isalpha(ch); // true (is alphabetic) isdigit(ch); // false (is digit) isalnum(ch); // true (is alphanumeric) isspace(ch); // false (is whitespace) isupper(ch); // false (is uppercase) islower(ch); // true (is lowercase) // Character conversion char upper = toupper(ch); // 'A' char lower = tolower('B'); // 'b' // Example: Count vowels string text = "Hello World"; int vowelCount = 0; for (char c : text) { c = tolower(c); if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u') { vowelCount++; } } cout << "Vowels: " << vowelCount << endl;

Test Your Knowledge - Lesson 3

1. Array indices in C++ start at:

2. Which function returns the length of a C++ string?

3. How do you read an entire line of text including spaces?

Lesson 4: Pointers and References

Introduction to Pointers

Pointers are variables that store memory addresses. They are one of the most powerful features of C++.

Key Concept: A pointer "points to" a memory location where data is stored. The & operator gets an address, and * dereferences (accesses the value at) an address.

Pointer Basics

// Declaring pointers int x = 10; int* ptr; // Pointer to int (uninitialized) int* ptr2 = &x; // Pointer initialized with address of x // Address-of operator (&) cout << &x << endl; // Prints address of x (e.g., 0x7ffd...) // Dereference operator (*) cout << *ptr2 << endl; // Prints value at address (10) // Pointer assignment ptr = &x; // ptr now points to x *ptr = 20; // Changes x to 20 through pointer cout << x << endl; // 20 // Different pointer types double d = 3.14; double* dPtr = &d; char c = 'A'; char* cPtr = &c; // Null pointer int* nullPtr = nullptr; // C++11 (better than NULL) // int* nullPtr = NULL; // Old style
Critical Warning: Dereferencing an uninitialized or null pointer causes undefined behavior and often crashes. Always initialize pointers before use.

Pointers and Arrays

// Arrays and pointers are closely related int arr[] = {10, 20, 30, 40, 50}; int* ptr = arr; // Array name is pointer to first element // Access elements using pointer cout << *ptr << endl; // 10 (first element) cout << *(ptr + 1) << endl; // 20 (second element) cout << *(ptr + 2) << endl; // 30 (third element) // Pointer arithmetic ptr++; // Move to next element cout << *ptr << endl; // 20 // Array notation with pointers ptr = arr; cout << ptr[0] << endl; // 10 cout << ptr[2] << endl; // 30 // Traversing array with pointer ptr = arr; for (int i = 0; i < 5; i++) { cout << *(ptr + i) << " "; } cout << endl;

Pointers to Pointers

int x = 100; int* ptr = &x; // Pointer to int int** ptr2 = &ptr; // Pointer to pointer to int cout << x << endl; // 100 (direct value) cout << *ptr << endl; // 100 (one dereference) cout << **ptr2 << endl; // 100 (two dereferences) // Modifying through pointer to pointer **ptr2 = 200; cout << x << endl; // 200

Dynamic Memory Allocation

// Allocating single variable int* ptr = new int; // Allocate memory for one int *ptr = 42; cout << *ptr << endl; // 42 delete ptr; // Free memory (important!) // Allocating with initialization int* ptr2 = new int(100); cout << *ptr2 << endl; // 100 delete ptr2; // Allocating arrays int size = 5; int* arr = new int[size]; // Dynamic array for (int i = 0; i < size; i++) { arr[i] = i * 10; } // Use the array for (int i = 0; i < size; i++) { cout << arr[i] << " "; } cout << endl; delete[] arr; // Free array memory (note the []) // 2D dynamic array int rows = 3, cols = 4; int** matrix = new int*[rows]; for (int i = 0; i < rows; i++) { matrix[i] = new int[cols]; } // Use matrix... matrix[0][0] = 1; // Free memory for (int i = 0; i < rows; i++) { delete[] matrix[i]; } delete[] matrix;
Memory Leak Warning: Every new must have a corresponding delete. Use delete[] for arrays. Failing to free memory causes memory leaks.

References

References are aliases for existing variables. Unlike pointers, they cannot be null and must be initialized.

// Reference basics int x = 10; int& ref = x; // ref is a reference (alias) to x cout << ref << endl; // 10 ref = 20; // Changes x to 20 cout << x << endl; // 20 // References must be initialized // int& ref2; // Error! Must initialize // References cannot be reassigned int y = 30; ref = y; // This assigns y's value to x, not reassigns ref cout << x << endl; // 30 cout << y << endl; // 30

Pass by Value vs Pass by Reference

// Pass by value (copy) void incrementByValue(int x) { x++; // Only modifies the copy } // Pass by reference void incrementByReference(int& x) { x++; // Modifies the original } // Pass by pointer void incrementByPointer(int* x) { (*x)++; // Modifies the original } int main() { int num = 10; incrementByValue(num); cout << num << endl; // 10 (unchanged) incrementByReference(num); cout << num << endl; // 11 (changed) incrementByPointer(&num); cout << num << endl; // 12 (changed) return 0; }

Const Pointers and References

int x = 10; int y = 20; // Pointer to constant (can't change value) const int* ptr1 = &x; // *ptr1 = 20; // Error! Can't modify value ptr1 = &y; // OK - can change pointer // Constant pointer (can't change address) int* const ptr2 = &x; *ptr2 = 30; // OK - can modify value // ptr2 = &y; // Error! Can't change pointer // Constant pointer to constant const int* const ptr3 = &x; // *ptr3 = 40; // Error! Can't modify value // ptr3 = &y; // Error! Can't change pointer // Const reference (common for function parameters) void printValue(const int& x) { cout << x << endl; // x = 100; // Error! Can't modify }
Best Practice: Use references for function parameters when you want to avoid copying large objects. Use const references when the function shouldn't modify the parameter.

Common Pointer Pitfalls

// Dangling pointer int* ptr = new int(10); delete ptr; // *ptr = 20; // Error! Dangling pointer (accessing freed memory) ptr = nullptr; // Good practice after delete // Memory leak void leakyFunction() { int* ptr = new int(100); // Forgot to delete! } // Memory leaked when function returns // Wild pointer int* ptr; // Uninitialized // *ptr = 5; // Error! Wild pointer (random address)

Return by Reference

// Return reference to allow chaining class Counter { int count; public: Counter() : count(0) {} Counter& increment() { count++; return *this; // Return reference to self } int getValue() { return count; } }; int main() { Counter c; c.increment().increment().increment(); // Chaining cout << c.getValue() << endl; // 3 return 0; }

Test Your Knowledge - Lesson 4

1. What operator is used to get the address of a variable?

2. Which is used to free dynamically allocated array memory?

3. What is the main difference between pointers and references?

Lesson 5: Object-Oriented Programming

Introduction to OOP

Object-Oriented Programming organizes code into classes and objects, encapsulating data and behavior together.

Four Pillars of OOP: Encapsulation, Abstraction, Inheritance, and Polymorphism.

Classes and Objects

// Class definition class Dog { public: // Data members (attributes) string name; int age; // Member functions (methods) void bark() { cout << name << " says: Woof!" << endl; } void displayInfo() { cout << "Name: " << name << ", Age: " << age << endl; } }; int main() { // Creating objects Dog dog1; dog1.name = "Buddy"; dog1.age = 3; dog1.bark(); // Buddy says: Woof! dog1.displayInfo(); // Name: Buddy, Age: 3 Dog dog2; dog2.name = "Max"; dog2.age = 5; dog2.bark(); // Max says: Woof! return 0; }

Constructors and Destructors

class Rectangle { private: double width; double height; public: // Default constructor Rectangle() { width = 0; height = 0; cout << "Default constructor called" << endl; } // Parameterized constructor Rectangle(double w, double h) { width = w; height = h; cout << "Parameterized constructor called" << endl; } // Constructor with initialization list (preferred) Rectangle(double w, double h, bool init) : width(w), height(h) { cout << "Constructor with init list" << endl; } // Destructor ~Rectangle() { cout << "Destructor called" << endl; } double getArea() { return width * height; } }; int main() { Rectangle r1; // Default constructor Rectangle r2(5.0, 3.0); // Parameterized constructor cout << "Area: " << r2.getArea() << endl; return 0; } // Destructors called automatically

Access Specifiers

class BankAccount { private: // Private: accessible only within class double balance; string accountNumber; protected: // Protected: accessible in class and derived classes string ownerName; public: // Public: accessible from anywhere BankAccount(string owner, double initial) { ownerName = owner; balance = initial; accountNumber = "ACC" + to_string(rand()); } // Getter methods double getBalance() const { return balance; } string getOwner() const { return ownerName; } // Setter methods void deposit(double amount) { if (amount > 0) { balance += amount; } } bool withdraw(double amount) { if (amount > 0 && amount <= balance) { balance -= amount; return true; } return false; } }; int main() { BankAccount account("Alice", 1000); account.deposit(500); cout << "Balance: $" << account.getBalance() << endl; // account.balance = 10000; // Error! Private member return 0; }
Encapsulation: Keep data members private and provide public getter/setter methods. This protects data from invalid modifications.

Inheritance

// Base class (parent) class Animal { protected: string name; int age; public: Animal(string n, int a) : name(n), age(a) {} void eat() { cout << name << " is eating" << endl; } void sleep() { cout << name << " is sleeping" << endl; } virtual void makeSound() { cout << "Some generic sound" << endl; } }; // Derived class (child) class Dog : public Animal { private: string breed; public: Dog(string n, int a, string b) : Animal(n, a), breed(b) {} void makeSound() override { cout << name << " barks: Woof!" << endl; } void fetch() { cout << name << " is fetching the ball" << endl; } }; class Cat : public Animal { public: Cat(string n, int a) : Animal(n, a) {} void makeSound() override { cout << name << " meows: Meow!" << endl; } }; int main() { Dog dog("Buddy", 3, "Golden Retriever"); dog.eat(); // Inherited from Animal dog.makeSound(); // Overridden in Dog dog.fetch(); // Dog-specific method Cat cat("Whiskers", 2); cat.sleep(); // Inherited from Animal cat.makeSound(); // Overridden in Cat return 0; }

Polymorphism

// Runtime polymorphism with virtual functions class Shape { public: virtual double getArea() { return 0; } virtual void display() { cout << "This is a shape" << endl; } // Virtual destructor important for polymorphism virtual ~Shape() {} }; class Circle : public Shape { private: double radius; public: Circle(double r) : radius(r) {} double getArea() override { return 3.14159 * radius * radius; } void display() override { cout << "Circle with radius " << radius << endl; } }; class Rectangle : public Shape { private: double width, height; public: Rectangle(double w, double h) : width(w), height(h) {} double getArea() override { return width * height; } void display() override { cout << "Rectangle " << width << "x" << height << endl; } }; int main() { // Polymorphism: base class pointer to derived objects Shape* shapes[3]; shapes[0] = new Circle(5.0); shapes[1] = new Rectangle(4.0, 6.0); shapes[2] = new Circle(3.0); // Calls correct overridden method for each object for (int i = 0; i < 3; i++) { shapes[i]->display(); cout << "Area: " << shapes[i]->getArea() << endl; cout << endl; } // Clean up for (int i = 0; i < 3; i++) { delete shapes[i]; } return 0; }
Virtual Functions: Use virtual keyword in base class to enable runtime polymorphism. The correct method is called based on the actual object type, not pointer type.

Abstract Classes and Pure Virtual Functions

// Abstract class (cannot be instantiated) class Shape { public: // Pure virtual function virtual double getArea() = 0; virtual double getPerimeter() = 0; virtual ~Shape() {} }; class Triangle : public Shape { private: double a, b, c; public: Triangle(double side1, double side2, double side3) : a(side1), b(side2), c(side3) {} // Must implement all pure virtual functions double getArea() override { double s = (a + b + c) / 2; return sqrt(s * (s-a) * (s-b) * (s-c)); } double getPerimeter() override { return a + b + c; } }; int main() { // Shape s; // Error! Cannot instantiate abstract class Triangle t(3, 4, 5); cout << "Area: " << t.getArea() << endl; cout << "Perimeter: " << t.getPerimeter() << endl; return 0; }

Friend Functions and Classes

class Box { private: double width; public: Box(double w) : width(w) {} // Friend function can access private members friend void printWidth(Box b); // Friend class friend class BoxPrinter; }; void printWidth(Box b) { cout << "Width: " << b.width << endl; } class BoxPrinter { public: void print(Box b) { cout << "Box width: " << b.width << endl; } }; int main() { Box box(10.5); printWidth(box); BoxPrinter printer; printer.print(box); return 0; }

Static Members

class Counter { private: static int count; // Static member variable int id; public: Counter() { id = ++count; } // Static member function static int getCount() { return count; } int getId() { return id; } }; // Initialize static member outside class int Counter::count = 0; int main() { cout << "Initial count: " << Counter::getCount() << endl; Counter c1, c2, c3; cout << "Count: " << Counter::getCount() << endl; // 3 cout << "c1 ID: " << c1.getId() << endl; // 1 cout << "c2 ID: " << c2.getId() << endl; // 2 cout << "c3 ID: " << c3.getId() << endl; // 3 return 0; }
Remember: Static members are shared by all objects of the class. They exist even before any object is created.

Test Your Knowledge - Lesson 5

1. What access specifier should be used for data members to ensure encapsulation?

2. Which keyword enables runtime polymorphism in C++?

3. What makes a class abstract in C++?

Lesson 6: Advanced C++

Standard Template Library (STL)

The STL provides powerful, reusable components: containers, iterators, and algorithms.

Vectors

#include <vector> using namespace std; // Creating vectors vector<int> numbers; // Empty vector vector<int> scores(5); // 5 elements, default value 0 vector<int> values(5, 10); // 5 elements, all set to 10 vector<int> nums = {1, 2, 3, 4, 5}; // Initialize with values // Adding elements numbers.push_back(10); // Add to end numbers.push_back(20); numbers.push_back(30); // Accessing elements cout << numbers[0] << endl; // 10 (no bounds checking) cout << numbers.at(1) << endl; // 20 (with bounds checking) cout << numbers.front() << endl; // First element cout << numbers.back() << endl; // Last element // Size and capacity cout << numbers.size() << endl; // Number of elements cout << numbers.capacity() << endl; // Allocated space cout << numbers.empty() << endl; // Check if empty // Removing elements numbers.pop_back(); // Remove last element numbers.clear(); // Remove all elements // Iterating vector<int> v = {1, 2, 3, 4, 5}; for (int i = 0; i < v.size(); i++) { cout << v[i] << " "; } cout << endl; // Range-based for loop for (int num : v) { cout << num << " "; } cout << endl; // Iterator for (vector<int>::iterator it = v.begin(); it != v.end(); it++) { cout << *it << " "; } cout << endl;

Other STL Containers

#include <list> #include <deque> #include <set> #include <map> #include <stack> #include <queue> // List (doubly-linked list) list<int> myList = {1, 2, 3, 4}; myList.push_front(0); // Add to front myList.push_back(5); // Add to back // Set (sorted unique elements) set<int> mySet = {5, 2, 8, 2, 1}; mySet.insert(10); cout << mySet.size() << endl; // 5 (duplicates removed) // Map (key-value pairs) map<string, int> ages; ages["Alice"] = 25; ages["Bob"] = 30; ages["Charlie"] = 28; cout << ages["Alice"] << endl; // 25 // Iterating through map for (auto& pair : ages) { cout << pair.first << ": " << pair.second << endl; } // Stack (LIFO) stack<int> myStack; myStack.push(10); myStack.push(20); myStack.push(30); cout << myStack.top() << endl; // 30 myStack.pop(); // Remove top // Queue (FIFO) queue<int> myQueue; myQueue.push(10); myQueue.push(20); cout << myQueue.front() << endl; // 10 myQueue.pop();

STL Algorithms

#include <algorithm> vector<int> v = {5, 2, 8, 1, 9, 3}; // Sort sort(v.begin(), v.end()); // Ascending: 1 2 3 5 8 9 sort(v.begin(), v.end(), greater<int>()); // Descending // Find auto it = find(v.begin(), v.end(), 8); if (it != v.end()) { cout << "Found: " << *it << endl; } // Binary search (on sorted container) if (binary_search(v.begin(), v.end(), 5)) { cout << "5 exists" << endl; } // Count int count = count(v.begin(), v.end(), 3); // Min/Max int minVal = *min_element(v.begin(), v.end()); int maxVal = *max_element(v.begin(), v.end()); // Reverse reverse(v.begin(), v.end()); // Sum (requires <numeric>) #include <numeric> int sum = accumulate(v.begin(), v.end(), 0);
STL Benefits: STL containers and algorithms are highly optimized, tested, and save development time. Always prefer STL over manual implementations.

Templates

// Function template template <typename T> T getMax(T a, T b) { return (a > b) ? a : b; } int main() { cout << getMax(5, 10) << endl; // int version cout << getMax(3.5, 2.1) << endl; // double version cout << getMax('a', 'z') << endl; // char version return 0; } // Class template template <typename T> class Box { private: T value; public: Box(T v) : value(v) {} T getValue() { return value; } void setValue(T v) { value = v; } }; int main() { Box<int> intBox(123); Box<string> strBox("Hello"); Box<double> dblBox(3.14); cout << intBox.getValue() << endl; cout << strBox.getValue() << endl; return 0; }

Smart Pointers (C++11)

Smart pointers automatically manage memory, preventing memory leaks.

#include <memory> // unique_ptr - exclusive ownership unique_ptr<int> ptr1(new int(10)); // Or better (C++14): auto ptr2 = make_unique<int>(20); cout << *ptr1 << endl; // 10 *ptr1 = 100; // unique_ptr cannot be copied // unique_ptr<int> ptr3 = ptr1; // Error! // But can be moved unique_ptr<int> ptr4 = move(ptr1); // ptr1 is now null // shared_ptr - shared ownership shared_ptr<int> sp1 = make_shared<int>(42); shared_ptr<int> sp2 = sp1; // Both point to same object cout << sp1.use_count() << endl; // 2 (reference count) sp1.reset(); // sp1 releases ownership cout << sp2.use_count() << endl; // 1 // weak_ptr - non-owning reference shared_ptr<int> sp = make_shared<int>(100); weak_ptr<int> wp = sp; if (auto temp = wp.lock()) { // Convert to shared_ptr cout << *temp << endl; } // Smart pointer with custom class class Person { public: string name; Person(string n) : name(n) { cout << name << " created" << endl; } ~Person() { cout << name << " destroyed" << endl; } }; auto person = make_unique<Person>("Alice"); // Automatically deleted when person goes out of scope
Best Practice: Use smart pointers instead of raw pointers with new/delete. Prefer unique_ptr by default, use shared_ptr when sharing ownership is necessary.

Lambda Expressions (C++11)

// Basic lambda auto greet = []() { cout << "Hello!" << endl; }; greet(); // Lambda with parameters auto add = [](int a, int b) { return a + b; }; cout << add(5, 3) << endl; // 8 // Lambda with capture int x = 10; auto addX = [x](int y) { return x + y; }; cout << addX(5) << endl; // 15 // Capture by reference int count = 0; auto increment = [&count]() { count++; }; increment(); increment(); cout << count << endl; // 2 // Capture all by value/reference int a = 1, b = 2; auto lambda1 = [=]() { return a + b; }; // Capture all by value auto lambda2 = [&]() { a++; b++; }; // Capture all by reference // Using with STL algorithms vector<int> nums = {1, 2, 3, 4, 5, 6}; // for_each for_each(nums.begin(), nums.end(), [](int n) { cout << n * 2 << " "; }); cout << endl; // sort with custom comparator sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; // Descending order });

Exception Handling

// Basic try-catch try { int age = -5; if (age < 0) { throw "Age cannot be negative"; } } catch (const char* msg) { cout << "Error: " << msg << endl; } // Throwing exceptions double divide(double a, double b) { if (b == 0) { throw runtime_error("Division by zero"); } return a / b; } try { cout << divide(10, 0) << endl; } catch (runtime_error& e) { cout << "Error: " << e.what() << endl; } // Multiple catch blocks try { // Some code throw out_of_range("Index out of range"); } catch (out_of_range& e) { cout << "Range error: " << e.what() << endl; } catch (exception& e) { cout << "General error: " << e.what() << endl; } catch (...) { cout << "Unknown error" << endl; } // Custom exception class class MyException : public exception { private: string message; public: MyException(string msg) : message(msg) {} const char* what() const noexcept override { return message.c_str(); } }; try { throw MyException("Custom error occurred"); } catch (MyException& e) { cout << e.what() << endl; }

File Operations

#include <fstream> // Writing to file ofstream outFile("output.txt"); if (outFile.is_open()) { outFile << "Hello, World!" << endl; outFile << "C++ File I/O" << endl; outFile.close(); } // Reading from file ifstream inFile("input.txt"); if (inFile.is_open()) { string line; while (getline(inFile, line)) { cout << line << endl; } inFile.close(); } // Reading word by word ifstream file("data.txt"); string word; while (file >> word) { cout << word << " "; } file.close(); // Append to file ofstream appendFile("log.txt", ios::app); appendFile << "New log entry" << endl; appendFile.close(); // Binary file operations ofstream binFile("data.bin", ios::binary); int numbers[] = {1, 2, 3, 4, 5}; binFile.write((char*)numbers, sizeof(numbers)); binFile.close();
Modern C++ Features: C++11 and later introduced many features: auto keyword, range-based for loops, smart pointers, lambda expressions, and more. These make C++ code cleaner and safer.

Test Your Knowledge - Lesson 6

1. Which smart pointer should be used for exclusive ownership?

2. Which STL container maintains sorted unique elements?

3. What symbol is used to capture all variables by reference in a lambda?