C++ Programming Made Easy – Learn with Examples & Notes

0

📚 C++ Programming Notes 📚

1: Introduction to C++ 🚀

Overview of C++ 🔍

C++ is a powerful, general-purpose programming language developed by Bjarne Stroustrup as an extension of the C language. It was designed to enhance C with object-oriented features while maintaining its efficiency and low-level memory manipulation capabilities.

Features of C++ 🌟

  • Object-Oriented Programming (OOP): Supports classes, objects, inheritance, polymorphism, encapsulation, and abstraction
  • Mid-level language: Combines features of high-level and low-level languages
  • Rich Library Support: Standard Template Library (STL)
  • Memory Management: Manual control via pointers
  • Platform Independent: Code can be compiled on different operating systems
  • Speed: Compiled language with high performance
  • Multi-paradigm: Supports procedural, object-oriented, and generic programming

Basic Structure of a C++ Program 🏗️

// This is a comment
#include <iostream> // Header file inclusion

// main function - program execution starts here
int main() {
    // Program statements
    std::cout << "Hello, World!"; // Output statement

    return 0; // Return statement
}

Compiling and Executing a C++ Program ⚙️

  1. Writing the code: Create a file with .cpp extension (e.g., program.cpp)
  2. Compilation: Convert source code to executable
    • Using g++: g++ program.cpp -o program
    • Using Visual Studio: Build project from IDE
  3. Execution: Run the compiled program
    • Windows: program.exe
    • Linux/macOS: ./program

C++ vs. C 🔄

Feature C C++
Paradigm Procedural Multi-paradigm (Procedural, OOP, Generic)
Functions Top-down approach Both top-down and bottom-up
Data Focus Program-oriented Object-oriented
File Extension .c .cpp, .cxx, .cc
Input/Output printf(), scanf() cin, cout (also supports printf/scanf)
Exception Handling Not supported Supported with try, catch, throw
Namespace Not available Available
Classes Not supported Fully supported
Function Overloading Not supported Supported

2: Basics of C++ 💻

Data Types and Variables 📊

Basic Data Types:

Data Type Description Size (typical) Example
int Integer numbers 4 bytes int age = 25;
float Floating-point numbers 4 bytes float price = 24.99f;
double Double precision floating-point 8 bytes double pi = 3.14159265359;
char Single character 1 byte char grade = 'A';
bool Boolean (true/false) 1 byte bool isActive = true;

Derived Data Types:

  • Arrays: int numbers[5] = {1, 2, 3, 4, 5};
  • Pointers: int* ptr = &num;
  • References: int& ref = num;

User-Defined Data Types:

  • Structures: struct Person { std::string name; int age; };
  • Classes: class Student { private: int id; public: void setID(int i); };
  • Enumerations: enum Colors { RED, GREEN, BLUE };

Variable Declaration and Initialization 📝

// Declaration
int count;

// Declaration with initialization
int age = 30;

// Multiple variables of same type
int x, y, z;

// Multiple variables with initialization
int a = 1, b = 2, c = 3;

// Constants
const double PI = 3.14159;

Constants and Operators 🧮

Constants Types:

  • Literal Constants: Direct values like 5, 3.14, 'A', "Hello"
  • Defined Constants: Using #define PI 3.14159
  • Constant Variables: Using const double PI = 3.14159;

Operators:

  1. Arithmetic Operators

    • Addition: + (e.g., a + b)
    • Subtraction: - (e.g., a - b)
    • Multiplication: * (e.g., a * b)
    • Division: / (e.g., a / b)
    • Modulus: % (e.g., a % b)
    • Increment: ++ (e.g., a++ or ++a)
    • Decrement: -- (e.g., a-- or --a)
  2. Relational Operators

    • Equal to: == (e.g., a == b)
    • Not equal to: != (e.g., a != b)
    • Greater than: > (e.g., a > b)
    • Less than: < (e.g., a < b)
    • Greater than or equal to: >= (e.g., a >= b)
    • Less than or equal to: <= (e.g., a <= b)
  3. Logical Operators

    • AND: && (e.g., a && b)
    • OR: || (e.g., a || b)
    • NOT: ! (e.g., !a)
  4. Assignment Operators

    • Simple assignment: = (e.g., a = b)
    • Add and assign: += (e.g., a += b is same as a = a + b)
    • Subtract and assign: -=
    • Multiply and assign: *=
    • Divide and assign: /=
    • Modulus and assign: %=
  5. Bitwise Operators

    • AND: &
    • OR: |
    • XOR: ^
    • Complement: ~
    • Left shift: <<
    • Right shift: >>

Input and Output (cin, cout) 🖥️

Standard Output (cout):

#include <iostream>

int main() {
    // Basic output
    std::cout << "Hello, World!";

    // Output with newline
    std::cout << "First line" << std::endl;
    std::cout << "Second line";

    // Output variables
    int age = 25;
    std::cout << "I am " << age << " years old.";

    return 0;
}

Standard Input (cin):

#include <iostream>

int main() {
    int number;
    std::cout << "Enter a number: ";
    std::cin >> number;
    std::cout << "You entered: " << number << std::endl;

    // Reading multiple values
    int x, y;
    std::cout << "Enter two numbers: ";
    std::cin >> x >> y;
    std::cout << "Sum: " << (x + y) << std::endl;

    // Reading strings
    std::string name;
    std::cout << "Enter your name: ";
    std::cin >> name; // Note: This reads only until the first whitespace
    std::cout << "Hello, " << name << "!" << std::endl;

    // Reading a line with spaces
    std::string fullName;
    std::cout << "Enter your full name: ";
    std::cin.ignore(); // Clear the input buffer
    std::getline(std::cin, fullName);
    std::cout << "Hello, " << fullName << "!" << std::endl;

    return 0;
}

Using namespace std:

Instead of writing std::cout and std::cin, you can use:

#include <iostream>
using namespace std; // Now 'std::' prefix is not needed

int main() {
    cout << "Hello, World!" << endl;
    int age;
    cin >> age;
    return 0;
}

Type Conversion and Typecasting 🔄

Implicit Type Conversion (Automatic):

int i = 10;
double d = i;    // Implicitly converts int to double

Explicit Type Conversion (Type Casting):

  1. C-style casting:
double d = 3.14;
int i = (int)d;    // Explicitly converts double to int, i = 3
  1. Function-style casting:
double d = 3.14;
int i = int(d);    // Equivalent to C-style casting
  1. C++ style casting:
// static_cast: for well-defined conversions
double d = 3.14;
int i = static_cast<int>(d);

// const_cast: to add or remove const qualifier
const int x = 10;
int* y = const_cast<int*>(&x);

// dynamic_cast: for polymorphic classes (with virtual functions)
// Used for safe downcasting in inheritance hierarchies
Derived* derived = dynamic_cast<Derived*>(basePtr);

// reinterpret_cast: for unsafe, low-level conversions
int* p = new int(10);
long addr = reinterpret_cast<long>(p);

Practice Questions ✏️

  1. Write a C++ program to display "Hello, World!" on the screen.

  2. Create a program that takes two integers as input and displays their sum, difference, product, and quotient.

  3. Write a program to convert temperature from Celsius to Fahrenheit using the formula: F = (C × 9/5) + 32.

  4. Create a program that calculates the area of a circle when the radius is provided by the user.

  5. Write a program that swaps the values of two variables without using a third variable.

Sample Solutions

Question 1:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, World!" << endl;
    return 0;
}

Question 2:

#include <iostream>
using namespace std;

int main() {
    int num1, num2;

    cout << "Enter two integers: ";
    cin >> num1 >> num2;

    cout << "Sum: " << num1 + num2 << endl;
    cout << "Difference: " << num1 - num2 << endl;
    cout << "Product: " << num1 * num2 << endl;

    if (num2 != 0) {
        cout << "Quotient: " << static_cast<double>(num1) / num2 << endl;
    } else {
        cout << "Cannot divide by zero." << endl;
    }

    return 0;
}

Question 3:

#include <iostream>
using namespace std;

int main() {
    double celsius, fahrenheit;

    cout << "Enter temperature in Celsius: ";
    cin >> celsius;

    fahrenheit = (celsius * 9.0/5.0) + 32;

    cout << celsius << " degrees Celsius is equal to ";
    cout << fahrenheit << " degrees Fahrenheit." << endl;

    return 0;
}

Question 4:

#include <iostream>
#include <cmath>
using namespace std;

int main() {
    const double PI = 3.14159;
    double radius, area;

    cout << "Enter the radius of the circle: ";
    cin >> radius;

    area = PI * radius * radius;
    // Alternatively: area = PI * pow(radius, 2);

    cout << "The area of the circle with radius " << radius;
    cout << " is " << area << endl;

    return 0;
}

Question 5:

#include <iostream>
using namespace std;

int main() {
    int a, b;

    cout << "Enter two numbers: ";
    cin >> a >> b;

    cout << "Before swapping: a = " << a << ", b = " << b << endl;

    // Swapping without using a third variable
    a = a + b;
    b = a - b;
    a = a - b;

    cout << "After swapping: a = " << a << ", b = " << b << endl;

    return 0;
}

3: Control Structures 🔄

Control structures direct the flow of program execution based on conditions or iterations.

Decision-Making Statements 🔀

1. if Statement

The if statement executes a block of code only if a specified condition evaluates to true.

#include <iostream>
using namespace std;

int main() {
    int number = 10;

    if (number > 0) {
        cout << "The number is positive." << endl;
    }

    return 0;
}

2. if-else Statement

The if-else statement executes one block of code if a condition is true and another block if it's false.

#include <iostream>
using namespace std;

int main() {
    int number = -5;

    if (number >= 0) {
        cout << "The number is positive or zero." << endl;
    } else {
        cout << "The number is negative." << endl;
    }

    return 0;
}

3. if-else if-else Ladder

For testing multiple conditions in sequence.

#include <iostream>
using namespace std;

int main() {
    int score = 85;

    if (score >= 90) {
        cout << "Grade: A" << endl;
    } else if (score >= 80) {
        cout << "Grade: B" << endl;
    } else if (score >= 70) {
        cout << "Grade: C" << endl;
    } else if (score >= 60) {
        cout << "Grade: D" << endl;
    } else {
        cout << "Grade: F" << endl;
    }

    return 0;
}

4. Nested if Statements

An if statement inside another if or else block.

#include <iostream>
using namespace std;

int main() {
    int age = 25;
    bool hasID = true;

    if (age >= 18) {
        if (hasID) {
            cout << "You can enter the venue." << endl;
        } else {
            cout << "You need ID to enter." << endl;
        }
    } else {
        cout << "You must be 18 or older to enter." << endl;
    }

    return 0;
}

5. switch-case Statement

Tests a variable against multiple values.

#include <iostream>
using namespace std;

int main() {
    int day = 4;

    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:
            cout << "Saturday" << endl;
            break;
        case 7:
            cout << "Sunday" << endl;
            break;
        default:
            cout << "Invalid day number" << endl;
    }

    return 0;
}

Notes on switch statements:

  • The break statement is necessary to exit the switch block after a match is found
  • Without break, execution "falls through" to the next case
  • The default case executes when no match is found
  • Only constants are allowed in case expressions
  • Only integral types (int, char, enum) can be used in the switch variable

