📚 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 ⚙️
- Writing the code: Create a file with .cpp extension (e.g., program.cpp)
- Compilation: Convert source code to executable
- Using g++: g++ program.cpp -o program
- Using Visual Studio: Build project from IDE
- 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 = #
- 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:
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)
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)
Logical Operators
- AND: && (e.g., a && b)
- OR: || (e.g., a || b)
- NOT: ! (e.g., !a)
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: %=
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):
- C-style casting:
double d = 3.14;
int i = (int)d; // Explicitly converts double to int, i = 3
- Function-style casting:
double d = 3.14;
int i = int(d); // Equivalent to C-style casting
- 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 ✏️
Write a C++ program to display "Hello, World!" on the screen.
Create a program that takes two integers as input and displays their sum, difference, product, and quotient.
Write a program to convert temperature from Celsius to Fahrenheit using the formula: F = (C × 9/5) + 32.
Create a program that calculates the area of a circle when the radius is provided by the user.
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:
- Function prototype: Declaration that tells the compiler about the function name, return type, and parameters
- 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:
- Base case(s): Condition(s) under which the function returns a value without further recursion
- 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 ✏️
Write a program to check if a number is prime using a function.
Create a program with a function that finds the GCD (Greatest Common Divisor) of two numbers using recursion.
Write a program that uses function overloading to calculate the area of different shapes (circle, rectangle, triangle).
Create a program that uses a recursive function to reverse a string.
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 = # // & is the address-of operator
pPi = π
// 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 ✏️
Write a program to reverse the elements of an array using pointers.
Create a program that dynamically allocates memory for a 2D array, initializes it, and then frees the memory.
Write a program to find the length of a string using pointers.
Create a menu-driven program that performs various string operations (concat, compare, copy, length, etc.) using C++ string class.
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 🧠
Basic OOP:
- Create a Person class with name, age and address as attributes. Implement constructors and methods to get/set these attributes.
Inheritance:
- Create a Shape base class and derive Circle and Rectangle classes. Implement area calculation for each.
Operator Overloading:
- Create a Fraction class and overload +, -, *, / operators to work with fractions.
Friend Functions:
- Implement a Distance class with feet and inches, and create a friend function to add two Distance objects.
Polymorphism:
- Create a base class Vehicle with a virtual function calculateRent() and derive Car and Bike classes that override this function.
9: Inheritance 🧬
- Create a base class Vehicle with a virtual function calculateRent() and derive Car and Bike classes that override this function.
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 = ▭
// 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 🧠
Inheritance Implementation:
- Create a Person base class and derive Student and Teacher classes. Include appropriate attributes and methods.
Diamond Problem:
- Implement a diamond inheritance hierarchy and solve it using virtual inheritance.
Runtime Polymorphism:
- Create a Shape hierarchy with Circle, Rectangle, and Triangle. Use virtual functions to calculate area and perimeter.
Abstract Class:
- Design an abstract Account class with derived classes SavingsAccount and CheckingAccount. Implement appropriate pure virtual functions.
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 🧠
Function Templates:
- Create a function template to find the minimum and maximum elements in an array of any data type.
Class Templates:
- Implement a template Stack class with push, pop, and display operations.
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.
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.
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.
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) 📚
- Create a student database program that uses file handling to store and retrieve student information (roll number, name, marks).
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
- What is the main difference between std::vector and std::list?
- When would you use std::deque instead of std::vector?
- Write a lambda expression that takes two integers and returns their sum.
- What's the difference between unique_ptr and shared_ptr?
- How would you safely share data between multiple threads?
- Implement a simple thread-safe singleton pattern.
- When would you choose a map over an unordered_map?
- Write code to find the maximum element in a vector using STL algorithms.
- Explain how you would use the Observer design pattern in C++.
- What are the advantages of using smart pointers over raw pointers?