Pages From OPP SUMMERISED Part 3
Pages From OPP SUMMERISED Part 3
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
2. Compile-Time Polymorphism
a) Function Overloading
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) {}
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
Example:
#include <iostream>
using namespace std;
class Animal {
public:
virtual void sound() {
cout << "Animal sound" << endl;
}
};
int main() {
Animal* animal1 = new Dog();
Animal* animal2 = new Cat();
delete animal1;
delete animal2;
return 0;
}
b) Virtual Functions
Example:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "Drawing a shape" << endl;
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square();
delete shape1;
delete shape2;
return 0;
}
Syntax:
virtual return_type function_name() = 0;
Example:
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() = 0; // Pure virtual function
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new 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;
}
};
int main() {
Base* obj = new Derived();
delete obj; // Calls both Derived and Base destructors
return 0;
}
Output:
Derived destructor
Base destructor
6. Best Practices
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;
}
};
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();
delete shape1;
delete shape2;
return 0;
}
class BankAccount {
private:
string accountNumber;
double balance;
public:
// Constructor
BankAccount(string accNum, double bal) : accountNumber(accNum), balance(bal) {}
int main() {
BankAccount acc("123456789", 1000);
acc.deposit(500);
cout << "Balance after deposit: $" << acc.getBalance() << endl;
acc.withdraw(200);
cout << "Balance after withdrawal: $" << acc.getBalance() << endl;
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.
2. Access Specifiers
C++ provides three access specifiers to control the visibility of class members:
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;
}
};
Example:
#include <iostream>
using namespace std;
class Employee {
private:
string name;
int salary;
public:
// Setter for name
void setName(string n) {
name = n;
}
int main() {
Employee emp;
emp.setName("John Doe");
emp.setSalary(50000);
class BankAccount {
private:
string accountNumber;
double balance;
public:
// Constructor
BankAccount(string accNum, double bal) : accountNumber(accNum), balance(bal) {}
int main() {
BankAccount acc("123456789", 1000);
acc.deposit(500);
cout << "Balance after deposit: $" << acc.getBalance() << endl;
acc.withdraw(200);
cout << "Balance after withdrawal: $" << acc.getBalance() << endl;
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?
Syntax:
class InterfaceName {
public:
virtual return_type function_name() = 0; // Pure virtual function
};
2. Creating an Interface
To create an interface:
Example:
#include <iostream>
using namespace std;
// Interface
class Drawable {
public:
virtual void draw() = 0; // Pure virtual function
};
int main() {
Drawable* shape1 = new Circle();
Drawable* shape2 = new 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;
};
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.
int main() {
PaymentMethod* payment1 = new CreditCard();
PaymentMethod* payment2 = new PayPal();
delete payment1;
delete payment2;
return 0;
}
6. Best Practices
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.
b) Closing a File
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;
}
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
Mode Description
Example:
ofstream outFile("example.txt", ios::out | ios::app); // Open for appending
Binary files store data in binary format, which is more efficient for non-text data.
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;
}
File position pointers track the current position in a file. You can manipulate them using:
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;
string line;
getline(file, line);
cout << "First line: " << line << endl;
file.close();
} else {
cout << "Unable to open file!" << endl;
}
return 0;
}
8. Error Handling
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. 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
}
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;
}
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;
}
Example:
#include <iostream>
#include <exception>
using namespace std;
int main() {
try {
throw MyException();
} catch (const MyException& e) {
cout << "Caught exception: " << e.what() << endl;
}
return 0;
}
8. Best Practices
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.
Syntax:
pointer = new data_type; // Allocate memory for a single element
pointer = new data_type[size]; // Allocate memory for an array
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;
}
int main() {
int size = 5;
int* arr = new int[size]; // Allocate memory for an array
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
Smart pointers automatically manage dynamic memory, preventing memory leaks and dangling
pointers. The <memory> header provides three types of smart pointers:
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
}
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
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;
}
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;
}
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.