Looping Statements 🔁

1. for Loop

Used when the number of iterations is known beforehand.

#include <iostream>
using namespace std;

int main() {
    // Basic for loop
    for (int i = 1; i <= 5; i++) {
        cout << i << " ";
    }
    cout << endl;

    // For loop with multiple variables
    for (int i = 0, j = 10; i < j; i++, j--) {
        cout << "i = " << i << ", j = " << j << endl;
    }

    // Range-based for loop (C++11 and later)
    int numbers[] = {1, 2, 3, 4, 5};
    for (int num : numbers) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

2. while Loop

Executes as long as a condition remains true. Condition is checked before the loop body executes.

#include <iostream>
using namespace std;

int main() {
    int count = 1;

    while (count <= 5) {
        cout << count << " ";
        count++;
    }
    cout << endl;

    return 0;
}

3. do-while Loop

Similar to while, but condition is checked after the loop body executes, ensuring at least one execution.

#include <iostream>
using namespace std;

int main() {
    int count = 1;

    do {
        cout << count << " ";
        count++;
    } while (count <= 5);
    cout << endl;

    // Even if the condition is initially false, the loop executes once
    int num = 10;
    do {
        cout << "This will print once even though the condition is false" << endl;
    } while (num < 5);

    return 0;
}

Nested Loops ➿

Loops can be nested within each other for complex iterations.

#include <iostream>
using namespace std;

int main() {
    // Print a pattern
    for (int i = 1; i <= 5; i++) {
        for (int j = 1; j <= i; j++) {
            cout << "* ";
        }
        cout << endl;
    }

    return 0;
}

Output:

* 
* * 
* * * 
* * * * 
* * * * *

Jump Statements 🦘

1. break Statement

Terminates the loop or switch statement and transfers control to the statement immediately following the terminated statement.

#include <iostream>
using namespace std;

int main() {
    // Breaking out of a loop
    for (int i = 1; i <= 10; i++) {
        if (i == 6) {
            break; // Exit the loop when i equals 6
        }
        cout << i << " ";
    }
    cout << endl;

    return 0;
}

Output: 1 2 3 4 5

2. continue Statement

Skips the current iteration and continues with the next iteration of the loop.

#include <iostream>
using namespace std;

int main() {
    // Skipping even numbers
    for (int i = 1; i <= 10; i++) {
        if (i % 2 == 0) {
            continue; // Skip the rest of the loop body for even numbers
        }
        cout << i << " ";
    }
    cout << endl;

    return 0;
}

Output: 1 3 5 7 9

3. goto Statement

Transfers control to a labeled statement. Generally discouraged in modern programming for making code hard to follow.

#include <iostream>
using namespace std;

int main() {
    int i = 1;

start: // Label
    if (i <= 5) {
        cout << i << " ";
        i++;
        goto start; // Jump to the label
    }
    cout << endl;

    return 0;
}

Output: 1 2 3 4 5

Note: Usage of goto is generally discouraged as it can lead to "spaghetti code" and makes the program flow difficult to understand and maintain.

4: Functions in C++ 🧩

Functions are reusable blocks of code that perform specific tasks.

Function Prototypes and Definitions ✏️

A C++ function consists of:

  1. Function prototype: Declaration that tells the compiler about the function name, return type, and parameters
  2. Function definition: Actual implementation with the body of the function
#include <iostream>
using namespace std;

// Function prototype (declaration)
int add(int a, int b);

int main() {
    // Function call
    int sum = add(5, 3);
    cout << "Sum: " << sum << endl;

    return 0;
}

// Function definition
int add(int a, int b) {
    return a + b;
}

Function Components:

  • Return Type: Specifies the data type of the value returned by the function
  • Function Name: Identifier used to call the function
  • Parameters: Variables that receive values when the function is called
  • Function Body: Code that is executed when the function is called

Function Parameter Types 📋

1. Call by Value

Function receives a copy of the argument's value. Changes to the parameter inside the function don't affect the original variable.

#include <iostream>
using namespace std;

// Call by value
void modifyValue(int num) {
    num = num * 2; // This change is local to the function
    cout << "Inside function: " << num << endl;
}

int main() {
    int x = 10;
    cout << "Before function call: " << x << endl;

    modifyValue(x);

    cout << "After function call: " << x << endl; // x remains unchanged

    return 0;
}

Output:

Before function call: 10
Inside function: 20
After function call: 10

2. Call by Reference

Function receives a reference to the original variable. Changes to the parameter inside the function affect the original variable.

#include <iostream>
using namespace std;

// Call by reference using reference parameters
void modifyValue(int &num) {
    num = num * 2; // This changes the original variable
    cout << "Inside function: " << num << endl;
}

int main() {
    int x = 10;
    cout << "Before function call: " << x << endl;

    modifyValue(x);

    cout << "After function call: " << x << endl; // x is modified

    return 0;
}

Output:

Before function call: 10
Inside function: 20
After function call: 20

3. Call by Address/Pointer

Similar to call by reference, but using pointers.

#include <iostream>
using namespace std;

// Call by address using pointers
void modifyValue(int *num) {
    *num = *num * 2; // Dereference and modify
    cout << "Inside function: " << *num << endl;
}

int main() {
    int x = 10;
    cout << "Before function call: " << x << endl;

    modifyValue(&x); // Pass the address of x

    cout << "After function call: " << x << endl; // x is modified

    return 0;
}

Output:

Before function call: 10
Inside function: 20
After function call: 20

Default Parameters 🔧

C++ allows function parameters to have default values. If a value is not provided when the function is called, the default value is used.

#include <iostream>
using namespace std;

// Function with default parameters
void displayInfo(string name, int age = 20, string country = "Unknown") {
    cout << "Name: " << name << ", Age: " << age << ", Country: " << country << endl;
}

int main() {
    displayInfo("Alice", 25, "USA");     // All parameters specified
    displayInfo("Bob", 30);              // country uses default value
    displayInfo("Charlie");              // age and country use default values

    return 0;
}

Output:

Name: Alice, Age: 25, Country: USA
Name: Bob, Age: 30, Country: Unknown
Name: Charlie, Age: 20, Country: Unknown

Inline Functions ⚡

Inline functions are expanded in place when called, potentially improving performance by avoiding function call overhead.

#include <iostream>
using namespace std;

// Inline function
inline int square(int x) {
    return x * x;
}

int main() {
    int num = 5;
    cout << "Square of " << num << " is " << square(num) << endl;

    return 0;
}

Notes on inline functions:

  • Compiler may ignore the inline hint for complex functions
  • Best used for small, frequently called functions
  • Increases code size but reduces function call overhead
  • Must be defined before they are called or in the same file

Function Overloading 🔄

C++ allows multiple functions with the same name but different parameter lists (number or types of parameters).

#include <iostream>
using namespace std;

// Function overloading
int add(int a, int b) {
    cout << "Adding two integers: ";
    return a + b;
}

double add(double a, double b) {
    cout << "Adding two doubles: ";
    return a + b;
}

int add(int a, int b, int c) {
    cout << "Adding three integers: ";
    return a + b + c;
}

string add(string a, string b) {
    cout << "Concatenating two strings: ";
    return a + b;
}

int main() {
    cout << add(5, 3) << endl;
    cout << add(5.2, 3.7) << endl;
    cout << add(5, 3, 2) << endl;
    cout << add("Hello, ", "World!") << endl;

    return 0;
}

Output:

Adding two integers: 8
Adding two doubles: 8.9
Adding three integers: 10
Concatenating two strings: Hello, World!

Function overloading is resolved by:

  • Number of parameters
  • Types of parameters
  • Order of parameters

Note: Function overloading cannot be based solely on the return type.

Recursion 🔄

A function calling itself to solve a problem by breaking it down into smaller, similar subproblems.

#include <iostream>
using namespace std;

// Recursive function to calculate factorial
unsigned long long factorial(int n) {
    // Base case
    if (n == 0 || n == 1) {
        return 1;
    }
    // Recursive case
    return n * factorial(n - 1);
}

int main() {
    int num = 5;
    cout << "Factorial of " << num << " is " << factorial(num) << endl;

    return 0;
}

Output:

Factorial of 5 is 120

Key components of recursion:

  1. Base case(s): Condition(s) under which the function returns a value without further recursion
  2. Recursive case: The function calls itself with modified parameters

Common recursive algorithms:

  • Factorial calculation
  • Fibonacci sequence
  • Binary search
  • Tree traversal
  • Tower of Hanoi

Example: Fibonacci Sequence using Recursion

#include <iostream>
using namespace std;

// Recursive function for Fibonacci number
int fibonacci(int n) {
    // Base cases
    if (n <= 1) {
        return n;
    }
    // Recursive case
    return fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    int terms = 10;
    cout << "Fibonacci Series (first " << terms << " terms):" << endl;

    for (int i = 0; i < terms; i++) {
        cout << fibonacci(i) << " ";
    }
    cout << endl;

    return 0;
}

Output:

Fibonacci Series (first 10 terms):
0 1 1 2 3 5 8 13 21 34

Practice Questions ✏️

  1. Write a program to check if a number is prime using a function.

  2. Create a program with a function that finds the GCD (Greatest Common Divisor) of two numbers using recursion.

  3. Write a program that uses function overloading to calculate the area of different shapes (circle, rectangle, triangle).

  4. Create a program that uses a recursive function to reverse a string.

  5. Write a program with a menu-driven calculator using switch-case and functions.

Sample Solutions

Question 1:

#include <iostream>
#include <cmath>
using namespace std;

// Function to check if a number is prime
bool isPrime(int num) {
    // Handle special cases
    if (num <= 1) {
        return false;
    }
    if (num <= 3) {
        return true;
    }
    if (num % 2 == 0 || num % 3 == 0) {
        return false;
    }

    // Check for factors up to sqrt(num)
    for (int i = 5; i * i <= num; i += 6) {
        if (num % i == 0 || num % (i + 2) == 0) {
            return false;
        }
    }

    return true;
}

int main() {
    int number;

    cout << "Enter a number: ";
    cin >> number;

    if (isPrime(number)) {
        cout << number << " is a prime number." << endl;
    } else {
        cout << number << " is not a prime number." << endl;
    }

    return 0;
}

Question 2:

#include <iostream>
using namespace std;

// Recursive function to find GCD (Euclidean algorithm)
int gcd(int a, int b) {
    // Base case
    if (b == 0) {
        return a;
    }
    // Recursive case
    return gcd(b, a % b);
}

int main() {
    int num1, num2;

    cout << "Enter two positive integers: ";
    cin >> num1 >> num2;

    cout << "GCD of " << num1 << " and " << num2 << " is " << gcd(num1, num2) << endl;

    return 0;
}

Question 3:

#include <iostream>
#include <cmath>
using namespace std;

// Function overloading for area calculation
// Circle
double area(double radius) {
    const double PI = 3.14159;
    return PI * radius * radius;
}

// Rectangle
double area(double length, double width) {
    return length * width;
}

// Triangle (using base and height)
double area(double base, double height, char shape) {
    if (shape == 't' || shape == 'T') {
        return 0.5 * base * height;
    } else {
        cout << "Invalid shape specified." << endl;
        return -1;
    }
}

// Triangle (using three sides - Heron's formula)
double area(double a, double b, double c) {
    double s = (a + b + c) / 2;
    return sqrt(s * (s - a) * (s - b) * (s - c));
}

int main() {
    int choice;

    do {
        cout << "\nCalculate Area of:" << endl;
        cout << "1. Circle" << endl;
        cout << "2. Rectangle" << endl;
        cout << "3. Triangle (base and height)" << endl;
        cout << "4. Triangle (three sides)" << endl;
        cout << "0. Exit" << endl;
        cout << "Enter your choice: ";
        cin >> choice;

        switch (choice) {
            case 1: {
                double radius;
                cout << "Enter the radius: ";
                cin >> radius;
                cout << "Area of circle: " << area(radius) << endl;
                break;
            }
            case 2: {
                double length, width;
                cout << "Enter length and width: ";
                cin >> length >> width;
                cout << "Area of rectangle: " << area(length, width) << endl;
                break;
            }
            case 3: {
                double base, height;
                cout << "Enter base and height: ";
                cin >> base >> height;
                cout << "Area of triangle: " << area(base, height, 't') << endl;
                break;
            }
            case 4: {
                double a, b, c;
                cout << "Enter three sides: ";
                cin >> a >> b >> c;
                if (a + b > c && a + c > b && b + c > a) {
                    cout << "Area of triangle: " << area(a, b, c) << endl;
                } else {
                    cout << "Invalid triangle! The sum of any two sides must be greater than the third side." << endl;
                }
                break;
            }
            case 0:
                cout << "Exiting program..." << endl;
                break;
            default:
                cout << "Invalid choice! Please try again." << endl;
        }
    } while (choice != 0);

    return 0;
}

Question 4:

#include <iostream>
#include <string>
using namespace std;

// Recursive function to reverse a string
string reverseString(string str) {
    // Base case: empty string or single character
    if (str.length() <= 1) {
        return str;
    }

    // Recursive case: first char + reverse of remaining substring
    return reverseString(str.substr(1)) + str[0];
}

int main() {
    string input;

    cout << "Enter a string: ";
    getline(cin, input);

    string reversed = reverseString(input);

    cout << "Original string: " << input << endl;
    cout << "Reversed string: " << reversed << endl;

    return 0;
}

Question 5:

#include <iostream>
using namespace std;

// Function prototypes
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
double power(double base, double exponent);

int main() {
    int choice;
    double num1, num2;

    do {
        // Display menu
        cout << "\n---- Calculator Menu ----" << endl;
        cout << "1. Addition" << endl;
        cout << "2. Subtraction" << endl;
        cout << "3. Multiplication" << endl;
        cout << "4. Division" << endl;
        cout << "5. Power" << endl;
        cout << "0. Exit" << endl;
        cout << "Enter your choice: ";
        cin >> choice;

        // Exit if user chooses 0
        if (choice == 0) {
            cout << "Exiting calculator. Goodbye!" << endl;
            break;
        }

        // Get numbers from user
        if (choice >= 1 && choice <= 5) {
            cout << "Enter first number: ";
            cin >> num1;
            cout << "Enter second number: ";
            cin >> num2;
        }

        // Process the choice
        switch (choice) {
            case 1:
                cout << num1 << " + " << num2 << " = " << add(num1, num2) << endl;
                break;
            case 2:
                cout << num1 << " - " << num2 << " = " << subtract(num1, num2) << endl;
                break;
            case 3:
                cout << num1 << " * " << num2 << " = " << multiply(num1, num2) << endl;
                break;
            case 4:
                if (num2 != 0) {
                    cout << num1 << " / " << num2 << " = " << divide(num1, num2) << endl;
                } else {
                    cout << "Error: Cannot divide by zero!" << endl;
                }
                break;
            case 5:
                cout << num1 << " raised to power " << num2 << " = " << power(num1, num2) << endl;
                break;
            default:
                cout << "Invalid choice! Please try again." << endl;
        }
    } while (true);

    return 0;
}

// Function definitions
double add(double a, double b) {
    return a + b;
}

double subtract(double a, double b) {
    return a - b;
}

double multiply(double a, double b) {
    return a * b;
}

double divide(double a, double b) {
    return a / b;
}

double power(double base, double exponent) {
    double result = 1.0;
    for (int i = 0; i < exponent; i++) {
        result *= base;
    }
    return result;
}

5: Arrays and Strings 📊

Introduction to Arrays 🔢

An array is a collection of elements of the same type stored in contiguous memory locations. It's a fixed-size data structure that provides random access to elements using indices.

Array Declaration and Initialization

#include <iostream>
using namespace std;

int main() {
    // Declaration
    int numbers[5]; // Declares an array of 5 integers

    // Declaration with initialization
    int values[5] = {10, 20, 30, 40, 50};

    // Initialization without specifying size
    int data[] = {5, 10, 15, 20, 25}; // Size determined by the number of elements

    // Partial initialization
    int partial[5] = {1, 2}; // Remaining elements are initialized to 0

    // Accessing array elements
    cout << "Third element: " << values[2] << endl; // Arrays are 0-indexed, so index 2 is the third element

    // Modifying array elements
    values[1] = 25;
    cout << "Modified second element: " << values[1] << endl;

    return 0;
}

Array Size and Bounds

#include <iostream>
using namespace std;

int main() {
    int arr[] = {10, 20, 30, 40, 50};

    // Calculate the size of array
    int size = sizeof(arr) / sizeof(arr[0]);
    cout << "Size of array: " << size << endl;

    // Iterating through array elements
    cout << "Array elements: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    // Range-based for loop (C++11 and later)
    cout << "Using range-based for loop: ";
    for (int num : arr) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

Note: C++ does not perform automatic bounds checking. Accessing elements outside array bounds leads to undefined behavior.

1D and 2D Arrays 📏

One-Dimensional (1D) Arrays

As shown above, a 1D array is a simple list of elements.

#include <iostream>
using namespace std;

int main() {
    // Example: Finding the maximum element in an array
    int numbers[] = {45, 67, 23, 89, 12, 56};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    int max = numbers[0]; // Assume first element is the maximum

    for (int i = 1; i < size; i++) {
        if (numbers[i] > max) {
            max = numbers[i];
        }
    }

    cout << "Maximum element: " << max << endl;

    return 0;
}

Two-Dimensional (2D) Arrays

A 2D array is an array of arrays - essentially a matrix or a table with rows and columns.

#include <iostream>
using namespace std;

int main() {
    // Declaration and initialization of 2D array
    int matrix[3][4] = {
        {10, 20, 30, 40},   // Row 0
        {15, 25, 35, 45},   // Row 1
        {18, 28, 38, 48}    // Row 2
    };

    // Accessing elements
    cout << "Element at row 1, column 2: " << matrix[1][2] << endl;

    // Modifying elements
    matrix[2][3] = 100;

    // Printing the entire 2D array
    cout << "Matrix elements:" << endl;
    for (int i = 0; i < 3; i++) {       // Rows
        for (int j = 0; j < 4; j++) {   // Columns
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }

    return 0;
}

Multi-Dimensional Arrays

C++ supports arrays with more than two dimensions, though they are less common.

// 3D array example
int cube[3][3][3]; // 3x3x3 cube

// Accessing elements
cube[0][1][2] = 45;

// Initialization
int threeDim[2][2][3] = {
    {
        {1, 2, 3},
        {4, 5, 6}
    },
    {
        {7, 8, 9},
        {10, 11, 12}
    }
};

Character Arrays and Strings 📝

Character Arrays

A sequence of characters stored in an array.

#include <iostream>
using namespace std;

int main() {
    // Character array (C-style string)
    char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // Note the null terminator '\0'

    // Simplified initialization
    char message[] = "Hello"; // Compiler adds the null terminator automatically

    // Printing character arrays
    cout << "Greeting: " << greeting << endl;
    cout << "Message: " << message << endl;

    // Reading character array
    char name[50];
    cout << "Enter your name: ";
    cin >> name; // Reads until whitespace
    cout << "Hello, " << name << "!" << endl;

    // Reading a line with spaces
    char fullName[50];
    cout << "Enter your full name: ";
    cin.ignore(); // Clear the input buffer
    cin.getline(fullName, 50);
    cout << "Hello, " << fullName << "!" << endl;

    return 0;
}

Important: C-style strings must end with a null character ('\0'), which marks the end of the string.

C++ String Class

The std::string class provides a more convenient and safer way to handle strings than C-style character arrays.

#include <iostream>
#include <string>
using namespace std;

int main() {
    // Declaration and initialization
    string greeting = "Hello, World!";

    // String operations
    cout << "Original string: " << greeting << endl;
    cout << "Length: " << greeting.length() << endl;
    cout << "Size: " << greeting.size() << endl; // Same as length()

    // Accessing individual characters
    cout << "First character: " << greeting[0] << endl;
    cout << "Using at(): " << greeting.at(1) << endl; // with bounds checking

    // Concatenation
    string firstName = "John";
    string lastName = "Doe";
    string fullName = firstName + " " + lastName;
    cout << "Full name: " << fullName << endl;

    // Appending
    string text = "Hello";
    text += " there";
    cout << "Appended string: " << text << endl;

    // Substring
    string sample = "Programming in C++";
    string sub = sample.substr(0, 11); // Start at index 0, take 11 characters
    cout << "Substring: " << sub << endl;

    // Finding substrings
    string sentence = "C++ is a powerful language";
    size_t pos = sentence.find("powerful");
    if (pos != string::npos) {
        cout << "Found 'powerful' at position: " << pos << endl;
    }

    // Input with spaces
    string userInput;
    cout << "Enter a line of text: ";
    getline(cin, userInput);
    cout << "You entered: " << userInput << endl;

    return 0;
}

String Functions in C++ 🔠

C-style String Functions (from <cstring> header)

#include <iostream>
#include <cstring> // For C-style string functions
using namespace std;

int main() {
    char str1[50] = "Hello";
    char str2[50] = "World";
    char str3[50];

    // strlen() - Length of string
    cout << "Length of str1: " << strlen(str1) << endl;

    // strcpy() - Copy string
    strcpy(str3, str1);
    cout << "str3 after strcpy: " << str3 << endl;

    // strcat() - Concatenate strings
    strcat(str1, str2);
    cout << "str1 after strcat: " << str1 << endl;

    // strcmp() - Compare strings (returns 0 if equal)
    int result = strcmp("Apple", "Banana");
    cout << "strcmp result: " << result << endl;
    // < 0 if str1 is less than str2
    // 0 if str1 is equal to str2
    // > 0 if str1 is greater than str2

    // strchr() - Find first occurrence of a character
    char text[] = "programming";
    char* ptr = strchr(text, 'g');
    if (ptr) {
        cout << "First 'g' found at position: " << (ptr - text) << endl;
    }

    // strstr() - Find first occurrence of a substring
    char haystack[] = "C++ programming is fun";
    char needle[] = "programming";
    char* found = strstr(haystack, needle);
    if (found) {
        cout << "Substring found at position: " << (found - haystack) << endl;
    }

    return 0;
}

String Class Member Functions (from <string> header)

#include <iostream>
#include <string>
using namespace std;

int main() {
    string str = "Hello, C++ Programming!";

    // Basic functions
    cout << "Original string: " << str << endl;
    cout << "Length: " << str.length() << endl;
    cout << "Is empty? " << (str.empty() ? "Yes" : "No") << endl;

    // Modification functions
    string s1 = "Hello";
    s1.append(" World");
    cout << "After append: " << s1 << endl;

    string s2 = "original";
    s2.replace(0, 4, "new"); // Replace "orig" with "new"
    cout << "After replace: " << s2 << endl;

    string s3 = "excess text";
    s3.erase(6, 5); // Erase 5 characters starting from index 6
    cout << "After erase: " << s3 << endl;

    string s4 = "start";
    s4.insert(5, " end"); // Insert at position 5
    cout << "After insert: " << s4 << endl;

    // Search functions
    string text = "Find the position of a word in this text";
    size_t pos1 = text.find("position");
    cout << "Position of 'position': " << pos1 << endl;

    size_t pos2 = text.rfind("in"); // Last occurrence
    cout << "Last occurrence of 'in': " << pos2 << endl;

    // Comparison
    string fruit1 = "apple";
    string fruit2 = "banana";
    int comp = fruit1.compare(fruit2);
    cout << "Comparison result: " << comp << endl;

    // Conversion to C-style string
    const char* cstr = str.c_str();
    cout << "As C-style string: " << cstr << endl;

    return 0;
}

6: Pointers and Dynamic Memory Allocation 🔍

Introduction to Pointers 📌

A pointer is a variable that stores the memory address of another variable.

#include <iostream>
using namespace std;

int main() {
    // Declaring and initializing variables
    int num = 42;
    double pi = 3.14159;

    // Declaring pointers
    int* pNum;      // Pointer to an integer
    double* pPi;    // Pointer to a double

    // Assigning addresses to pointers
    pNum = &num;    // & is the address-of operator
    pPi = &pi;

    // Displaying values and addresses
    cout << "Value of num: " << num << endl;
    cout << "Address of num: " << &num << endl;
    cout << "Value stored in pNum: " << pNum << endl;
    cout << "Value pointed to by pNum: " << *pNum << endl; // * is the dereference operator

    // Modifying the value through pointer
    *pNum = 100;
    cout << "New value of num: " << num << endl;

    return 0;
}

Null Pointers

A pointer that doesn't point to any valid memory location should be assigned NULL or nullptr (C++11).

int* ptr = nullptr; // Modern way (C++11 and later)
int* ptr2 = NULL;   // Traditional way
int* ptr3 = 0;      // Also valid

Pointer Arithmetic ➕

Pointers can be incremented, decremented, and used in arithmetic operations.

#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* ptr = arr; // Points to the first element of the array

    // Accessing array elements using pointer
    cout << "First element: " << *ptr << endl;
    cout << "Second element: " << *(ptr + 1) << endl;
    cout << "Third element: " << *(ptr + 2) << endl;

    // Incrementing a pointer
    ptr++;
    cout << "After increment, ptr points to: " << *ptr << endl;

    // Decrementing a pointer
    ptr--;
    cout << "After decrement, ptr points to: " << *ptr << endl;

    // Iterating through an array using pointer arithmetic
    cout << "Array elements: ";
    for (int i = 0; i < 5; i++) {
        cout << *(ptr + i) << " ";
    }
    cout << endl;

    // Alternative iteration
    cout << "Using pointer increment: ";
    int* p = arr;
    for (int i = 0; i < 5; i++) {
        cout << *p << " ";
        p++;
    }
    cout << endl;

    return 0;
}

Note: When a pointer is incremented, it points to the next memory location of its data type size. For example, incrementing an int pointer adds 4 bytes (typically).

Dynamic Memory Allocation (new, delete) 🧠

Dynamic memory allocation allows programs to request memory at runtime.

#include <iostream>
using namespace std;

int main() {
    // Allocating single variable
    int* ptr = new int; // Allocate memory for an integer
    *ptr = 10;          // Assign a value
    cout << "Value: " << *ptr << endl;
    delete ptr;         // Free the allocated memory

    // Allocating with initialization
    double* dPtr = new double(3.14159);
    cout << "Double value: " << *dPtr << endl;
    delete dPtr;

    // Allocating arrays
    int size;
    cout << "Enter array size: ";
    cin >> size;

    int* arr = new int[size]; // Allocate dynamic array

    // Initialize array
    for (int i = 0; i < size; i++) {
        arr[i] = i * 10;
    }

    // Display array
    cout << "Array elements: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    delete[] arr; // Free the array memory (note the square brackets)

    return 0;
}

Memory Leaks and Dangling Pointers

// Memory leak example
{
    int* ptr = new int(42);
    // Missing delete statement - memory leak
}

// Dangling pointer example
int* ptr = new int(42);
delete ptr;     // Memory is freed
cout << *ptr;   // Dangling pointer - accessing freed memory (undefined behavior)

// Double delete
int* ptr = new int(42);
delete ptr;
delete ptr;     // Error: double delete

// Avoiding dangling pointers
ptr = nullptr;  // After deleting, set pointer to nullptr

Pointers and Arrays 📊

Arrays and pointers are closely related in C++.

#include <iostream>
using namespace std;

int main() {
    int arr[5] = {10, 20, 30, 40, 50};

    // Array name as a pointer
    cout << "arr points to address: " << arr << endl;        // Address of first element
    cout << "&arr[0] is: " << &arr[0] << endl;               // Same as above
    cout << "First element using arr: " << *arr << endl;     // Same as arr[0]

    // Pointer to array
    int* ptr = arr;

    // Array indexing vs pointer arithmetic
    cout << "Using array indexing: ";
    for (int i = 0; i < 5; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    cout << "Using pointer arithmetic: ";
    for (int i = 0; i < 5; i++) {
        cout << *(ptr + i) << " ";
    }
    cout << endl;

    cout << "Using pointer indexing: ";
    for (int i = 0; i < 5; i++) {
        cout << ptr[i] << " "; // Pointer can use array notation
    }
    cout << endl;

    return 0;
}

Pointer to Multi-dimensional Arrays

#include <iostream>
using namespace std;

int main() {
    int matrix[3][4] = {
        {10, 20, 30, 40},
        {50, 60, 70, 80},
        {90, 100, 110, 120}
    };

    // Pointer to a 1D array of 4 integers
    int (*ptr)[4] = matrix;

    // Accessing elements
    cout << "First element: " << ptr[0][0] << endl;
    cout << "Element at (1,2): " << ptr[1][2] << endl;

    // Iterating through all elements
    cout << "Matrix elements using pointer:" << endl;
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            cout << ptr[i][j] << "\t";
        }
        cout << endl;
    }

    return 0;
}

Pointers to Functions 🔄

Function pointers allow storing and invoking functions indirectly.

#include <iostream>
using namespace std;

// Function declarations
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);

