0% found this document useful (0 votes)
31 views30 pages

Pages From OPP SUMMERISED Part 3

The document discusses key concepts of Object-Oriented Programming (OOP) in C++, including polymorphism, data abstraction, and encapsulation. It explains compile-time and run-time polymorphism, the use of abstract classes and pure virtual functions, and the importance of access specifiers for data encapsulation. Additionally, it provides best practices for implementing these concepts effectively in C++.

Uploaded by

Jibril Wk
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
31 views30 pages

Pages From OPP SUMMERISED Part 3

The document discusses key concepts of Object-Oriented Programming (OOP) in C++, including polymorphism, data abstraction, and encapsulation. It explains compile-time and run-time polymorphism, the use of abstract classes and pure virtual functions, and the importance of access specifiers for data encapsulation. Additionally, it provides best practices for implementing these concepts effectively in C++.

Uploaded by

Jibril Wk
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 30

}

return *this;
}

void display() {
cout << str << endl;
}

~MyString() {
delete[] str;
}
};

int main() {
MyString s1("Hello"), s2;
s2 = s1; // Uses overloaded = operator
s2.display(); // Output: Hello
return 0;
}

6. Best Practices
1. Use function overloading to provide multiple ways to perform similar tasks.
2. Use operator overloading to make user-defined types behave like built-in types.
3. Avoid overloading operators in a way that makes the code less intuitive.
4. Always handle self-assignment when overloading the assignment operator.

26. Polymorphism
Polymorphism in C++
Polymorphism is a core concept in Object-Oriented Programming (OOP) that allows objects
of different classes to be treated as objects of a common base class. It enables flexibility and
extensibility in code by allowing a single interface to represent different underlying forms (data
types).

1. Types of Polymorphism

Polymorphism in C++ can be categorized into two types:

1. Compile-Time Polymorphism (Static Binding)


o Achieved through function overloading and operator overloading.
o The function to be executed is determined at compile time.
2. Run-Time Polymorphism (Dynamic Binding)
o Achieved through function overriding and virtual functions.
o The function to be executed is determined at run time.

2. Compile-Time Polymorphism
a) Function Overloading

• Multiple functions with the same name but different parameters.

Example:
#include <iostream>
using namespace std;

void print(int i) {
cout << "Integer: " << i << endl;
}

void print(double d) {
cout << "Double: " << d << endl;
}

int main() {
print(5); // Output: Integer: 5
print(3.14); // Output: Double: 3.14
return 0;
}
b) Operator Overloading

• Defining custom behavior for operators when used with user-defined types.

Example:
#include <iostream>
using namespace std;

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

// Overload the + operator


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

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

int main() {
Complex c1(3, 4), c2(1, 2);
Complex c3 = c1 + c2; // Uses overloaded + operator
c3.display(); // Output: 4 + 6i
return 0;
}

3. Run-Time Polymorphism
Run-time polymorphism is achieved using virtual functions and function overriding.

a) Function Overriding

• A derived class provides a specific implementation of a method that is already defined


in its base class.

Example:
#include <iostream>
using namespace std;

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

class Dog : public Animal {


public:
void sound() override {
cout << "Bark" << endl;
}
};

class Cat : public Animal {


public:
void sound() override {
cout << "Meow" << endl;
}
};

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

animal1->sound(); // Output: Bark


animal2->sound(); // Output: Meow

delete animal1;
delete animal2;
return 0;
}
b) Virtual Functions

• A function declared in the base class using the virtual keyword.


• Allows the derived class to override the function.

Example:
#include <iostream>
using namespace std;

class Shape {
public:
virtual void draw() {
cout << "Drawing a shape" << endl;
}
};

class Circle : public Shape {


public:
void draw() override {
cout << "Drawing a circle" << endl;
}
};

class Square : public Shape {


public:
void draw() override {
cout << "Drawing a square" << endl;
}
};

int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square();

shape1->draw(); // Output: Drawing a circle


shape2->draw(); // Output: Drawing a square

delete shape1;
delete shape2;
return 0;
}

4. Pure Virtual Functions and Abstract Classes

• A pure virtual function is a virtual function with no implementation.


• A class containing at least one pure virtual function is called an abstract class.
• Abstract classes cannot be instantiated and are meant to be inherited.