int main() {
    // Declaring a function pointer
    int (*operation)(int, int);

    int x = 15, y = 5;

    // Assigning function addresses to the pointer
    operation = add;
    cout << "Addition: " << operation(x, y) << endl;

    operation = subtract;
    cout << "Subtraction: " << operation(x, y) << endl;

    operation = multiply;
    cout << "Multiplication: " << operation(x, y) << endl;

    operation = divide;
    cout << "Division: " << operation(x, y) << endl;

    return 0;
}

// Function definitions
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }

Using Function Pointers with Arrays

#include <iostream>
using namespace std;

// Function declarations
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);

int main() {
    // Array of function pointers
    int (*operations[4])(int, int) = {add, subtract, multiply, divide};

    int x = 15, y = 5;
    string opNames[4] = {"Addition", "Subtraction", "Multiplication", "Division"};

    // Using the array of function pointers
    for (int i = 0; i < 4; i++) {
        cout << opNames[i] << ": " << operations[i](x, y) << endl;
    }

    return 0;
}

// Function definitions
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }

Function Pointers as Arguments

#include <iostream>
#include <algorithm>
using namespace std;

// Function to compare integers for ascending order
bool ascending(int a, int b) {
    return a < b;
}

// Function to compare integers for descending order
bool descending(int a, int b) {
    return a > b;
}

// Function that takes a function pointer as an argument
void sortArray(int arr[], int size, bool (*compareFunc)(int, int)) {
    for (int i = 0; i < size - 1; i++) {
        for (int j = 0; j < size - i - 1; j++) {
            if (compareFunc(arr[j + 1], arr[j])) {
                // Swap if the comparison function returns true
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}

int main() {
    int numbers[] = {64, 25, 12, 22, 11};
    int size = sizeof(numbers) / sizeof(numbers[0]);

    // Sort in ascending order
    sortArray(numbers, size, ascending);

    cout << "Sorted in ascending order: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;

    // Sort in descending order
    sortArray(numbers, size, descending);

    cout << "Sorted in descending order: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;

    return 0;
}

Practice Questions ✏️

  1. Write a program to reverse the elements of an array using pointers.

  2. Create a program that dynamically allocates memory for a 2D array, initializes it, and then frees the memory.

  3. Write a program to find the length of a string using pointers.

  4. Create a menu-driven program that performs various string operations (concat, compare, copy, length, etc.) using C++ string class.

  5. Implement a simple array-based calculator where functions are selected using function pointers.

Sample Solutions

Question 1:

#include <iostream>
using namespace std;

// Function to reverse array using pointers
void reverseArray(int* arr, int size) {
    int* start = arr;
    int* end = arr + size - 1;

    while (start < end) {
        // Swap elements at start and end
        int temp = *start;
        *start = *end;
        *end = temp;

        // Move pointers towards center
        start++;
        end--;
    }
}

int main() {
    int size;

    cout << "Enter the size of the array: ";
    cin >> size;

    int* arr = new int[size];

    cout << "Enter " << size << " integers: ";
    for (int i = 0; i < size; i++) {
        cin >> arr[i];
    }

    cout << "Original array: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    reverseArray(arr, size);

    cout << "Reversed array: ";
    for (int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;

    delete[] arr;

    return 0;
}

Question 2:

#include <iostream>
using namespace std;

int main() {
    int rows, cols;

    cout << "Enter number of rows: ";
    cin >> rows;
    cout << "Enter number of columns: ";
    cin >> cols;

    // Allocate memory for 2D array
    int** matrix = new int*[rows];
    for (int i = 0; i < rows; i++) {
        matrix[i] = new int[cols];
    }

    // Initialize the matrix
    cout << "Initializing the matrix with row*column values:" << endl;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * j;
        }
    }

    // Display the matrix
    cout << "Matrix elements:" << endl;
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            cout << matrix[i][j] << "\t";
        }
        cout << endl;
    }

    // Free memory (important to avoid memory leaks)
    for (int i = 0; i < rows; i++) {
        delete[] matrix[i];
    }
    delete[] matrix;

    cout << "Memory has been freed." << endl;

    return 0;
}

Question 3:

#include <iostream>
using namespace std;

// Function to find string length using pointers
int stringLength(const char* str) {
    const char* ptr = str;

    while (*ptr != '\0') {
        ptr++;
    }

    return ptr - str;
}

int main() {
    char str[100];

    cout << "Enter a string: ";
    cin.getline(str, 100);

    cout << "Length of the string: " << stringLength(str) << endl;

    // Verify with standard function
    cout << "Length using strlen(): " << strlen(str) << endl;

    return 0;
}

Question 4:

#include <iostream>
#include <string>
using namespace std;