Syntax:
virtual return_type function_name() = 0;
Example:
#include <iostream>
using namespace std;

class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};

class Circle : public Shape {


public:
void draw() override {
cout << "Drawing a circle" << endl;
}
};

class Square : public Shape {


public:
void draw() override {
cout << "Drawing a square" << endl;
}
};

int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square();

shape1->draw(); // Output: Drawing a circle


shape2->draw(); // Output: Drawing a square

delete shape1;
delete shape2;
return 0;
}

5. Virtual Destructors

• If a base class pointer points to a derived class object, the base class destructor will be
called when the object is deleted, leading to memory leaks.
• To avoid this, declare the base class destructor as virtual.

Example:
#include <iostream>
using namespace std;

class Base {
public:
virtual ~Base() {
cout << "Base destructor" << endl;
}
};

class Derived : public Base {


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

int main() {
Base* obj = new Derived();
delete obj; // Calls both Derived and Base destructors
return 0;
}
Output:
Derived destructor
Base destructor

6. Best Practices

1. Use virtual functions to enable run-time polymorphism.


2. Declare the base class destructor as virtual if the class is meant to be inherited.
3. Use abstract classes to define interfaces for derived classes.
4. Prefer compile-time polymorphism for performance-critical code.

27. Data Abstraction


• Abstraction: Hide implementation details.

Data Abstraction in C++


Data abstraction is a fundamental concept in Object-Oriented Programming (OOP) that
involves hiding the implementation details of a class and exposing only the essential features
to the user. It allows you to focus on what an object does rather than how it does it.

1. Key Concepts of Data Abstraction

1. Separation of Interface and Implementation:


o The interface (public methods) defines what operations can be performed.
o The implementation (private methods and data) defines how these operations are
performed.
2. Focus on Essential Features:
o Expose only the necessary details to the user.
o Hide the complex and unnecessary details.
3. Encapsulation and Abstraction:
o Encapsulation bundles data and methods into a single unit (class).
o Abstraction hides the internal details and exposes only the functionality.
2. Access Specifiers
C++ provides three access specifiers to control the visibility of class members:

• public: Members are accessible from anywhere.


• private: Members are accessible only within the class.
• protected: Members are accessible within the class and by derived classes.

Example:
class Employee {
private:
string name; // Private data member
int salary; // Private data member

public:
// Public methods to access private data
void setName(string n) {
name = n;
}
string getName() {
return name;
}
void setSalary(int s) {
salary = s;
}
int getSalary() {
return salary;
}
};

3. Abstract Classes and Pure Virtual Functions

An abstract class is a class that cannot be instantiated and is meant to be inherited. It contains
at least one pure virtual function.

Syntax:
virtual return_type function_name() = 0;
Example:
#include <iostream>
using namespace std;

// Abstract class
class Shape {
public:
// Pure virtual function
virtual void draw() = 0;
};
// Derived class
class Circle : public Shape {
public:
void draw() override {
cout << "Drawing a circle" << endl;
}
};

// Derived class
class Square : public Shape {
public:
void draw() override {
cout << "Drawing a square" << endl;
}
};

int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square();

shape1->draw(); // Output: Drawing a circle


shape2->draw(); // Output: Drawing a square

delete shape1;
delete shape2;
return 0;
}

4. Benefits of Data Abstraction


1. Simplifies Complexity:
o Users interact with a simplified interface without worrying about internal details.
2. Improves Maintainability:
o Changes to the internal implementation do not affect the external interface.
3. Enhances Security:
o Sensitive data and implementation details are hidden from the user.
4. Promotes Reusability:
o Abstract classes and interfaces can be reused across different parts of the
program.

5. Example: Abstraction in a Bank Account Class


#include <iostream>
using namespace std;

class BankAccount {
private:
string accountNumber;
double balance;

public:
// Constructor
BankAccount(string accNum, double bal) : accountNumber(accNum), balance(bal) {}

// Getter for account number


string getAccountNumber() {
return accountNumber;
}

// Getter for balance


double getBalance() {
return balance;
}

// Method to deposit money


void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited: $" << amount << endl;
} else {
cout << "Invalid deposit amount!" << endl;
}
}

// Method to withdraw money


void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrawn: $" << amount << endl;
} else {
cout << "Invalid withdrawal amount!" << endl;
}
}
};