int main() {
    string str1, str2;
    int choice;

    do {
        cout << "\n---- String Operations Menu ----" << endl;
        cout << "1. Enter strings" << endl;
        cout << "2. Concatenate strings" << endl;
        cout << "3. Compare strings" << endl;
        cout << "4. Copy string" << endl;
        cout << "5. Find substring" << endl;
        cout << "6. Get string length" << endl;
        cout << "7. Replace substring" << endl;
        cout << "8. Extract substring" << endl;
        cout << "0. Exit" << endl;
        cout << "Enter your choice: ";
        cin >> choice;

        switch (choice) {
            case 1:
                cin.ignore();
                cout << "Enter first string: ";
                getline(cin, str1);
                cout << "Enter second string: ";
                getline(cin, str2);
                break;

            case 2:
                cout << "Concatenated string: " << str1 + str2 << endl;
                break;

            case 3:
                if (str1 == str2)
                    cout << "Strings are equal" << endl;
                else if (str1 > str2)
                    cout << "First string is greater" << endl;
                else
                    cout << "Second string is greater" << endl;

                cout << "Using compare(): " << str1.compare(str2) << endl;
                break;

            case 4:
                str1 = str2;
                cout << "After copying, str1 = " << str1 << endl;
                break;

            case 5:
                {
                    string substr;
                    cin.ignore();
                    cout << "Enter substring to find: ";
                    getline(cin, substr);

                    size_t pos = str1.find(substr);
                    if (pos != string::npos)
                        cout << "Substring found at position: " << pos << endl;
                    else
                        cout << "Substring not found" << endl;
                }
                break;

            case 6:
                cout << "Length of str1: " << str1.length() << endl;
                cout << "Length of str2: " << str2.length() << endl;
                break;

            case 7:
                {
                    string oldStr, newStr;
                    cin.ignore();
                    cout << "Enter substring to replace: ";
                    getline(cin, oldStr);
                    cout << "Enter new substring: ";
                    getline(cin, newStr);

                    size_t pos = str1.find(oldStr);
                    if (pos != string::npos) {
                        str1.replace(pos, oldStr.length(), newStr);
                        cout << "After replacement: " << str1 << endl;
                    } else {
                        cout << "Substring not found" << endl;
                    }
                }
                break;

            case 8:
                {
                    int start, length;
                    cout << "Enter start position: ";
                    cin >> start;
                    cout << "Enter length to extract: ";
                    cin >> length;

                    if (start < str1.length()) {
                        string substr = str1.substr(start, length);
                        cout << "Extracted substring: " << substr << endl;
                    } else {
                        cout << "Invalid start position" << endl;
                    }
                }
                break;

            case 0:
                cout << "Exiting program..." << endl;
                break;

            default:
                cout << "Invalid choice! Please try again." << endl;
        }
    } while (choice != 0);

    return 0;
}

Question 5:

#include <iostream>
using namespace std;

// Calculator functions
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }

int main() {
    // Array of function pointers
    double (*operations[4])(double, double) = {add, subtract, multiply, divide};
    string opNames[4] = {"Addition", "Subtraction", "Multiplication", "Division"};
    char opSymbols[4] = {'+', '-', '*', '/'};

    // Array to store numbers
    const int MAX_NUMBERS = 10;
    double numbers[MAX_NUMBERS];
    int count = 0;

    int choice;

7: Object-Oriented Programming (OOP) Concepts 🧩

Introduction to OOP 🚀

Object-Oriented Programming is a programming paradigm based on the concept of "objects" that contain data and code. OOP focuses on:

  • Encapsulation: Bundling data and methods that operate on that data
  • Inheritance: Creating new classes from existing ones
  • Polymorphism: Treating objects of different classes through a common interface
  • Abstraction: Hiding implementation details while showing only functionality

Why OOP? 🤔

  • Organizes code around data rather than logic
  • Makes code reusable, modular and easier to maintain
  • Models real-world entities effectively

Classes and Objects 🏗️

Class: Blueprint or template for creating objects Object: Instance of a class

// Class definition
class Car {
private:
    string brand;
    int year;

public:
    // Methods
    void setBrand(string b) { brand = b; }
    void setYear(int y) { year = y; }
    string getBrand() { return brand; }
    int getYear() { return year; }
    void honk() { cout << "Beep! Beep!" << endl; }
};

// Object creation
int main() {
    Car myCar;              // Create object
    myCar.setBrand("Toyota");  // Set properties
    myCar.setYear(2023);

    cout << myCar.getBrand() << " " << myCar.getYear() << endl; // Toyota 2023
    myCar.honk();           // Beep! Beep!

    return 0;
}

Constructors and Destructors 🔨

Constructor: Special method called when an object is created

  • Same name as the class
  • No return type (not even void)
  • Can be overloaded

Destructor: Special method called when an object is destroyed

  • Same name as the class preceded by tilde (~)
  • No parameters or return type
  • Cannot be overloaded
class Rectangle {
private:
    double length;
    double width;

public:
    // Default constructor
    Rectangle() {
        length = 0.0;
        width = 0.0;
        cout << "Default constructor called" << endl;
    }

    // Parameterized constructor
    Rectangle(double l, double w) {
        length = l;
        width = w;
        cout << "Parameterized constructor called" << endl;
    }

    // Copy constructor
    Rectangle(const Rectangle &rect) {
        length = rect.length;
        width = rect.width;
        cout << "Copy constructor called" << endl;
    }

    // Destructor
    ~Rectangle() {
        cout << "Destructor called for Rectangle with length " << length << endl;
    }

    double area() {
        return length * width;
    }
};

int main() {
    Rectangle rect1;                 // Default constructor
    Rectangle rect2(5.0, 3.0);       // Parameterized constructor
    Rectangle rect3 = rect2;         // Copy constructor

    cout << "Area of rect2: " << rect2.area() << endl;  // 15.0

    return 0;  // Destructors called here
}

Access Specifiers 🔐

Control access to class members:

  • private: Members can only be accessed from within the class
  • public: Members can be accessed from outside the class
  • protected: Similar to private, but accessible in derived classes
class BankAccount {
private:
    double balance;     // Only accessible within the class

protected:
    string accountType; // Accessible within this class and derived classes

public:
    string ownerName;   // Accessible from anywhere

    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    double getBalance() {
        return balance;
    }
};

int main() {
    BankAccount account;
    account.ownerName = "John";     // OK (public)
    // account.balance = 1000;      // Error! (private)
    // account.accountType = "Savings"; // Error! (protected)
    account.deposit(500);           // OK (public method)
    cout << account.getBalance();   // OK (public method)

    return 0;
}

8: Operator Overloading and Friend Functions 🔄

Operator Overloading Concepts 🛠️

Operator overloading allows operators to work with user-defined data types.

Basic rules:

  • At least one operand must be a user-defined type
  • Cannot change operator precedence or arity
  • Cannot create new operators
  • Some operators cannot be overloaded (::, ., .*, ?:)

Unary and Binary Operator Overloading ➕➖

Unary Operators: Work on a single operand (++, --, -, !, etc.) Binary Operators: Work on two operands (+, -, *, /, ==, etc.)

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // Unary minus operator overloading
    Complex operator-() {
        return Complex(-real, -imag);
    }

    // Prefix increment operator
    Complex& operator++() {
        real++;
        return *this;
    }

    // Postfix increment operator
    Complex operator++(int) {
        Complex temp = *this;
        ++(*this);
        return temp;
    }

    void display() {
        cout << real << " + " << imag << "i" << endl;
    }
};

int main() {
    Complex c1(3.0, 4.0);
    Complex c2 = -c1;        // Calls operator-()

    c1.display();  // 3 + 4i
    c2.display();  // -3 + -4i

    ++c1;          // Calls prefix operator++()
    c1.display();  // 4 + 4i

    Complex c3 = c1++;  // Calls postfix operator++(int)
    c1.display();  // 5 + 4i
    c3.display();  // 4 + 4i

    return 0;
}

Overloading +, -, *, /, <<, >> ⚙️

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}

    // + operator
    Complex operator+(const Complex& obj) {
        return Complex(real + obj.real, imag + obj.imag);
    }

    // - operator
    Complex operator-(const Complex& obj) {
        return Complex(real - obj.real, imag - obj.imag);
    }

    // * operator
    Complex operator*(const Complex& obj) {
        return Complex(
            real * obj.real - imag * obj.imag,
            real * obj.imag + imag * obj.real
        );
    }

    // Friend function for stream insertion operator <<
    friend ostream& operator<<(ostream& os, const Complex& obj);

    // Friend function for stream extraction operator >>
    friend istream& operator>>(istream& is, Complex& obj);
};

// Implementation of stream insertion operator
ostream& operator<<(ostream& os, const Complex& obj) {
    os << obj.real;
    if (obj.imag >= 0)
        os << " + " << obj.imag << "i";
    else
        os << " - " << -obj.imag << "i";
    return os;
}

// Implementation of stream extraction operator
istream& operator>>(istream& is, Complex& obj) {
    cout << "Enter real part: ";
    is >> obj.real;
    cout << "Enter imaginary part: ";
    is >> obj.imag;
    return is;
}

int main() {
    Complex c1(3, 2), c2(1, 4);

    Complex c3 = c1 + c2;    // (3+2i) + (1+4i) = (4+6i)
    Complex c4 = c1 - c2;    // (3+2i) - (1+4i) = (2-2i)
    Complex c5 = c1 * c2;    // (3+2i) * (1+4i) = (3-8) + (12+2)i = (-5+14i)

    cout << "c1 = " << c1 << endl;
    cout << "c2 = " << c2 << endl;
    cout << "c1 + c2 = " << c3 << endl;
    cout << "c1 - c2 = " << c4 << endl;
    cout << "c1 * c2 = " << c5 << endl;

    Complex c6;
    cin >> c6;
    cout << "You entered: " << c6 << endl;

    return 0;
}

Friend Functions and Friend Classes 👫

Friend Function: A non-member function that has access to private and protected members of a class Friend Class: A class whose members have access to private and protected members of another class

class Rectangle {
private:
    double length;
    double width;

public:
    Rectangle(double l = 0, double w = 0) : length(l), width(w) {}

    // Friend function declaration
    friend double calculateArea(const Rectangle &rect);

    // Friend class declaration
    friend class RectangleHelper;
};

// Friend function definition
double calculateArea(const Rectangle &rect) {
    // Direct access to private members
    return rect.length * rect.width;
}

// Friend class
class RectangleHelper {
public:
    void printDimensions(const Rectangle &rect) {
        // Direct access to private members
        cout << "Length: " << rect.length << ", Width: " << rect.width << endl;
    }

    void setDimensions(Rectangle &rect, double l, double w) {
        // Direct access to private members
        rect.length = l;
        rect.width = w;
    }
};

int main() {
    Rectangle rect(5, 3);
    cout << "Area: " << calculateArea(rect) << endl;  // 15

    RectangleHelper helper;
    helper.printDimensions(rect);  // Length: 5, Width: 3
    helper.setDimensions(rect, 10, 4);
    helper.printDimensions(rect);  // Length: 10, Width: 4

    return 0;
}

Practice Questions 🧠

  1. Basic OOP:

    • Create a Person class with name, age and address as attributes. Implement constructors and methods to get/set these attributes.
  2. Inheritance:

    • Create a Shape base class and derive Circle and Rectangle classes. Implement area calculation for each.
  3. Operator Overloading:

    • Create a Fraction class and overload +, -, *, / operators to work with fractions.
  4. Friend Functions:

    • Implement a Distance class with feet and inches, and create a friend function to add two Distance objects.
  5. Polymorphism:

    • Create a base class Vehicle with a virtual function calculateRent() and derive Car and Bike classes that override this function.

      9: Inheritance 🧬

Types of Inheritance 🌳

Inheritance allows a class (derived/child) to acquire properties and behaviors of another class (base/parent).

1. Single Inheritance

One derived class inherits from one base class.

class Animal {
public:
    void eat() {
        cout << "I can eat!" << endl;
    }
};

class Dog : public Animal {
public:
    void bark() {
        cout << "I can bark! Woof woof!" << endl;
    }
};

int main() {
    Dog dog1;
    dog1.eat();  // Inherited from Animal
    dog1.bark(); // Dog's own method

    return 0;
}

2. Multiple Inheritance

One derived class inherits from multiple base classes.

class Engine {
public:
    void start() {
        cout << "Engine started" << endl;
    }
};

class Vehicle {
public:
    void move() {
        cout << "Vehicle is moving" << endl;
    }
};

class Car : public Engine, public Vehicle {
public:
    void honk() {
        cout << "Car is honking" << endl;
    }
};

int main() {
    Car car1;
    car1.start(); // From Engine
    car1.move();  // From Vehicle
    car1.honk();  // Car's own method

    return 0;
}

3. Multilevel Inheritance

A derived class inherits from another derived class.

class Animal {
public:
    void eat() {
        cout << "I can eat!" << endl;
    }
};

class Mammal : public Animal {
public:
    void breathe() {
        cout << "I can breathe!" << endl;
    }
};

class Dog : public Mammal {
public:
    void bark() {
        cout << "I can bark! Woof woof!" << endl;
    }
};

int main() {
    Dog dog1;
    dog1.eat();    // From Animal (grandparent)
    dog1.breathe(); // From Mammal (parent)
    dog1.bark();   // Dog's own method

    return 0;
}

4. Hierarchical Inheritance

Multiple derived classes inherit from a single base class.

class Animal {
public:
    void eat() {
        cout << "I can eat!" << endl;
    }
};

class Dog : public Animal {
public:
    void bark() {
        cout << "I can bark!" << endl;
    }
};

class Cat : public Animal {
public:
    void meow() {
        cout << "I can meow!" << endl;
    }
};

int main() {
    Dog dog1;
    Cat cat1;

    dog1.eat();  // From Animal
    dog1.bark(); // Dog's own method

    cat1.eat();  // From Animal
    cat1.meow(); // Cat's own method

    return 0;
}

5. Hybrid Inheritance

Combination of multiple types of inheritance.

class Animal {
public:
    void eat() {
        cout << "I can eat!" << endl;
    }
};

class Mammal : public Animal {
public:
    void breathe() {
        cout << "I can breathe!" << endl;
    }
};

class Bird : public Animal {
public:
    void fly() {
        cout << "I can fly!" << endl;
    }
};

class Bat : public Mammal, public Bird {
public:
    void nocturnal() {
        cout << "I am active at night!" << endl;
    }
};

Function Overriding 🔄

Function overriding occurs when a derived class provides a specific implementation for a function already defined in its base class.

class Base {
public:
    void display() {
        cout << "Display of Base" << endl;
    }
};

class Derived : public Base {
public:
    // Function overriding
    void display() {
        cout << "Display of Derived" << endl;
    }
};

int main() {
    Derived derivedObj;
    derivedObj.display(); // Calls Derived's display()

    // To call Base's display
    derivedObj.Base::display();

    return 0;
}

Virtual Base Classes 🔗

Virtual base classes solve the diamond problem in multiple inheritance, where a class inherits from two classes that both inherit from a common base class.

class Animal {
public:
    int age;
    Animal() {
        cout << "Animal constructor called" << endl;
    }
};

// Note the 'virtual' keyword
class Mammal : virtual public Animal {
public:
    Mammal() {
        cout << "Mammal constructor called" << endl;
    }
};

// Note the 'virtual' keyword
class Bird : virtual public Animal {
public:
    Bird() {
        cout << "Bird constructor called" << endl;
    }
};

class Bat : public Mammal, public Bird {
public:
    Bat() {
        cout << "Bat constructor called" << endl;
    }
};

int main() {
    Bat b;
    // Without virtual base class, age would be ambiguous
    b.age = 10; // OK

    return 0;
}

Constructors in Inheritance 🏗️

When a derived class is instantiated, base class constructors are executed first, in order of inheritance, followed by the derived class constructor.

class Base {
public:
    Base() {
        cout << "Base default constructor" << endl;
    }

    Base(int x) {
        cout << "Base parameterized constructor with value " << x << endl;
    }
};

class Derived : public Base {
public:
    // Calls Base default constructor
    Derived() {
        cout << "Derived default constructor" << endl;
    }

    // Calls Base parameterized constructor
    Derived(int x) : Base(x) {
        cout << "Derived parameterized constructor with value " << x << endl;
    }
};

int main() {
    Derived d1;        // Calls both default constructors
    Derived d2(10);    // Calls Base(10) and then Derived(10)

    return 0;
}

10: Polymorphism and Virtual Functions 🔄

Runtime and Compile-time Polymorphism 🔄

Polymorphism allows objects of different classes to be treated as objects of a common superclass.

1. Compile-time Polymorphism (Static Binding)

Achieved through function overloading and operator overloading.

class Calculator {
public:
    // Function overloading
    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() {
    Calculator calc;

    cout << calc.add(5, 10) << endl;          // Calls int version
    cout << calc.add(5.5, 10.5) << endl;      // Calls double version
    cout << calc.add(5, 10, 15) << endl;      // Calls 3-parameter version

    return 0;
}

2. Runtime Polymorphism (Dynamic Binding)

Achieved through virtual functions and function overriding.

class Animal {
public:
    virtual void makeSound() {
        cout << "Animal makes a sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Dog barks: Woof woof!" << endl;
    }
};

class Cat : public Animal {
public:
    void makeSound() override {
        cout << "Cat meows: Meow meow!" << endl;
    }
};

int main() {
    Animal *animal1 = new Dog();
    Animal *animal2 = new Cat();

    animal1->makeSound();  // Dog barks: Woof woof!
    animal2->makeSound();  // Cat meows: Meow meow!

    delete animal1;
    delete animal2;

    return 0;
}

Virtual Functions 🌀

Virtual functions ensure that the correct function is called for an object, regardless of the reference type used.

class Shape {
public:
    virtual double area() {
        cout << "Parent class area" << endl;
        return 0;
    }

    // Non-virtual function
    void display() {
        cout << "This is a shape" << endl;
    }
};

class Rectangle : public Shape {
private:
    double length;
    double width;

public:
    Rectangle(double l, double w) : length(l), width(w) {}

    double area() override {
        cout << "Rectangle area" << endl;
        return length * width;
    }

    void display() {
        cout << "This is a rectangle" << endl;
    }
};

class Circle : public Shape {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    double area() override {
        cout << "Circle area" << endl;
        return 3.14 * radius * radius;
    }

    void display() {
        cout << "This is a circle" << endl;
    }
};

int main() {
    Shape *shape;
    Rectangle rect(5, 3);
    Circle circle(5);

    // Store Rectangle address in Shape pointer
    shape = &rect;
    // Virtual function, calls Rectangle's area()
    shape->area();     // Rectangle area
    // Non-virtual function, calls Shape's display()
    shape->display();  // This is a shape

    // Store Circle address in Shape pointer
    shape = &circle;
    // Virtual function, calls Circle's area()
    shape->area();     // Circle area
    // Non-virtual function, calls Shape's display()
    shape->display();  // This is a shape

    return 0;
}

Virtual Destructors 💫

Important when deleting derived class objects through base class pointers.

class Base {
public:
    Base() {
        cout << "Base constructor" << endl;
    }

    // Without virtual, derived destructor won't be called
    virtual ~Base() {
        cout << "Base destructor" << endl;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived constructor" << endl;
    }

    ~Derived() {
        cout << "Derived destructor" << endl;
    }
};

int main() {
    Base *base = new Derived();
    delete base;  
    // Output with virtual:
    // Base constructor
    // Derived constructor
    // Derived destructor
    // Base destructor

    return 0;
}

Abstract Classes and Pure Virtual Functions 🏛️

Abstract classes cannot be instantiated and must be derived. They contain at least one pure virtual function.

class AbstractEmployee {
public:
    // Pure virtual function (abstract method)
    virtual void calculateSalary() = 0;

    // Regular virtual function with implementation
    virtual void displayDetails() {
        cout << "Employee Details" << endl;
    }

    // Non-virtual function
    void markAttendance() {
        cout << "Attendance marked" << endl;
    }
};

class Developer : public AbstractEmployee {
public:
    // Must implement pure virtual function
    void calculateSalary() override {
        cout << "Developer's salary calculated" << endl;
    }

    // Override regular virtual function
    void displayDetails() override {
        cout << "Developer Details" << endl;
    }
};

class Manager : public AbstractEmployee {
public:
    // Must implement pure virtual function
    void calculateSalary() override {
        cout << "Manager's salary calculated" << endl;
    }
};

int main() {
    // AbstractEmployee emp; // Error! Cannot instantiate abstract class

    Developer dev;
    Manager mgr;

    dev.calculateSalary();  // Developer's salary calculated
    dev.displayDetails();   // Developer Details
    dev.markAttendance();   // Attendance marked

    mgr.calculateSalary();  // Manager's salary calculated
    mgr.displayDetails();   // Employee Details (uses base implementation)
    mgr.markAttendance();   // Attendance marked

    // Polymorphic behavior
    AbstractEmployee *employees[2];
    employees[0] = &dev;
    employees[1] = &mgr;

    for(int i = 0; i < 2; i++) {
        employees[i]->calculateSalary();
        employees[i]->displayDetails();
    }

    return 0;
}

Practice Questions 🧠

  1. Inheritance Implementation:

    • Create a Person base class and derive Student and Teacher classes. Include appropriate attributes and methods.
  2. Diamond Problem:

    • Implement a diamond inheritance hierarchy and solve it using virtual inheritance.
  3. Runtime Polymorphism:

    • Create a Shape hierarchy with Circle, Rectangle, and Triangle. Use virtual functions to calculate area and perimeter.
  4. Abstract Class:

    • Design an abstract Account class with derived classes SavingsAccount and CheckingAccount. Implement appropriate pure virtual functions.
  5. Constructors in Inheritance:

    • Create a multilevel inheritance hierarchy and trace the order of constructor calls.

11: Templates and Exception Handling 🧩

Function Templates 🔄

Templates allow you to write generic functions that work with different data types without code duplication.

// Function template
template <typename T>
T maximum(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    // Integer comparison
    cout << "Max of 3 and 7: " << maximum<int>(3, 7) << endl;

    // Double comparison
    cout << "Max of 3.5 and 7.2: " << maximum<double>(3.5, 7.2) << endl;

    // Character comparison (compares ASCII values)
    cout << "Max of 'a' and 'z': " << maximum<char>('a', 'z') << endl;

    // Type deduction (compiler determines the type)
    cout << "Max of 13 and 24: " << maximum(13, 24) << endl;

    return 0;
}

Template with Multiple Parameters

template <typename T1, typename T2>
void printPair(T1 a, T2 b) {
    cout << "Pair: " << a << ", " << b << endl;
}

int main() {
    printPair<int, string>(10, "Hello");
    printPair<double, char>(3.14, 'A');

    // Type deduction
    printPair(100, "World");
    printPair(7.5, 'B');

    return 0;
}

Function Templates with Non-Type Parameters

template <typename T, int size>
void printArray(T arr[]) {
    cout << "Array elements: ";
    for(int i = 0; i < size; i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
}

int main() {
    int numbers[5] = {1, 2, 3, 4, 5};
    char letters[3] = {'a', 'b', 'c'};

    printArray<int, 5>(numbers);
    printArray<char, 3>(letters);

    return 0;
}

Class Templates 📦

Class templates allow you to create classes that can work with different data types.

template <typename T>
class Box {
private:
    T item;

public:
    Box() {}

    Box(T item) : item(item) {}

    void setItem(T newItem) {
        item = newItem;
    }

    T getItem() const {
        return item;
    }
};

int main() {
    // Box of integer
    Box<int> intBox(123);
    cout << "Int Box contains: " << intBox.getItem() << endl;

    // Box of string
    Box<string> stringBox("Hello World");
    cout << "String Box contains: " << stringBox.getItem() << endl;

    // Box of double
    Box<double> doubleBox;
    doubleBox.setItem(3.14159);
    cout << "Double Box contains: " << doubleBox.getItem() << endl;

    return 0;
}

Class Template with Multiple Parameters

template <typename T, typename U>
class Pair {
private:
    T first;
    U second;

public:
    Pair(T first, U second) : first(first), second(second) {}

    T getFirst() const { return first; }
    U getSecond() const { return second; }

    void setFirst(T newFirst) { first = newFirst; }
    void setSecond(U newSecond) { second = newSecond; }

    void display() const {
        cout << "Pair: (" << first << ", " << second << ")" << endl;
    }
};

int main() {
    Pair<int, string> student(101, "John");
    student.display();  // Pair: (101, John)

    Pair<string, double> product("Laptop", 999.99);
    product.display();  // Pair: (Laptop, 999.99)

    return 0;
}

Template Specialization

template <typename T>
class DataProcessor {
public:
    void process(T data) {
        cout << "Processing generic data: " << data << endl;
    }
};

// Template specialization for string type
template <>
class DataProcessor<string> {
public:
    void process(string data) {
        cout << "Processing string data: " << data << endl;
        cout << "String length: " << data.length() << endl;
    }
};

int main() {
    DataProcessor<int> intProcessor;
    intProcessor.process(42);  // Processing generic data: 42

    DataProcessor<string> stringProcessor;
    stringProcessor.process("Hello");  // Processing string data: Hello
                                     // String length: 5

    return 0;
}

Exception Handling ⚠️

Exception handling provides a way to handle runtime errors gracefully.

Basic Exception Handling

#include <iostream>
#include <stdexcept>  // For standard exceptions
using namespace std;

int main() {
    try {
        int numerator = 10;
        int denominator = 0;

        if (denominator == 0) {
            throw runtime_error("Division by zero!");
        }

        int result = numerator / denominator;
        cout << "Result: " << result << endl;
    }
    catch (const runtime_error& e) {
        cout << "Caught exception: " << e.what() << endl;
    }

    cout << "Program continues after exception" << endl;

    return 0;
}

Multiple Catch Blocks

#include <iostream>
#include <stdexcept>
using namespace std;

int main() {
    try {
        int choice;
        cout << "Enter 1 for runtime_error, 2 for out_of_range, 3 for integer exception: ";
        cin >> choice;

        if (choice == 1) {
            throw runtime_error("Runtime error occurred");
        }
        else if (choice == 2) {
            throw out_of_range("Index out of range");
        }
        else if (choice == 3) {
            throw 42;  // Throwing an integer
        }

        cout << "No exception thrown" << endl;
    }
    catch (const runtime_error& e) {
        cout << "Caught runtime_error: " << e.what() << endl;
    }
    catch (const out_of_range& e) {
        cout << "Caught out_of_range: " << e.what() << endl;
    }
    catch (int e) {
        cout << "Caught integer exception: " << e << endl;
    }
    catch (...) {  // Catch-all handler
        cout << "Caught unknown exception" << endl;
    }

    return 0;
}

Custom Exceptions

#include <iostream>
#include <stdexcept>
using namespace std;

// Custom exception class
class InsufficientFundsException : public exception {
private:
    string message;

public:
    InsufficientFundsException(double amount, double balance) {
        message = "Cannot withdraw $" + to_string(amount) + 
                 ". Current balance is $" + to_string(balance);
    }

    const char* what() const noexcept override {
        return message.c_str();
    }
};

class BankAccount {
private:
    double balance;

public:
    BankAccount(double initialBalance) : balance(initialBalance) {}

    void deposit(double amount) {
        if (amount <= 0) {
            throw invalid_argument("Deposit amount must be positive");
        }
        balance += amount;
    }

    void withdraw(double amount) {
        if (amount <= 0) {
            throw invalid_argument("Withdrawal amount must be positive");
        }

        if (amount > balance) {
            throw InsufficientFundsException(amount, balance);
        }

        balance -= amount;
    }

    double getBalance() const {
        return balance;
    }
};

int main() {
    try {
        BankAccount account(1000);

        cout << "Initial balance: $" << account.getBalance() << endl;

        account.deposit(500);
        cout << "After deposit: $" << account.getBalance() << endl;

        account.withdraw(200);
        cout << "After withdrawal: $" << account.getBalance() << endl;

        // This will throw an exception
        account.withdraw(2000);
    }
    catch (const InsufficientFundsException& e) {
        cout << "Error: " << e.what() << endl;
    }
    catch (const invalid_argument& e) {
        cout << "Invalid argument: " << e.what() << endl;
    }
    catch (const exception& e) {
        cout << "Standard exception: " << e.what() << endl;
    }

    return 0;
}

Exception Handling in Functions

void functionA() {
    cout << "Entering functionA" << endl;
    throw runtime_error("Exception from functionA");
    cout << "This line will not execute" << endl;
}

void functionB() {
    cout << "Entering functionB" << endl;
    functionA();
    cout << "This line will not execute" << endl;
}

void functionC() {
    cout << "Entering functionC" << endl;
    try {
        functionB();
    }
    catch (const exception& e) {
        cout << "Caught in functionC: " << e.what() << endl;
    }
    cout << "Exiting functionC" << endl;
}

int main() {
    functionC();
    return 0;
}

12: File Handling 📁

File Streams 📄

C++ provides three classes for file operations:

  • ifstream: For reading from files
  • ofstream: For writing to files
  • fstream: For both reading and writing
#include <iostream>
#include <fstream>  // For file operations
#include <string>
using namespace std;

int main() {
    // Writing to a file
    ofstream outFile("sample.txt");

    if (outFile.is_open()) {
        outFile << "This is a line of text." << endl;
        outFile << "This is another line of text." << endl;
        outFile << 42 << endl;
        outFile.close();
        cout << "Data written to file successfully." << endl;
    }
    else {
        cout << "Failed to open file for writing." << endl;
    }

    // Reading from a file
    ifstream inFile("sample.txt");
    string line;

    if (inFile.is_open()) {
        cout << "Reading file contents:" << endl;
        while (getline(inFile, line)) {
            cout << line << endl;
        }
        inFile.close();
    }
    else {
        cout << "Failed to open file for reading." << endl;
    }

    return 0;
}

File Read and Write Operations 🔍

Reading and Writing Different Data Types

// Writing various data types
ofstream outFile("data.txt");

if (outFile.is_open()) {
    int num = 42;
    double pi = 3.14159;
    string name = "John Doe";

    outFile << num << endl;
    outFile << pi << endl;
    outFile << name << endl;
    outFile.close();
}

// Reading various data types
ifstream inFile("data.txt");

if (inFile.is_open()) {
    int num;
    double pi;
    string name;

    inFile >> num;
    inFile >> pi;
    inFile >> name;  // This will only read "John"

    cout << "Number: " << num << endl;
    cout << "Pi: " << pi << endl;
    cout << "Name: " << name << endl;

    inFile.close();
}

Reading Words vs. Lines

ofstream outFile("text.txt");
if (outFile.is_open()) {
    outFile << "This is the first line." << endl;
    outFile << "This is the second line with multiple words." << endl;
    outFile.close();
}

// Reading word by word
ifstream inFile1("text.txt");
if (inFile1.is_open()) {
    string word;
    cout << "Reading word by word:" << endl;
    while (inFile1 >> word) {
        cout << word << endl;
    }
    inFile1.close();
}

// Reading line by line
ifstream inFile2("text.txt");
if (inFile2.is_open()) {
    string line;
    cout << "\nReading line by line:" << endl;
    while (getline(inFile2, line)) {
        cout << line << endl;
    }
    inFile2.close();
}

Character-by-Character Reading

ifstream inFile("sample.txt");
if (inFile.is_open()) {
    char ch;
    cout << "Reading character by character:" << endl;
    while (inFile.get(ch)) {
        cout << ch;
    }
    inFile.close();
}

Reading and Writing Binary Data

struct Person {
    char name[50];
    int age;
    double salary;
};

// Writing binary data
Person person1 = {"John Doe", 30, 75000.50};

ofstream outFile("person.dat", ios::binary);
if (outFile.is_open()) {
    outFile.write((char*)&person1, sizeof(Person));
    outFile.close();
}

// Reading binary data
Person person2;

ifstream inFile("person.dat", ios::binary);
if (inFile.is_open()) {
    inFile.read((char*)&person2, sizeof(Person));
    inFile.close();

    cout << "Name: " << person2.name << endl;
    cout << "Age: " << person2.age << endl;
    cout << "Salary: " << person2.salary << endl;
}

File Modes 🔐

File modes determine how files are opened for reading and writing.

// Common file modes:
// ios::in    - Open for reading (default for ifstream)
// ios::out   - Open for writing (default for ofstream)
// ios::app   - Append to the end of the file
// ios::ate   - Position at the end of the file when opened
// ios::trunc - Delete contents when opening (default with ios::out)
// ios::binary - Open in binary mode

// Opening a file for reading
ifstream file1("example.txt", ios::in);

// Opening a file for writing (will truncate existing content)
ofstream file2("example.txt", ios::out);

// Opening a file for appending
ofstream file3("example.txt", ios::app);

// Opening a file for both reading and writing
fstream file4("example.txt", ios::in | ios::out);

// Opening a file for binary reading
ifstream file5("data.bin", ios::in | ios::binary);

// Opening a file, positioning at the end, but allowing reading/writing anywhere
fstream file6("example.txt", ios::in | ios::out | ios::ate);

File Position Operations

fstream file("example.txt", ios::in | ios::out);
if (file.is_open()) {
    // Get current position
    streampos currentPos = file.tellg();
    cout << "Current position: " << currentPos << endl;

    // Seek to position 10 from beginning
    file.seekg(10, ios::beg);

    // Read character at position 10
    char ch;
    file.get(ch);
    cout << "Character at position 10: " << ch << endl;

    // Seek to 5 characters from current position
    file.seekg(5, ios::cur);

    // Read character
    file.get(ch);
    cout << "Character 5 positions later: " << ch << endl;

    // Seek to 10 characters from end
    file.seekg(-10, ios::end);

    // Read character
    file.get(ch);
    cout << "Character 10 positions from end: " << ch << endl;

    file.close();
}

Checking File State

ifstream file("nonexistent.txt");

// Check if file opened successfully
if (!file) {
    cout << "Failed to open file!" << endl;
}

// Alternative checks
if (file.fail()) {
    cout << "File operation failed!" << endl;
}

// Read beyond end of file
string word;
file >> word;
if (file.eof()) {
    cout << "End of file reached!" << endl;
}

// Check for any errors
if (file.bad()) {
    cout << "I/O error occurred!" << endl;
}

// Clear error flags
file.clear();

// Is the file still good?
if (file.good()) {
    cout << "File is in good state!" << endl;
}

Practice Questions 🧠

  1. Function Templates:

    • Create a function template to find the minimum and maximum elements in an array of any data type.
  2. Class Templates:

    • Implement a template Stack class with push, pop, and display operations.
  3. Exception Handling:

    • Write a program that reads integers from the user and handles various exceptions like division by zero, out of range values, and invalid input.
  4. Custom Exception:

    • Create a custom exception class for a shopping cart that throws exceptions when items exceed the maximum limit or when trying to remove non-existent items.
  5. File Operations:

    • Write a program that reads a text file, counts the number of words, lines, and characters, and writes the statistics to another file.
  6. Student Database:

    • Create a student database program that uses file handling to store and retrieve student information (roll number, name, marks).

      13: Standard Template Library (STL) 📚

Introduction to STL 🌟

The Standard Template Library (STL) is a powerful set of C++ template classes that provides common programming data structures and functions such as lists, stacks, arrays, etc. The STL consists of:

  • Containers: Objects that store data
  • Algorithms: Functions that process data
  • Iterators: Objects that connect algorithms with containers
  • Function objects: Objects that act like functions
  • Allocators: Objects that handle memory allocation

The STL allows for efficient, reusable, and adaptable software design.

Containers 📦

Vector 📊

Vectors are dynamic arrays that can resize themselves automatically when elements are added or removed.

#include <vector>

int main() {
    // Declaration
    std::vector<int> numbers;

    // Adding elements
    numbers.push_back(10);
    numbers.push_back(20);
    numbers.push_back(30);

    // Accessing elements
    std::cout << "First element: " << numbers[0] << std::endl;
    std::cout << "Size: " << numbers.size() << std::endl;

    // Iterating through elements
    for(int num : numbers) {
        std::cout << num << " ";
    }

    return 0;
}

List 📝

Lists are implemented as doubly-linked lists, allowing efficient insertions and deletions anywhere in the sequence.

#include <list>

int main() {
    std::list<int> myList;

    // Adding elements
    myList.push_back(10);
    myList.push_front(5);
    myList.push_back(15);

    // Traversing using iterator
    for(auto it = myList.begin(); it != myList.end(); ++it) {
        std::cout << *it << " ";  // Output: 5 10 15
    }

    return 0;
}

Deque 🔄

Double-ended queues support fast insertions and deletions at both the beginning and the end.

#include <deque>

int main() {
    std::deque<int> myDeque;

    // Adding elements at both ends
    myDeque.push_back(30);
    myDeque.push_front(10);
    myDeque.push_back(40);
    myDeque.push_front(5);

    // Output: 5 10 30 40
    for(int i : myDeque) {
        std::cout << i << " ";
    }

    return 0;
}

Map 🗺️

Maps store key-value pairs sorted by keys.

#include <map>
#include <string>

int main() {
    std::map<std::string, int> ages;

    // Inserting key-value pairs
    ages["Alice"] = 30;
    ages["Bob"] = 25;
    ages["Charlie"] = 35;

    // Accessing values
    std::cout << "Bob's age: " << ages["Bob"] << std::endl;

    // Checking if key exists
    if(ages.find("David") == ages.end()) {
        std::cout << "David is not in the map" << std::endl;
    }

    // Iterating through key-value pairs
    for(const auto& pair : ages) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    return 0;
}

Set 🎯

Sets store unique elements in a sorted order.

#include <set>

int main() {
    std::set<int> uniqueNumbers;

    // Inserting elements
    uniqueNumbers.insert(30);
    uniqueNumbers.insert(10);
    uniqueNumbers.insert(30);  // Duplicate, won't be inserted
    uniqueNumbers.insert(20);

    // Output: 10 20 30 (sorted and unique)
    for(int num : uniqueNumbers) {
        std::cout << num << " ";
    }

    return 0;
}

Iterators and Algorithms 🔄

Iterators provide a way to access container elements sequentially without exposing the underlying structure.

Types of Iterators:

  • Input iterators: Read forward (one pass)
  • Output iterators: Write forward (one pass)
  • Forward iterators: Read/write forward (multiple passes)
  • Bidirectional iterators: Read/write forward/backward
  • Random access iterators: Read/write with random access

Common Algorithms:

#include <vector>
#include <algorithm>

int main() {
    std::vector<int> nums = {5, 2, 8, 1, 9};

    // Sorting
    std::sort(nums.begin(), nums.end());
    // nums = {1, 2, 5, 8, 9}

    // Finding
    auto it = std::find(nums.begin(), nums.end(), 5);
    if(it != nums.end()) {
        std::cout << "Found 5 at position: " << (it - nums.begin()) << std::endl;
    }

    // Transforming elements
    std::transform(nums.begin(), nums.end(), nums.begin(), 
                  [](int x) { return x * 2; });
    // nums = {2, 4, 10, 16, 18}

    // Counting occurrences
    int count = std::count(nums.begin(), nums.end(), 10);

    return 0;
}

14: Advanced C++ Concepts 🔥

Lambda Expressions λ

Lambda expressions are anonymous functions that can be defined inline. They're useful for short functions used in limited contexts.

#include <iostream>
#include <vector>
#include <algorithm>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    // Basic lambda expression
    auto print = [](int n) { std::cout << n << " "; };
    std::for_each(numbers.begin(), numbers.end(), print);

    // Lambda with capture
    int threshold = 3;
    auto count_above = [threshold](const std::vector<int>& vec) {
        return std::count_if(vec.begin(), vec.end(), 
                           [threshold](int n) { return n > threshold; });
    };

    std::cout << "\nNumbers above " << threshold << ": " 
              << count_above(numbers) << std::endl;

    return 0;
}

Smart Pointers 🧠

Smart pointers automate memory management, reducing the risk of memory leaks and making code safer.

unique_ptr

Exclusive ownership - only one unique_ptr can own a resource.

#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource released\n"; }
    void use() { std::cout << "Resource being used\n"; }
};