int main() {
BankAccount acc("123456789", 1000);

cout << "Account Number: " << acc.getAccountNumber() << endl;


cout << "Initial Balance: $" << acc.getBalance() << endl;

acc.deposit(500);
cout << "Balance after deposit: $" << acc.getBalance() << endl;

acc.withdraw(200);
cout << "Balance after withdrawal: $" << acc.getBalance() << endl;

acc.withdraw(2000); // Invalid withdrawal


return 0;
}

6. Best Practices
1. Use Access Specifiers:
o Declare data members as private to enforce encapsulation and abstraction.
2. Provide Public Methods:
o Expose only the necessary methods to interact with the class.
3. Use Abstract Classes:
o Define interfaces using abstract classes and pure virtual functions.
4. Hide Implementation Details:
o Avoid exposing internal logic and data structures.

28. Data Encapsulation


• Encapsulation: Bundle data and methods that operate on the data.

Data Encapsulation in C++

Data encapsulation is a fundamental concept in Object-Oriented Programming (OOP) that


involves bundling data (attributes) and methods (functions) that operate on the data into a
single unit, called a class. It also restricts direct access to some of an object's components, which
is a way of preventing unintended interference and misuse of the data.

1. Key Concepts of Data Encapsulation


1. Data Hiding:
o Restricting direct access to data members by making them private.
o Access is provided through public methods (getters and setters).
2. Abstraction:
o Exposing only the necessary details and hiding the implementation.
3. Encapsulation:
o Combining data and methods into a single unit (class) and controlling access to
them.

2. Access Specifiers

C++ provides three access specifiers to control the visibility of class members:

• public: Members are accessible from anywhere.


• private: Members are accessible only within the class.
• protected: Members are accessible within the class and by derived classes.

Example:
class Employee {
private:
string name; // Private data member
int salary; // Private data member
public:
// Public methods to access private data
void setName(string n) {
name = n;
}
string getName() {
return name;
}
void setSalary(int s) {
salary = s;
}
int getSalary() {
return salary;
}
};

3. Getters and Setters


• Getters: Methods used to retrieve the value of private data members.
• Setters: Methods used to modify the value of private data members.

Example:
#include <iostream>
using namespace std;

class Employee {
private:
string name;
int salary;

public:
// Setter for name
void setName(string n) {
name = n;
}

// Getter for name


string getName() {
return name;
}

// Setter for salary


void setSalary(int s) {
if (s > 0) { // Validation
salary = s;
} else {
cout << "Invalid salary!" << endl;
}
}

// Getter for salary


int getSalary() {
return salary;
}
};

int main() {
Employee emp;
emp.setName("John Doe");
emp.setSalary(50000);

cout << "Name: " << emp.getName() << endl;


cout << "Salary: $" << emp.getSalary() << endl;

emp.setSalary(-1000); // Invalid salary


return 0;
}

4. Benefits of Data Encapsulation

1. Control Over Data:


o Prevents unauthorized access and modification of data.
o Allows validation and constraints to be applied through setters.
2. Code Maintainability:
o Changes to the internal implementation do not affect the external interface.
3. Reusability:
o Encapsulated classes can be reused in different parts of the program.
4. Security:
o Sensitive data is hidden from external access.

5. Example: Encapsulation in a Bank Account Class


#include <iostream>
using namespace std;

class BankAccount {
private:
string accountNumber;
double balance;

public:
// Constructor
BankAccount(string accNum, double bal) : accountNumber(accNum), balance(bal) {}

// Getter for account number


string getAccountNumber() {
return accountNumber;
}

// Getter for balance


double getBalance() {
return balance;
}
// Method to deposit money
void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited: $" << amount << endl;
} else {
cout << "Invalid deposit amount!" << endl;
}
}

// Method to withdraw money


void withdraw(double amount) {
if (amount > 0 && amount <= balance) {
balance -= amount;
cout << "Withdrawn: $" << amount << endl;
} else {
cout << "Invalid withdrawal amount!" << endl;
}
}
};

int main() {
BankAccount acc("123456789", 1000);

cout << "Account Number: " << acc.getAccountNumber() << endl;


cout << "Initial Balance: $" << acc.getBalance() << endl;

acc.deposit(500);
cout << "Balance after deposit: $" << acc.getBalance() << endl;

acc.withdraw(200);
cout << "Balance after withdrawal: $" << acc.getBalance() << endl;

acc.withdraw(2000); // Invalid withdrawal


return 0;
}

6. Best Practices
1. Make Data Members Private:
o Always declare data members as private to enforce encapsulation.
2. Use Getters and Setters:
o Provide public methods to access and modify private data members.
3. Validate Data:
o Use setters to validate data before modifying private members.
4. Avoid Exposing Implementation Details:
o Expose only the necessary methods and hide the internal logic.
29. Interfaces
Interfaces in C++

In C++, interfaces are implemented using abstract classes with pure virtual functions. An
interface defines a contract that derived classes must follow by implementing all the pure virtual
functions. Interfaces are used to achieve abstraction and polymorphism.

1. What is an Interface?

An interface is a class that:

• Contains only pure virtual functions (no implementation).


• Cannot be instantiated.
• Is meant to be inherited by other classes.

Syntax:
class InterfaceName {
public:
virtual return_type function_name() = 0; // Pure virtual function
};

2. Creating an Interface

To create an interface:

1. Declare a class with pure virtual functions.


2. Derived classes must implement all pure virtual functions.

Example:
#include <iostream>
using namespace std;

// Interface
class Drawable {
public:
virtual void draw() = 0; // Pure virtual function
};

// Derived class implementing the interface


class Circle : public Drawable {
public:
void draw() override {
cout << "Drawing a circle" << endl;
}
};
// Derived class implementing the interface
class Square : public Drawable {
public:
void draw() override {
cout << "Drawing a square" << endl;
}
};

int main() {
Drawable* shape1 = new Circle();
Drawable* shape2 = new Square();

shape1->draw(); // Output: Drawing a circle


shape2->draw(); // Output: Drawing a square

delete shape1;
delete shape2;
return 0;
}

3. Multiple Interfaces
A class can implement multiple interfaces by inheriting from multiple abstract classes.

Example:
#include <iostream>
using namespace std;

// First interface
class Drawable {
public:
virtual void draw() = 0;
};

// Second interface
class Resizable {
public:
virtual void resize(int factor) = 0;
};

// Derived class implementing both interfaces


class Circle : public Drawable, public Resizable {
public:
void draw() override {
cout << "Drawing a circle" << endl;
}
void resize(int factor) override {
cout << "Resizing circle by factor " << factor << endl;
}
};

int main() {
Circle circle;
circle.draw(); // Output: Drawing a circle
circle.resize(2); // Output: Resizing circle by factor 2
return 0;
}

4. Benefits of Interfaces
1. Abstraction:
o Hides implementation details and exposes only the functionality.
2. Polymorphism:
o Allows objects of different classes to be treated as objects of a common interface.
3. Flexibility:
o Enables loose coupling between classes.
4. Reusability:
o Interfaces can be reused across different parts of the program.

5. Example: Interface for a Payment System


#include <iostream>
using namespace std;

// Interface for payment methods


class PaymentMethod {
public:
virtual void pay(double amount) = 0;
};

// Derived class implementing the interface


class CreditCard : public PaymentMethod {
public:
void pay(double amount) override {
cout << "Paying $" << amount << " via Credit Card" << endl;
}
};

// Derived class implementing the interface


class PayPal : public PaymentMethod {
public:
void pay(double amount) override {
cout << "Paying $" << amount << " via PayPal" << endl;
}
};

int main() {
PaymentMethod* payment1 = new CreditCard();
PaymentMethod* payment2 = new PayPal();

payment1->pay(100); // Output: Paying $100 via Credit Card


payment2->pay(50); // Output: Paying $50 via PayPal

delete payment1;
delete payment2;
return 0;
}

6. Best Practices

1. Use Pure Virtual Functions:


o Define interfaces using pure virtual functions.
2. Avoid Data Members:
o Interfaces should not contain data members (only methods).
3. Follow Naming Conventions:
o Use meaningful names for interfaces (e.g., Drawable, Resizable).
4. Implement All Methods:
o Derived classes must implement all pure virtual functions.

30. Files and Streams


Files and Streams in C++

Files and streams are used to handle input and output operations in C++. The <fstream> library
provides classes for working with files, while the <iostream> library handles standard input/output
streams.

1. File Stream Classes

C++ provides three main classes for file handling:

1. ofstream: Used for writing to files.


2. ifstream: Used for reading from files.
3. fstream: Used for both reading and writing.

2. Opening and Closing Files


a) Opening a File

• Use the open() method or the constructor to open a file.


• Specify the file mode (e.g., ios::in, ios::out, ios::app).

b) Closing a File

• Use the close() method to close a file.


Example:
#include <fstream>
using namespace std;

int main() {
ofstream outFile;
outFile.open("example.txt", ios::out); // Open file for writing
if (outFile.is_open()) {
outFile << "Hello, File!" << endl;
outFile.close(); // Close the file
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}

3. Writing to a File
Use the ofstream class to write data to a file.

Example:
#include <fstream>
using namespace std;

int main() {
ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "This is line 1." << endl;
outFile << "This is line 2." << endl;
outFile.close();
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}

4. Reading from a File

Use the ifstream class to read data from a file.

Example:
#include <fstream>
#include <iostream>
using namespace std;

int main() {
ifstream inFile("example.txt");
string line;
if (inFile.is_open()) {
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}

5. File Modes

File modes determine how a file is opened. Common modes include:

Mode Description

ios::in Open for input (reading).

ios::out Open for output (writing).

ios::app Append to the end of the file.

ios::ate Open and seek to the end of the file.

ios::trunc Truncate the file (delete contents).

ios::binary Open in binary mode.

Example:
ofstream outFile("example.txt", ios::out | ios::app); // Open for appending

6. Binary File Operations

Binary files store data in binary format, which is more efficient for non-text data.

a) Writing to a Binary File


#include <fstream>
using namespace std;

int main() {
ofstream outFile("data.bin", ios::binary);
if (outFile.is_open()) {
int data = 12345;
outFile.write((char*)&data, sizeof(data)); // Write binary data
outFile.close();
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}
b) Reading from a Binary File
#include <fstream>
#include <iostream>
using namespace std;

int main() {
ifstream inFile("data.bin", ios::binary);
if (inFile.is_open()) {
int data;
inFile.read((char*)&data, sizeof(data)); // Read binary data
cout << "Data: " << data << endl;
inFile.close();
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}

7. File Position Pointers

File position pointers track the current position in a file. You can manipulate them using:

• tellg() / tellp(): Get the current position.


• seekg() / seekp(): Set the position.

Example:
#include <fstream>
#include <iostream>
using namespace std;

int main() {
fstream file("example.txt", ios::in | ios::out);
if (file.is_open()) {
file << "Hello, World!" << endl;

// Move to the beginning of the file


file.seekg(0, ios::beg);

string line;
getline(file, line);
cout << "First line: " << line << endl;

file.close();
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}
8. Error Handling

Always check if a file was successfully opened before performing operations.

Example:
#include <fstream>
#include <iostream>
using namespace std;

int main() {
ifstream inFile("nonexistent.txt");
if (!inFile) {
cout << "Error: File not found!" << endl;
} else {
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
}
return 0;
}

9. Best Practices

1. Check File Open Status:


o Always verify if a file was successfully opened.
2. Close Files Properly:
o Use the close() method to release resources.
3. Use Binary Mode for Non-Text Data:
o Binary mode is more efficient for storing complex data structures.
4. Handle Errors Gracefully:
o Provide meaningful error messages for file operations.

31. Exception Handling


Exception Handling in C++

Exception handling is a mechanism to handle runtime errors gracefully. It allows you to


separate error-handling code from normal code, making programs more robust and
maintainable.
1. Key Concepts

1. Exception:
o An abnormal condition that occurs during program execution (e.g., division by
zero, file not found).
2. Try Block:
o A block of code where exceptions may occur. It is followed by one or more catch
blocks.
3. Catch Block:
o A block of code that handles exceptions thrown by the try block.
4. Throw Statement:
o Used to raise an exception explicitly.

2. Basic Syntax
try {
// Code that may throw an exception
if (errorCondition) {
throw exception; // Throw an exception
}
} catch (exceptionType& e) {
// Handle the exception
}

3. Example: Handling Division by Zero


#include <iostream>
using namespace std;

int main() {
int numerator, denominator;
cout << "Enter numerator and denominator: ";
cin >> numerator >> denominator;

try {
if (denominator == 0) {
throw "Division by zero error!"; // Throw a string exception
}
cout << "Result: " << numerator / denominator << endl;
} catch (const char* e) {
cout << "Error: " << e << endl;
}

return 0;
}

4. Standard Exceptions
C++ provides a set of standard exception classes in the <stdexcept> header. These include:
• std::runtime_error
• std::logic_error
• std::out_of_range
• std::invalid_argument

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

int main() {
try {
throw runtime_error("A runtime error occurred!");
} catch (const runtime_error& e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}

5. Multiple Catch Blocks


You can have multiple catch blocks to handle different types of exceptions.

Example:
#include <iostream>
using namespace std;

int main() {
try {
int choice;
cout << "Enter 1 for int, 2 for double, 3 for string: ";
cin >> choice;

if (choice == 1) {
throw 42; // Throw an int
} else if (choice == 2) {
throw 3.14; // Throw a double
} else if (choice == 3) {
throw string("Hello"); // Throw a string
} else {
throw invalid_argument("Invalid choice!"); // Throw a standard exception
}
} catch (int e) {
cout << "Caught int: " << e << endl;
} catch (double e) {
cout << "Caught double: " << e << endl;
} catch (const string& e) {
cout << "Caught string: " << e << endl;
} catch (const invalid_argument& e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
6. Rethrowing Exceptions

You can rethrow an exception in a catch block to pass it to an outer try-catch block.

Example:
#include <iostream>
using namespace std;

void innerFunction() {
try {
throw runtime_error("Error in inner function!");
} catch (const runtime_error& e) {
cout << "Inner function caught: " << e.what() << endl;
throw; // Rethrow the exception
}
}

int main() {
try {
innerFunction();
} catch (const runtime_error& e) {
cout << "Main function caught: " << e.what() << endl;
}
return 0;
}

7. Custom Exception Classes

You can create custom exception classes by inheriting from std::exception.

Example:
#include <iostream>
#include <exception>
using namespace std;

class MyException : public exception {


public:
const char* what() const noexcept override {
return "Custom exception occurred!";
}
};

int main() {
try {
throw MyException();
} catch (const MyException& e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
8. Best Practices

1. Catch Specific Exceptions:


o Catch specific exceptions rather than using a generic catch (...).
2. Avoid Throwing Raw Types:
o Use standard or custom exception classes instead of raw types (e.g., int, char*).
3. Clean Up Resources:
o Use RAII (Resource Acquisition Is Initialization) to ensure resources are
released even if an exception is thrown.
4. Document Exceptions:
o Document the exceptions that a function can throw.

9. Example: File Handling with Exception Handling


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

void readFile(const string& filename) {


ifstream file(filename);
if (!file) {
throw runtime_error("Unable to open file: " + filename);
}
string line;
while (getline(file, line)) {
cout << line << endl;
}
file.close();
}

int main() {
try {
readFile("nonexistent.txt");
} catch (const runtime_error& e) {
cout << "Error: " << e.what() << endl;
}
return 0;
}
32. Dynamic Memory
Dynamic Memory in C++

Dynamic memory allocation allows you to allocate memory at runtime instead of compile time.
This is useful when the amount of memory needed is not known in advance or when you need to
manage memory manually.

1. Dynamic Memory Allocation

C++ provides two operators for dynamic memory allocation:

1. new: Allocates memory.


2. delete: Deallocates memory.

Syntax:
pointer = new data_type; // Allocate memory for a single element
pointer = new data_type[size]; // Allocate memory for an array

delete pointer; // Deallocate memory for a single element


delete[] pointer; // Deallocate memory for an array

2. Allocating Memory for a Single Element


Example:
#include <iostream>
using namespace std;

int main() {
int* ptr = new int; // Allocate memory for an integer
*ptr = 42; // Assign a value
cout << "Value: " << *ptr << endl; // Output: Value: 42
delete ptr; // Deallocate memory
return 0;
}

3. Allocating Memory for an Array


Example:
#include <iostream>
using namespace std;

int main() {
int size = 5;
int* arr = new int[size]; // Allocate memory for an array

for (int i = 0; i < size; i++) {


arr[i] = i + 1; // Initialize array
}

for (int i = 0; i < size; i++) {


cout << arr[i] << " "; // Output: 1 2 3 4 5
}

delete[] arr; // Deallocate memory


return 0;
}

4. Common Pitfalls

1. Memory Leaks:
o Forgetting to deallocate memory using delete or delete[].
2. int* ptr = new int;
3. // Forgot to delete ptr;
4. Dangling Pointers:
o Using a pointer after the memory it points to has been deallocated.
5. int* ptr = new int;
6. delete ptr;
7. *ptr = 10; // Undefined behavior
8. Double Deletion:
o Deallocating the same memory block twice.
9. int* ptr = new int;
10. delete ptr;
11. delete ptr; // Undefined behavior

5. Smart Pointers (C++11 and later)

Smart pointers automatically manage dynamic memory, preventing memory leaks and dangling
pointers. The <memory> header provides three types of smart pointers:

1. std::unique_ptr: Manages a single object with exclusive ownership.


2. std::shared_ptr: Manages an object with shared ownership.
3. std::weak_ptr: A non-owning reference to an object managed by std::shared_ptr.

Example:
#include <iostream>
#include <memory> // For smart pointers
using namespace std;

int main() {
// Unique pointer
unique_ptr<int> ptr1(new int(42));
cout << "Value: " << *ptr1 << endl; // Output: Value: 42

// Shared pointer
shared_ptr<int> ptr2(new int(100));
cout << "Value: " << *ptr2 << endl; // Output: Value: 100
// Weak pointer
weak_ptr<int> ptr3 = ptr2;
if (auto sharedPtr = ptr3.lock()) {
cout << "Value: " << *sharedPtr << endl; // Output: Value: 100
}

// No need to manually delete; memory is automatically released


return 0;
}

6. Dynamic Memory for Objects

You can allocate memory for objects dynamically.

Example:
#include <iostream>
using namespace std;

class MyClass {
public:
MyClass() {
cout << "Constructor called!" << endl;
}
~MyClass() {
cout << "Destructor called!" << endl;
}
void display() {
cout << "Hello, World!" << endl;
}
};

int main() {
MyClass* obj = new MyClass(); // Allocate memory for an object
obj->display(); // Output: Hello, World!
delete obj; // Deallocate memory
return 0;
}

7. Best Practices

1. Use Smart Pointers:


o Prefer std::unique_ptr or std::shared_ptr over raw pointers.
2. Avoid Manual Memory Management:
o Use RAII (Resource Acquisition Is Initialization) to manage resources.
3. Check for Allocation Failures:
o Handle cases where new fails to allocate memory (e.g., using std::nothrow).
4. Avoid Dangling Pointers:
o Set pointers to nullptr after deleting them.
8. Example: Dynamic Array with Exception Handling
#include <iostream>
#include <new> // For std::nothrow
using namespace std;

int main() {
int size = 1000000000; // Very large size
int* arr = new (nothrow) int[size]; // Allocate memory without throwing an exception

if (!arr) {
cout << "Memory allocation failed!" << endl;
} else {
for (int i = 0; i < size; i++) {
arr[i] = i + 1;
}
delete[] arr; // Deallocate memory
}

return 0;
}

33. Namespaces
Namespaces in C++
Namespaces are used to organize code and prevent name conflicts in large programs. They
allow you to group related classes, functions, and variables under a unique name.

1. Defining a Namespace
Use the namespace keyword to define a namespace.

Syntax:
namespace NamespaceName {
// Classes, functions, variables, etc.
}
Example:
#include <iostream>
using namespace std;

namespace Math {
int add(int a, int b) {
return a + b;
}
}

int main() {
cout << Math::add(5, 10) << endl; // Output: 15
return 0;
}

2. Accessing Namespace Members

You can access namespace members using the scope resolution operator (::).

Example:
#include <iostream>
using namespace std;

namespace Math {
int add(int a, int b) {
return a + b;
}
}

int main() {
int result = Math::add(5, 10); // Access using ::
cout << "Result: " << result << endl; // Output: Result: 15
return 0;
}

3. The using Directive

The using directive allows you to bring all members of a namespace into the current scope.

Example:
#include <iostream>
using namespace std;

namespace Math {
int add(int a, int b) {
return a + b;
}
}

using namespace Math; // Bring all Math members into the current scope

int main() {
cout << add(5, 10) << endl; // Output: 15
return 0;
}

4. Nested Namespaces
Namespaces can be nested inside other namespaces.

You might also like