int main() {
    // Creating a unique_ptr
    std::unique_ptr<Resource> res1 = std::make_unique<Resource>();

    // Using the resource
    res1->use();

    // Transfer ownership (move)
    std::unique_ptr<Resource> res2 = std::move(res1);

    // res1 is now null
    if(!res1) {
        std::cout << "res1 is null\n";
    }

    // res2 owns the resource
    res2->use();

    // Resource is automatically released when res2 goes out of scope
    return 0;
}

shared_ptr

Shared ownership - multiple shared_ptr objects can own the same resource.

#include <memory>

int main() {
    // Creating a shared_ptr
    std::shared_ptr<Resource> res1 = std::make_shared<Resource>();

    {
        // Creating another shared_ptr that shares ownership
        std::shared_ptr<Resource> res2 = res1;

        std::cout << "Reference count: " << res1.use_count() << std::endl; // 2

        // Both pointers can use the resource
        res1->use();
        res2->use();

        // res2 goes out of scope here, but resource is not deleted
    }

    // Reference count decreases
    std::cout << "Reference count: " << res1.use_count() << std::endl; // 1

    // Resource is deleted when the last shared_ptr goes out of scope
    return 0;
}

Multithreading Basics 🧵

C++11 introduced standard library support for multithreading.

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mtx; // Mutex for synchronizing access to std::cout

void printMessage(const std::string& message, int count) {
    for(int i = 0; i < count; ++i) {
        // Lock to prevent interleaved output
        std::lock_guard<std::mutex> lock(mtx);
        std::cout << message << " " << i << std::endl;
    }
}

int main() {
    // Create threads
    std::thread t1(printMessage, "Thread 1", 5);
    std::thread t2(printMessage, "Thread 2", 5);

    // Wait for threads to finish
    t1.join();
    t2.join();

    std::cout << "All threads completed" << std::endl;

    return 0;
}

Design Patterns 🏗️

Design patterns are reusable solutions to common problems in software design.

Singleton Pattern

Ensures a class has only one instance and provides a global point of access to it.

class Singleton {
private:
    // Private constructor
    Singleton() {}

    // Delete copy constructor and assignment operator
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    static Singleton* instance;

public:
    static Singleton* getInstance() {
        if(instance == nullptr) {
            instance = new Singleton();
        }
        return instance;
    }

    void someBusinessLogic() {
        // Do something...
    }
};

// Initialize static member
Singleton* Singleton::instance = nullptr;

Factory Method Pattern

Defines an interface for creating an object, but lets subclasses decide which class to instantiate.

// Product interface
class Button {
public:
    virtual void render() = 0;
    virtual void onClick() = 0;
    virtual ~Button() = default;
};

// Concrete products
class WindowsButton : public Button {
public:
    void render() override {
        std::cout << "Rendering a Windows button\n";
    }

    void onClick() override {
        std::cout << "Windows button clicked\n";
    }
};

class MacButton : public Button {
public:
    void render() override {
        std::cout << "Rendering a Mac button\n";
    }

    void onClick() override {
        std::cout << "Mac button clicked\n";
    }
};

// Creator abstract class
class Dialog {
public:
    void renderWindow() {
        // Create a button
        Button* button = createButton();
        // Use the button
        button->render();
        button->onClick();
        delete button;
    }

    // Factory method
    virtual Button* createButton() = 0;
    virtual ~Dialog() = default;
};

// Concrete creators
class WindowsDialog : public Dialog {
public:
    Button* createButton() override {
        return new WindowsButton();
    }
};

class MacDialog : public Dialog {
public:
    Button* createButton() override {
        return new MacButton();
    }
};

❓ Practice Questions

  1. What is the main difference between std::vector and std::list?
  2. When would you use std::deque instead of std::vector?
  3. Write a lambda expression that takes two integers and returns their sum.
  4. What's the difference between unique_ptr and shared_ptr?
  5. How would you safely share data between multiple threads?
  6. Implement a simple thread-safe singleton pattern.
  7. When would you choose a map over an unordered_map?
  8. Write code to find the maximum element in a vector using STL algorithms.
  9. Explain how you would use the Observer design pattern in C++.
  10. What are the advantages of using smart pointers over raw pointers?

Tags

Post a Comment

0Comments
Post a Comment (0)
Let's Chat
Let's Chat

Let's Make Magic Happen! ✨

Drop us a message or join our whatsapp group