Oop
Oop
Definition:
An object is a fundamental concept in Object-Oriented Programming (OOP). It is an instance of
a class that represents a real-world entity. An object contains data (attributes) and methods
(functions) that define its behavior.
Real-World Example:
Syntax in C++:
#include <iostream>
using namespace std;
// Class definition
class Car {
public:
string color;
string brand;
void drive() {
cout << "The car is driving!" << endl;
}
};
int main() {
// Creating an object
Car myCar;
myCar.color = "Red";
myCar.brand = "Toyota";
return 0;
}
Output:
Summary:
1. What is a Class?
Definition:
A class is a blueprint or template used to create objects. It defines the attributes (data members)
and behaviors (methods or functions) that the objects will have.
Attributes (Data Members): Variables that store data related to the class.
Methods (Member Functions): Functions that define the behavior or actions of the class.
Definition:
Object-Oriented Programming (OOP) is a programming paradigm based on the concept of
classes and objects. It focuses on representing real-world entities using objects.
Example in C++:
#include <iostream>
using namespace std;
// Defining a class
class Student {
public:
string name;
int age;
int main() {
// Creating an object of the class
Student student1;
student1.name = "Ali";
student1.age = 20;
return 0;
}
Output:
Name: Ali
Age: 20
Advantages of OOP:
Summary:
Definition:
Data members are variables defined in a class that store the attributes or properties of an object.
Definition:
Member functions are methods defined inside a class that operate on the data members. They
define the behavior of an object.
Purpose: Perform operations or actions on the data members of the class.
Member functions can also be public, private, or protected, controlling their accessibility.
Key Differences:
Aspect Data Members Member Functions
Example in C++:
#include <iostream>
using namespace std;
// Define a class
class Person {
public:
// Data members
string name;
int age;
int main() {
// Create an object of the class
Person person1;
return 0;
}
Output:
Name: Ayesha
Age: 22
Key Points:
1. Data Members: Store object-specific data (e.g., name, age in the above example).
2. Member Functions: Perform actions like setting or displaying the data (e.g., setDetails() and
displayDetails()).
This combination ensures that objects can hold data and operate on that data, adhering to the
principles of encapsulation in OOP.
Definition:
Access modifiers in Object-Oriented Programming are keywords that define the visibility or
accessibility of data members and member functions of a class. They determine who can
access or modify the class attributes and methods.
1. Public
o Members declared as public are accessible from anywhere (inside or outside the
class).
o Use: When data or functions need to be openly available.
o Syntax:
public:
int x;
void display();
2. Private
o Members declared as private are accessible only within the class.
o Use: To protect sensitive data and ensure proper encapsulation.
o Syntax:
private:
int x;
void calculate();
3. Protected
o Members declared as protected are accessible within the class and by derived classes
(child classes).
o Use: When you want to allow restricted access in inheritance.
o Syntax:
protected:
int x;
void setValue();
Example in C++:
#include <iostream>
using namespace std;
class Base {
private:
int privateData = 10; // Accessible only within this class
protected:
int protectedData = 20; // Accessible in this class and derived classes
public:
int publicData = 30; // Accessible from anywhere
int main() {
Base baseObj;
baseObj.display();
Derived derivedObj;
derivedObj.show();
return 0;
}
Output:
Private Data: 10
Protected Data: 20
Public Data: 30
Public Data: 30
Protected Data: 20
Public Data: 30
Key Differences:
Private Yes No No
Summary:
Definition:
A constructor is a special type of member function in a class that is automatically called when
an object of the class is created. It is used to initialize the data members of the class.
1. Same Name as the Class: A constructor must have the same name as the class.
2. No Return Type: It doesn’t have any return type, not even void.
3. Automatic Invocation: It is called automatically when an object is created.
4. Overloading: You can create multiple constructors in a class (constructor overloading).
Types of Constructors:
1. Default Constructor:
o A constructor with no parameters.
o Initializes data members with default values.
Example:
class Student {
public:
int age;
Student() { // Default constructor
age = 18;
}
};
2. Parameterized Constructor:
o A constructor that takes arguments to initialize data members.
Example:
class Student {
public:
int age;
Student(int a) { // Parameterized constructor
age = a;
}
};
3. Copy Constructor:
o A constructor that initializes an object by copying another object.
Example:
class Student {
public:
int age;
Student(const Student &obj) { // Copy constructor
age = obj.age;
}
};
Example in C++:
#include <iostream>
using namespace std;
class Student {
private:
string name;
int age;
public:
// Default constructor
Student() {
name = "Unknown";
age = 0;
}
// Parameterized constructor
Student(string n, int a) {
name = n;
age = a;
}
int main() {
// Using default constructor
Student student1;
student1.display();
return 0;
}
Output:
Summary:
Definition:
Default values for functions in Object-Oriented Programming allow you to assign a value to a
function's parameter(s) in advance. If no argument is provided for that parameter when calling
the function, the default value is used.
Key Features:
1. Simplifies Function Calls: Optional arguments reduce the need for overloading functions with
similar logic.
2. Flexibility: You can omit certain arguments while still calling the function.
3. Default Only at the End: Default parameters must appear after all non-default parameters.
Syntax in C++:
void functionName(type param1 = defaultValue1, type param2 = defaultValue2) {
// Function logic
}
Example in C++:
#include <iostream>
using namespace std;
class Calculator {
public:
// Function with default values
int add(int a, int b = 10, int c = 5) {
return a + b + c;
}
};
int main() {
Calculator calc;
return 0;
}
Output:
Result (3 arguments): 35
Result (2 arguments): 30
Result (1 argument): 20
Explanation:
2. Function Calls:
o If you omit b and c, their default values are used.
o If you pass arguments, they override the default values.
Rules for Default Parameters:
2. Default values are assigned at function declaration (in the prototype) or definition, but not
both.
class Person {
public:
void introduce(string name, string city = "Unknown") {
cout << "Name: " << name << ", City: " << city << endl;
}
};
int main() {
Person person;
person.introduce("Ali", "Karachi"); // Both arguments provided
person.introduce("Sara"); // Default city used
return 0;
}
Output:
1. Code Simplicity: Makes function calls simpler when default behavior is sufficient.
2. Reduces Overloading: Avoids the need to define multiple functions for similar operations.
Summary:
Default values simplify function calls by providing predefined values for parameters.
If arguments are provided during the call, they override the defaults.
Default values are useful for creating flexible and concise code.
Inline Function in OOP
Definition:
An inline function is a function where the function body is expanded in place at the point
where it is called, instead of transferring control to the function's definition. It is used to reduce
the overhead of function calls by embedding the function code directly into the calling code
during compilation.
1. Faster Execution: Avoids the overhead of a function call, such as jumping to the function's
memory location and returning.
2. Compiler-Dependent: The compiler decides whether to actually inline the function, even if it is
defined as inline.
3. Limited Use: Best suited for small, frequently used functions where the overhead of a function
call is significant compared to the function's size.
Syntax:
inline returnType functionName(parameters) {
// Function body
}
Example in C++:
#include <iostream>
using namespace std;
// Inline function
inline int square(int x) {
return x * x;
}
int main() {
cout << "Square of 5: " << square(5) << endl; // Inline expansion here
cout << "Square of 10: " << square(10) << endl; // Inline expansion here
return 0;
}
Output:
Square of 5: 25
Square of 10: 100
How It Works:
1. When the square(5) function is called, the compiler replaces the call with the actual code of
the function:
2. This eliminates the overhead of transferring control to the function and back.
Advantages:
Disadvantages:
1. Code Size Increase: Expanding inline functions can make the executable larger (known as code
bloat).
2. Compiler Control: The compiler may ignore the inline keyword if it deems the function
unsuitable for inlining.
3. Complexity: Large inline functions can lead to reduced readability and slower compile times.
Important Notes:
2. Recursion and Inline: Inline functions cannot be recursive because they would result in infinite
inlining.
Summary:
An inline function improves performance by embedding function code directly at the point of
call.
Use inline functions for small and simple functions to reduce call overhead but avoid using
them for large or complex functions to prevent code bloat.
Definition:
An open parameter list allows a function to accept a variable number of arguments. This
means the function can take zero, one, or multiple parameters, making it more flexible for
handling diverse inputs. In C++, this is typically implemented using variadic functions or
features like templates.
To define an open parameter list in C++, you can use ellipses (...) in the function declaration.
Syntax:
returnType functionName(parameter1, parameter2, ...);
Example Using std::initializer_list:
#include <iostream>
#include <initializer_list>
using namespace std;
int main() {
cout << "Sum of 1, 2, 3: " << sum({1, 2, 3}) << endl;
cout << "Sum of 5, 10, 15, 20: " << sum({5, 10, 15, 20}) << endl;
return 0;
}
Output:
Sum of 1, 2, 3: 6
Sum of 5, 10, 15, 20: 50
int main() {
cout << "Sum of 3 numbers (1, 2, 3): " << sum(3, 1, 2, 3) << endl;
cout << "Sum of 5 numbers (5, 10, 15, 20, 25): " << sum(5, 5, 10, 15, 20,
25) << endl;
return 0;
}
Output:
Sum of 3 numbers (1, 2, 3): 6
Sum of 5 numbers (5, 10, 15, 20, 25): 75
#include <iostream>
using namespace std;
// Variadic template
template<typename... Args>
void print(Args... args) {
(cout << ... << args) << endl; // Fold expression to print all arguments
}
int main() {
print("Hello, ", "World!", " This is an open parameter list.");
print(1, 2, 3, 4, 5);
return 0;
}
Output:
Advantages:
Disadvantages:
1. Complexity: Managing open parameter lists (especially with va_list) can be error-prone.
2. Type-Safety: Variadic functions (using ...) lack type-checking, which can lead to runtime errors.
Use Cases:
Summary:
An open parameter list enables functions to accept a variable number of arguments, making
them highly flexible. In C++, this is implemented using variadic functions, initializer lists, or
templates. Variadic templates are type-safe and preferred in modern C++ programming.
References in C++
Definition:
A reference in C++ is an alias for an existing variable. It allows you to access and modify the
original variable directly without creating a copy of it. A reference is essentially a pointer that is
automatically dereferenced. Once a reference is initialized to a variable, it cannot be changed
to refer to another variable.
Key Features:
Syntax:
type &referenceName = originalVariable;
int main() {
int a = 5;
int &b = a; // 'b' is a reference to 'a'
return 0;
}
Output:
Original value of a: 5
Value of b (reference to a): 5
After modifying b, value of a: 10
After modifying b, value of b: 10
Explanation:
1. int &b = a; creates a reference b to the variable a. Both a and b now refer to the same
memory location.
2. Modifying b directly changes the value of a because b is just an alias for a.
3. After changing b, both a and b have the same value.
1. Function Parameters: Passing arguments by reference allows a function to modify the original
argument.
2. Avoiding Copies: References can be used to avoid unnecessary copies of large objects (e.g., in
function calls).
3. Return by Reference: A function can return a reference to a variable, allowing you to modify the
original value outside the function.
Example: Reference as Function Parameter
#include <iostream>
using namespace std;
int main() {
int num = 5;
increment(num); // Pass by reference
cout << "After increment, num: " << num << endl; // Output: 6
return 0;
}
Output:
References vs Pointers:
1. Initialization:
o A reference must always be initialized, while a pointer can be initialized later.
o Example:
int a = 5;
int *p; // Pointer, not initialized yet
int &r = a; // Reference, must be initialized immediately
2. Reassignment:
o A pointer can be reassigned to point to another variable, while a reference cannot.
o Example:
int a = 5, b = 10;
int &r = a; // Reference to 'a'
r = b; // This does not reassign 'r' to 'b', it assigns the value
of 'b' to 'a'
3. Dereferencing:
o A reference does not need to be dereferenced; it directly accesses the original variable.
o A pointer needs to be dereferenced using * to access the value it points to.
Advantages of References:
Disadvantages of References:
1. Cannot Be Null: A reference must always refer to an object, whereas a pointer can be nullptr.
2. Cannot Be Reassigned: Once initialized, a reference cannot point to another object.
Summary:
A reference is an alias for an existing variable that allows direct access and modification without
creating a copy.
It provides cleaner syntax and is useful for passing parameters to functions or returning large
objects from functions without unnecessary copies.
References are more efficient and safer than pointers in many cases, but they cannot be null or
reassigned.
Pointers to Objects:
A pointer to an object is a pointer that stores the memory address of an object. Just like pointers
to basic data types (like int or char), pointers to objects are used to refer to the memory location
where the object is stored. This allows you to manipulate objects indirectly and can be useful
in certain situations, such as dynamic memory allocation or managing arrays of objects.
1. Declaration:
A pointer to an object is declared by using the class name followed by an asterisk (*).
class MyClass {
public:
int x;
MyClass() : x(0) {}
};
int main() {
MyClass obj; // Declare an object
MyClass* ptr = &obj; // Pointer to the object
ptr->x = 5; // Access object's member using pointer
cout << "x = " << obj.x << endl; // Output: x = 5
return 0;
}
Output:
CopyEdit
x = 5
When working with pointers to objects, use the arrow operator (->) to access the object's
members.
Example:
Dynamic memory allocation allows you to allocate memory for objects at runtime using the
new keyword. This is different from static memory allocation where the size of the data is
determined during compile time.
When you allocate memory dynamically using new, it’s essential to free that memory when it’s
no longer needed using the delete operator.
#include <iostream>
using namespace std;
class MyClass {
public:
int x;
MyClass() : x(0) {}
};
int main() {
// Dynamic memory allocation for a single object
MyClass* ptr1 = new MyClass();
ptr1->x = 25;
cout << "Value of x in ptr1: " << ptr1->x << endl;
cout << "Values of x in ptrArray: " << ptrArray[0].x << ", " <<
ptrArray[1].x << ", " << ptrArray[2].x << endl;
return 0;
}
Output:
Value of x in ptr1: 25
Values of x in ptrArray: 5, 10, 15
1. Memory Management:
Always remember to deallocate dynamically allocated memory using delete or
delete[] to prevent memory leaks.
2. Memory Leaks:
A memory leak occurs when dynamically allocated memory is not deallocated, causing
wasted memory and potentially slowing down the system.
3. Accessing Deallocated Memory:
After calling delete or delete[], the pointer still holds the address of the deallocated
memory, which is called a dangling pointer. Dereferencing it can lead to undefined
behavior.
Summary:
A pointer to an object stores the memory address of an object, allowing indirect access to the
object's members using the arrow operator (->).
Dynamic memory allocation uses new to allocate memory for objects at runtime, and delete is
used to free that memory when it's no longer needed.
It's important to manage dynamically allocated memory carefully to avoid memory leaks and
dangling pointers.
Definition:
A copy constructor is a special constructor in C++ used to create a new object as a copy of an
existing object. It is called when an object is passed by value, returned by value, or explicitly
copied to another object. The copy constructor ensures that a deep copy of an object is made,
meaning that the new object gets its own copy of the data, rather than just copying the address
(shallow copy).
Syntax:
Here, otherObject is the object that you are copying from, and this is the new object being
created.
If you don't define a copy constructor, C++ provides a default copy constructor, which performs
a shallow copy. This means that it copies the values of the members of the original object
directly into the new object. This can cause problems, especially when the object contains
pointers, because both objects will point to the same memory location.
class MyClass {
public:
int *ptr;
// Constructor
MyClass(int value) {
ptr = new int;
*ptr = value;
}
// Copy Constructor
MyClass(const MyClass &other) {
ptr = new int; // Allocate new memory
*ptr = *(other.ptr); // Copy the value of the original object's
pointer
cout << "Copy constructor called!" << endl;
}
// Destructor
~MyClass() {
delete ptr;
}
void display() {
cout << "Value: " << *ptr << endl;
}
};
int main() {
MyClass obj1(10); // Create an object
obj1.display(); // Display value of obj1
return 0;
}
Output:
Value: 10
Copy constructor called!
Value: 10
Explanation:
1. Constructor:
The MyClass(int value) constructor dynamically allocates memory for ptr and
assigns the value.
2. Copy Constructor:
The copy constructor allocates new memory for the ptr of the new object and copies the
value from the original object's ptr to the new one. This ensures that obj2 does not just
point to the same memory as obj1, thus preventing shallow copying.
3. Destructor:
The destructor ~MyClass() frees the dynamically allocated memory for ptr.
Shallow Copy: The default copy constructor simply copies the values of the object. If the object
contains pointers, both the original and copied objects will point to the same memory.
Deep Copy: The copy constructor shown above performs a deep copy, where a new memory
location is allocated, and the value is copied into the new object.
1. When an object contains dynamically allocated memory (e.g., pointers), to avoid shallow
copying.
2. When you want to ensure deep copying of the object’s data.
3. When you need special handling during object copying, such as logging or extra logic during
copying.
class MyClass {
public:
int *ptr;
// Constructor
MyClass(int value) {
ptr = new int;
*ptr = value;
}
// Destructor
~MyClass() {
delete ptr;
}
};
int main() {
MyClass obj1(10); // Create an object
MyClass obj2 = obj1; // Default shallow copy (no custom copy constructor)
cout << "obj1 ptr: " << obj1.ptr << " Value: " << *obj1.ptr << endl;
cout << "obj2 ptr: " << obj2.ptr << " Value: " << *obj2.ptr << endl;
return 0;
}
Output:
Summary:
The copy constructor ensures that when you copy an object, you make a deep copy (for objects
containing pointers or dynamically allocated memory).
It is called when objects are passed by value or returned by value from a function.
Defining a copy constructor is essential for managing resources and avoiding shallow copies,
which can lead to memory corruption or undefined behavior.
Shallow copies and deep copies are crucial concepts in object-oriented programming in C++.
Destructor in C++
Definition:
A destructor is a special member function of a class that is executed when an object of the class
goes out of scope or is explicitly destroyed. Its primary purpose is to free resources that were
allocated dynamically during the lifetime of the object, ensuring proper cleanup. It is
automatically called when the object is destroyed, and it doesn't require explicit invocation.
Syntax of Destructor:
~ClassName();
The destructor has the same name as the class, but it is prefixed with a tilde (~).
It does not take any arguments and does not return any value.
1. Automatically Called:
Destructors are called automatically when an object goes out of scope (in case of local
objects) or when an object is explicitly deleted (in case of dynamically allocated objects).
2. Only One Destructor:
A class can have only one destructor. It cannot be overloaded.
3. No Parameters or Return Type:
Destructors cannot have parameters, and they do not return any value.
4. Memory Management:
Destructors are typically used to free memory that was allocated with new or malloc (in
C++) and clean up any other resources the object may have acquired.
MyClass obj; // Destructor will be called when obj goes out of scope
Example of a Destructor:
#include <iostream>
using namespace std;
class MyClass {
public:
int *ptr;
// Constructor
MyClass(int value) {
ptr = new int; // Dynamically allocate memory
*ptr = value;
cout << "Constructor called, value: " << *ptr << endl;
}
// Destructor
~MyClass() {
delete ptr; // Free the dynamically allocated memory
cout << "Destructor called, memory freed." << endl;
}
void display() {
cout << "Value: " << *ptr << endl;
}
};
int main() {
MyClass obj1(10); // Create an object
obj1.display(); // Display value of obj1
return 0;
}
Output:
Explanation:
1. Constructor:
The constructor initializes the ptr with dynamically allocated memory, and the value is
assigned to it.
2. Destructor:
The destructor is called when the object obj1 goes out of scope and when ptr2 is
deleted. The destructor frees the memory allocated for ptr and prints a message.
3. Dynamic Object Cleanup:
For dynamically allocated objects, it is important to manually call delete to free the
memory and call the destructor.
1. Resource Management:
Destructors are essential for proper memory management. If dynamically allocated
memory is not freed, it can lead to memory leaks.
2. Cleanup Tasks:
Destructors are also used to release other resources such as file handles, network
connections, or system resources that were acquired during the lifetime of the object.
Destructors in Inheritance:
In the case of inheritance, the destructor of the base class is called when a derived class object
is deleted. If the destructor of the base class is not virtual, deleting an object of a derived class
through a base class pointer can cause undefined behavior.
To avoid this, it is recommended to make destructors virtual in base classes when using
inheritance.
class Base {
public:
virtual ~Base() { // Virtual Destructor in Base Class
cout << "Base class Destructor" << endl;
}
};
int main() {
Base* obj = new Derived(); // Base class pointer, derived class object
delete obj; // Properly calls the derived class destructor followed by
the base class destructor
return 0;
}
Output:
Summary:
Definition:
A friend function in C++ is a function that is not a member of a class but has the ability to
access the class's private and protected members. A friend function is declared inside the class
using the friend keyword.
class Box {
private:
double length;
public:
Box(double l) : length(l) {} // Constructor
int main() {
Box box(10.5); // Create an object of class Box
cout << "The length of the box is: " << getLength(box) << endl; // Friend
function accessing private member
return 0;
}
Output:
csharp
CopyEdit
The length of the box is: 10.5
Explanation:
1. Class Declaration:
In the Box class, length is a private member, and the getLength function is declared as a
friend function inside the class.
2. Friend Function Definition:
The getLength function, although not a member of Box, can access the private member
length because it is declared as a friend function.
3. Using the Friend Function:
In the main function, getLength is called with a Box object as an argument, and it
successfully retrieves the value of the private member length.
class Complex {
private:
int real, imag;
public:
Complex(int r, int i) : real(r), imag(i) {}
void display() {
cout << real << " + " << imag << "i" << endl;
}
};
int main() {
Complex c1(3, 4), c2(5, 6);
Complex c3 = c1 + c2; // Using overloaded operator +
c3.display(); // Output: 8 + 10i
return 0;
}
Output:
go
CopyEdit
8 + 10i
1. The operator+ is overloaded as a friend function so that it can access the private members of
the Complex class (i.e., real and imag).
2. The operator + is used to add two Complex objects together, and the result is stored in c3.
3. The friend function allows the operator to directly access the private members of both Complex
objects.
1. Flexibility:
Friend functions allow external functions to access the private data of a class, providing
more flexibility in your program design.
2. Code Separation:
By using friend functions, you can separate logic from the class while still allowing
access to private members.
3. Operator Overloading:
Friend functions are commonly used for operator overloading, making it possible to
define operations between objects of different classes.
1. Violation of Encapsulation:
Friend functions break the principle of encapsulation, which is one of the core concepts
of object-oriented programming, as they have access to private members of the class.
2. Tight Coupling:
Making a function a friend can tightly couple the function with the class, which may lead
to maintenance issues in large programs.
Summary:
A friend function in C++ is a non-member function that can access private and protected
members of a class.
It is declared inside the class using the friend keyword and is often used for operations like
operator overloading or when a function needs to access private data without being a member.
While friend functions offer flexibility, they can also violate encapsulation and increase tight
coupling between functions and classes.
Function overloading is a feature in C++ that allows you to define multiple functions with the
same name but with different parameter lists. The functions can differ in the number, type, or
order of their parameters. The compiler determines which function to call based on the number
or type of arguments passed during the function call.
You can overload this function by changing the number or type of parameters.
int main() {
cout << "Sum of two integers: " << add(5, 3) << endl; // Calls add(int,
int)
cout << "Sum of two doubles: " << add(5.5, 3.3) << endl; // Calls
add(double, double)
cout << "Sum of three integers: " << add(1, 2, 3) << endl; // Calls
add(int, int, int)
return 0;
}
Output:
mathematica
CopyEdit
Sum of two integers: 8
Sum of two doubles: 8.8
Sum of three integers: 6
Explanation:
2. When the function is called in main(), the compiler selects the correct overloaded
function based on the type and number of arguments passed to it.
Simplification of Code:
Overloading helps in writing clean and simplified code where functions performing
similar tasks can be grouped under a single name.
Handling Different Data Types:
When you need to perform the same operation on different types of data, function
overloading allows you to reuse the function name while handling type differences.
Improved Readability:
Function overloading improves the readability of your code by allowing you to use the
same function name for operations that logically perform similar actions (e.g., add() for
adding integers, doubles, etc.).
class Display {
public:
// Overloaded function for displaying an integer
void show(int i) {
cout << "Integer: " << i << endl;
}
int main() {
Display obj;
return 0;
}
Output:
Integer: 5
Float: 3.14
String: Hello!
class Example {
public:
// Overloaded functions with identical parameter types
void func(int a) {
cout << "Function with integer: " << a << endl;
}
void func(float a) {
cout << "Function with float: " << a << endl;
}
};
int main() {
Example obj;
// Ambiguous call:
// func(5); will be ambiguous because the argument 5 can be interpreted as
both an int and a float
// obj.func(5); // Error: ambiguity between int and float
return 0;
}
In the above case, calling obj.func(5) is ambiguous because 5 could be interpreted as both an
int and a float. To resolve this ambiguity, the argument should be cast to the appropriate type,
or a different function signature should be used.
Summary:
Function overloading allows you to define multiple functions with the same name, but different
parameters.
It simplifies the code, especially when multiple operations need to be performed on different
data types.
Overloading does not consider return type, so the functions must differ in parameters.
Ambiguity can occur if the functions have similar signatures that the compiler can't
differentiate.
Definition:
The this pointer is an implicit pointer available in all non-static member functions of a class.
It points to the current object that is calling the member function.
It is used when you want to refer to the calling object's members explicitly.
Key Features:
To resolve conflicts when local variables or parameters have the same name as class members.
To return the current object from member functions, enabling function chaining.
Simpler Example:
#include <iostream>
using namespace std;
class Student {
string name;
public:
void setName(string name) {
// Use "this" to distinguish between the class member and the
parameter
this->name = name;
}
void display() {
cout << "Student Name: " << this->name << endl;
}
};
int main() {
Student student1;
return 0;
}
Output:
yaml
CopyEdit
Student Name: Ali
1. Naming Conflict:
In the function setName, both the parameter and the class member are named name.
o The parameter name hides the class member name.
o Using this->name, we explicitly refer to the class member name and assign the
parameter value to it.
2. this Usage:
o this->name refers to the name of the current object (student1 in this case).
#include <iostream>
using namespace std;
class Number {
int value;
public:
Number& setValue(int v) {
this->value = v; // Set the value
return *this; // Return the current object
}
void display() {
cout << "Value: " << value << endl;
}
};
int main() {
Number num;
return 0;
}
Output:
Value: 10
How It Works:
1. setValue assigns the parameter v to the class member value using this->value.
2. The function returns the current object (*this), allowing us to chain the display function.
When there's no conflict between local variables and class members, the this pointer is not
necessary.
Summary:
The this pointer is a useful tool for referencing the current object.
It’s mainly used to resolve conflicts and enable function chaining.
The this pointer cannot be used in static member functions because static functions are not
tied to any specific object.
A friend class in C++ is a class that is allowed to access the private and protected members of
another class. This is useful when two or more classes need to share their data closely, without
exposing it to the outside world.
Syntax:
cpp
CopyEdit
class ClassA {
// Declare ClassB as a friend
friend class ClassB;
};
Simpler Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Box {
private:
int length;
public:
Box() : length(0) {} // Constructor to initialize length
int main() {
Box box1;
BoxInspector inspector;
return 0;
}
Output:
1. When you need tight coupling between two or more classes to share sensitive data.
2. To enhance encapsulation, where only specific classes can access certain private members.
3. In cases like operator overloading or when helper classes need access to internals.
Limitations:
1. Friendship violates strict encapsulation since it exposes private members to the friend class.
2. It can make the code harder to maintain if overused.
class A {
private:
int secret;
public:
A() : secret(42) {}
class B {
public:
void showSecret(A a) {
cout << "Secret from class A: " << a.secret << endl;
}
};
int main() {
A objA;
B objB;
return 0;
}
Output:
Summary:
A friend class can access the private and protected members of another class.
It is used when two classes need to work closely with each other.
Friendship should be used judiciously as it breaks strict encapsulation.
Definition:
Static members in a class are shared among all objects of the class. These members belong to
the class itself, not to any specific object.
Static Data Members: A variable shared across all objects of a class.
Static Member Functions: A function that can access only static data members of the class.
Key Features:
Simpler Example:
class Counter {
private:
static int count; // Declare static data member
public:
Counter() {
count++; // Increment static member when object is created
}
void displayCount() {
cout << "Current count: " << count << endl;
}
int main() {
Counter c1, c2; // Create two objects
c1.displayCount();
c2.displayCount();
return 0;
}
Out put:
Current count: 2
Current count: 2
Total objects created: 2
1. count is a static variable, so it is shared among all objects of the Counter class.
2. Whenever an object is created, the constructor increments count.
3. The static function showTotalCount can access the static member directly.
4. #include <iostream>
5. using namespace std;
6.
7. class Math {
8. public:
9. static int add(int a, int b) {
10. return a + b; // Static function can perform
operations without needing an object
11. }
12. };
13.
14. int main() {
15. // Call static function using class name
16. cout << "Sum: " << Math::add(10, 20) << endl;
17.
18. return 0;
19. }
20.
3. Memory Management:
o Static members are stored in a separate memory space, not within each object.
Definition:
1. One-to-One:
A single instance of one class is associated with a single instance of another class.
Example: A person has one passport.
2. One-to-Many:
A single instance of one class is associated with multiple instances of another class.
Example: A teacher teaches multiple students.
3. Many-to-One:
Many instances of one class are associated with a single instance of another class.
Example: Many employees work in one department.
4. Many-to-Many:
Many instances of one class are associated with many instances of another class.
Example: Students enroll in many courses, and each course has many students.
Key Characteristics:
class Student {
private:
string name;
public:
Student(string n) : name(n) {}
class Course {
private:
string courseName;
public:
Course(string cName) : courseName(cName) {}
int main() {
Student student1("Alice");
Student student2("Bob");
Course course("Mathematics");
course.enrollStudent(student1);
course.enrollStudent(student2);
return 0;
}
Output:
CopyEdit
Alice has enrolled in Mathematics
Bob has enrolled in Mathematics
Definition:
Simple Association is a type of relationship between two classes where one class interacts with
another class without any ownership or dependency. This relationship is typically one-to-one
or one-to-many.
It is the most basic type of association where one object uses another object for some operation
or interaction.
Key Characteristics:
1. Independence:
The associated classes remain independent of each other. Deleting one object does not
affect the other.
2. Usage Relationship:
Simple association indicates that one class simply uses or interacts with another class.
3. Directionality:
o Unidirectional Association: One class knows about the other, but not vice versa.
o Bidirectional Association: Both classes know about each other.
Example in C++:
Unidirectional Association:
cpp
CopyEdit
#include <iostream>
#include <string>
using namespace std;
class Car {
private:
string model;
public:
Car(string m) : model(m) {}
class Driver {
private:
string name;
public:
Driver(string n) : name(n) {}
int main() {
Car car1("Toyota Corolla");
Driver driver1("John");
return 0;
}
Output:
csharp
CopyEdit
John is driving a Toyota Corolla
Bidirectional Association:
cpp
CopyEdit
#include <iostream>
#include <string>
using namespace std;
class Team;
class Player {
private:
string name;
public:
Player(string n) : name(n) {}
class Team {
private:
string teamName;
public:
Team(string tName) : teamName(tName) {}
int main() {
Team team("Warriors");
Player player("Alice");
return 0;
}
Output:
csharp
CopyEdit
Alice is part of Warriors
Alice has joined the team Warriors
1. Unidirectional Association:
o The Driver class knows about the Car class.
o The Car class does not know about the Driver.
2. Bidirectional Association:
o The Player class knows about the Team class, and the Team class also knows about the
Player.
When to Use Simple Association:
When one class uses or interacts with another without any complex dependency.
Examples:
o A student borrowing a book from a library.
o A person driving a car.
Definition:
Both aggregation and composition are types of relationships that define how objects are
connected. These relationships are part of the "has-a" relationship, but they differ in terms of
ownership and lifespan dependency.
1. Aggregation
Definition:
Aggregation is a weak "has-a" relationship where one class contains or uses objects of another
class. The contained objects can exist independently of the container object.
Key Characteristics:
No ownership: The container class does not own the objects; they can exist without it.
Loose coupling: The objects are loosely connected.
Example: A university "has" departments, but departments can exist independently.
Example in C++:
cpp
CopyEdit
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Department {
private:
string deptName;
public:
Department(string name) : deptName(name) {}
class University {
private:
string uniName;
vector<Department> departments;
public:
University(string name) : uniName(name) {}
int main() {
Department cs("Computer Science");
Department ee("Electrical Engineering");
uni.showDepartments();
return 0;
}
Output:
diff
CopyEdit
Tech University has the following departments:
- Computer Science
- Electrical Engineering
2. Composition
Definition:
Composition is a strong "has-a" relationship where the contained objects' lifespan is tied to the
lifespan of the container object. If the container is destroyed, the contained objects are also
destroyed.
Key Characteristics:
Example in C++:
cpp
CopyEdit
#include <iostream>
#include <string>
using namespace std;
class Engine {
private:
string type;
public:
Engine(string t) : type(t) {}
class Car {
private:
string model;
Engine engine; // Engine is part of Car (composition)
public:
Car(string m, string engineType) : model(m), engine(engineType) {}
int main() {
Car car("Toyota Corolla", "V8 Engine");
car.showDetails();
return 0;
}
Output:
yaml
CopyEdit
Car Model: Toyota Corolla, Engine Type: V8 Engine
Lifespan Contained objects can exist Contained objects are destroyed with the
Dependency independently. container.
When to Use:
Use aggregation when objects need to exist independently but still maintain a connection.
Use composition when objects are dependent on the container and cannot exist without it
Definition:
It enables code reuse, simplifies maintenance, and represents a "is-a" relationship between
classes.
Key Characteristics:
1. Code Reusability:
Common properties and methods are defined in the parent class and reused by child
classes.
2. Hierarchical Structure:
Classes are organized in a hierarchy, with parent and child relationships.
3. Types of Inheritance in C++:
o Single Inheritance: One child class inherits from one parent class.
o Multiple Inheritance: A child class inherits from multiple parent classes.
o Multilevel Inheritance: A class inherits from another class, which itself is a child class of
another class.
o Hierarchical Inheritance: Multiple child classes inherit from a single parent class.
o Hybrid Inheritance: A combination of two or more types of inheritance.
Example in C++
Single Inheritance:
cpp
CopyEdit
#include <iostream>
using namespace std;
// Base Class
class Animal {
public:
void eat() {
cout << "This animal eats food." << endl;
}
};
// Derived Class
class Dog : public Animal {
public:
void bark() {
cout << "The dog barks!" << endl;
}
};
int main() {
Dog myDog;
return 0;
}
Output:
CopyEdit
This animal eats food.
The dog barks!
Multilevel Inheritance:
cpp
CopyEdit
#include <iostream>
using namespace std;
// Base Class
class Animal {
public:
void eat() {
cout << "This animal eats food." << endl;
}
};
// Intermediate Class
class Mammal : public Animal {
public:
void walk() {
cout << "This mammal walks on land." << endl;
}
};
// Derived Class
class Dog : public Mammal {
public:
void bark() {
cout << "The dog barks!" << endl;
}
};
int main() {
Dog myDog;
return 0;
}
Output:
csharp
CopyEdit
This animal eats food.
This mammal walks on land.
The dog barks!
Multiple Inheritance:
cpp
CopyEdit
#include <iostream>
using namespace std;
// Base Class 1
class Teacher {
public:
void teach() {
cout << "Teaching students." << endl;
}
};
// Base Class 2
class Researcher {
public:
void research() {
cout << "Conducting research." << endl;
}
};
// Derived Class
class Professor : public Teacher, public Researcher {
public:
void publish() {
cout << "Publishing papers." << endl;
}
};
int main() {
Professor prof;
return 0;
}
Output:
CopyEdit
Teaching students.
Conducting research.
Publishing papers.
Advantages of Inheritance:
1. Code Reusability:
Avoids code duplication by reusing parent class features.
2. Scalability:
Makes it easier to extend the functionality of existing code.
3. Simplicity:
Helps organize code logically using parent-child relationships.
4. Maintenance:
Changes in the base class automatically reflect in all derived classes.
Real-World Examples:
1. Vehicles:
o A Car and a Bike inherit properties from a Vehicle class.
o Example: All vehicles have wheels, but the number of wheels may vary.
2. Educational System:
o Student, Teacher, and Admin inherit from a Person class.
Definition:
Generalization and specialization are two inverse concepts used in object-oriented design to
organize classes into hierarchies. They deal with the relationships between a more general class
(parent) and more specific classes (children).
1. Generalization:
Definition:
Generalization is the process of extracting common features (attributes and methods) from two
or more classes and creating a parent class to represent these shared features.
Example in C++:
cpp
CopyEdit
#include <iostream>
using namespace std;
// Derived Class 1
class Car : public Vehicle {
public:
void drive() {
cout << "Car is driving." << endl;
}
};
// Derived Class 2
class Bike : public Vehicle {
public:
void ride() {
cout << "Bike is riding." << endl;
}
};
int main() {
Car myCar;
Bike myBike;
return 0;
}
Output:
csharp
CopyEdit
Vehicle starts.
Car is driving.
Vehicle starts.
Bike is riding.
In this example, the Vehicle class represents the general concept, and Car and Bike specialize it.
2. Specialization:
Definition:
Specialization is the process of creating specific classes from a general class by adding unique
attributes and methods to make them more specialized.
"Is-a" relationship: Specialized classes inherit from the general class.
Bottom-up approach: Starts with a general class and defines specific features for subclasses.
Example in C++:
cpp
CopyEdit
#include <iostream>
using namespace std;
// Generalized Class
class Employee {
protected:
string name;
public:
Employee(string empName) : name(empName) {}
void showName() {
cout << "Name: " << name << endl;
}
};
// Specialized Class 1
class Manager : public Employee {
private:
int teamSize;
public:
Manager(string empName, int size) : Employee(empName), teamSize(size) {}
void showDetails() {
showName();
cout << "Team Size: " << teamSize << endl;
}
};
// Specialized Class 2
class Developer : public Employee {
private:
string programmingLanguage;
public:
Developer(string empName, string language)
: Employee(empName), programmingLanguage(language) {}
void showDetails() {
showName();
cout << "Programming Language: " << programmingLanguage << endl;
}
};
int main() {
Manager manager("Alice", 10);
Developer developer("Bob", "C++");
manager.showDetails();
developer.showDetails();
return 0;
}
Output:
yaml
CopyEdit
Name: Alice
Team Size: 10
Name: Bob
Programming Language: C++
Here, Manager and Developer are specialized classes derived from the generalized Employee
class.
Definition Combining common features into a parent class. Adding specific features to a derived class.
Approach Top-down (group specific into general). Bottom-up (add specifics to general).
Example Vehicle is generalized for Car and Bike. Manager is specialized from Employee.
Real-Life Examples:
1. Generalization:
o A Vehicle class can generalize Car, Bike, and Truck.
o All vehicles share common features like wheels, engines, and the ability to move.
2. Specialization:
o A Doctor class can specialize into Surgeon and Pediatrician.
o Each specialization has unique skills while inheriting general attributes like name, age,
and qualifications.
When a class is derived (inherits) from a base class, the construction and destruction of objects
follow a specific sequence. Understanding how constructors and destructors work in inheritance
is crucial for ensuring proper initialization and cleanup of resources.
Construction in Inheritance:
1. Order of Construction:
o The base class constructor is called first, followed by the derived class constructor.
o This ensures that the base class is initialized before the derived class adds its own
properties.
2. Syntax:
o If the base class constructor takes arguments, the derived class must explicitly call it in
its initializer list.
Example in C++:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base class constructor called!" << endl;
}
};
int main() {
Derived obj; // Creates an object of Derived
return 0;
}
Output:
kotlin
CopyEdit
Base class constructor called!
Derived class constructor called!
class Base {
public:
Base(int x) {
cout << "Base class constructor called with value: " << x << endl;
}
};
int main() {
Derived obj(5, 10);
return 0;
}
Output:
kotlin
CopyEdit
Base class constructor called with value: 5
Derived class constructor called with value: 10
Destruction in Inheritance:
1. Order of Destruction:
o The derived class destructor is called first, followed by the base class destructor.
o This ensures that resources allocated in the derived class are cleaned up before cleaning
up the base class.
Example in C++:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Base {
public:
~Base() {
cout << "Base class destructor called!" << endl;
}
};
int main() {
Derived obj; // Creates and destroys the object
return 0;
}
Output:
kotlin
CopyEdit
Derived class destructor called!
Base class destructor called!
Combined Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Base {
public:
Base() {
cout << "Base class constructor called!" << endl;
}
~Base() {
cout << "Base class destructor called!" << endl;
}
};
~Derived() {
cout << "Derived class destructor called!" << endl;
}
};
int main() {
Derived obj; // Object creation and destruction
return 0;
}
Output:
kotlin
CopyEdit
Base class constructor called!
Derived class constructor called!
Derived class destructor called!
Base class destructor called!
1. Construction Order:
o Base class constructor → Derived class constructor.
o Base class ensures the foundation is properly initialized.
2. Destruction Order:
o Derived class destructor → Base class destructor.
o The derived class releases its resources first before the base class.
3. Argument Passing:
o If the base class constructor requires arguments, the derived class must explicitly pass
those arguments using the initializer list.
Real-Life Example:
"Pointing down the hierarchy" refers to the concept of using base class pointers or references
to point to objects of derived classes in an inheritance hierarchy. This is often used in object-
oriented programming to achieve polymorphism, where the same base class pointer or reference
can refer to different derived class objects dynamically.
Key Concepts:
2. Dynamic Behavior:
o Using a base class pointer or reference, you can invoke methods in the derived class if
they are declared virtual in the base class.
o Without the virtual keyword, the base class methods will be called instead (static
binding).
3. Virtual Functions:
o Virtual functions ensure that the correct function in the derived class is called, even
when using a base class pointer or reference (dynamic binding).
Example in C++:
class Base {
public:
void display() {
cout << "Display from Base class" << endl;
}
};
int main() {
Base* ptr;
Derived derivedObj;
return 0;
}
Output:
csharp
CopyEdit
Display from Base class
Here, the display function in the Base class is called because the function is not virtual.
class Base {
public:
virtual void display() { // Virtual function
cout << "Display from Base class" << endl;
}
};
int main() {
Base* ptr;
Derived derivedObj;
return 0;
}
Output:
csharp
CopyEdit
Display from Derived class
With the virtual keyword, the Derived class's display function is called because of dynamic
binding.
Key Points:
Real-Life Example:
Shape Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Shape {
public:
virtual void draw() {
cout << "Drawing a generic shape." << endl;
}
};
int main() {
Shape* shapePtr;
Circle circleObj;
Rectangle rectObj;
shapePtr = &circleObj;
shapePtr->draw(); // Calls Circle's draw method
shapePtr = &rectObj;
shapePtr->draw(); // Calls Rectangle's draw method
return 0;
}
Output:
css
CopyEdit
Drawing a Circle.
Drawing a Rectangle.
1. Code Flexibility:
o A base class pointer or reference can work with any derived class object.
2. Reusability:
o No need to write separate code for each derived class.
3. Polymorphism:
o Achieves dynamic behavior by calling the correct method at runtime
Polymorphism in OOP
Definition:
Types of Polymorphism:
Function Overloading:
o Multiple functions with the same name but different parameter types or number of
parameters.
o The correct function is chosen at compile-time.
class Display {
public:
void show(int i) {
cout << "Integer: " << i << endl;
}
void show(double d) {
cout << "Double: " << d << endl;
}
void show(string s) {
cout << "String: " << s << endl;
}
};
int main() {
Display obj;
obj.show(10); // Calls show(int)
obj.show(3.14); // Calls show(double)
obj.show("Hello!"); // Calls show(string)
return 0;
}
Output:
makefile
CopyEdit
Integer: 10
Double: 3.14
String: Hello!
Operator Overloading:
o Allows you to define custom behavior for operators like +, -, *, etc., for user-defined
classes.
class Complex {
public:
int real, imag;
int main() {
Complex num1(3, 4), num2(1, 2);
Complex result = num1 + num2; // Uses overloaded operator '+'
cout << "Result: " << result.real << " + " << result.imag << "i" << endl;
return 0;
}
Output:
go
CopyEdit
Result: 4 + 6i
2. Runtime Polymorphism (Dynamic Polymorphism)
class Animal {
public:
virtual void sound() { // Virtual function
cout << "Animal makes a sound!" << endl;
}
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
animalPtr = &cat;
animalPtr->sound(); // Calls Cat's sound function
return 0;
}
Output:
CopyEdit
Dog barks!
Cat meows!
2. Virtual Functions:
o To enable runtime polymorphism, functions in the base class must be marked as
virtual.
o The correct function is called based on the actual object type (not the pointer/reference
type).
Benefits of Polymorphism:
1. Flexibility:
o It provides flexibility to write code that can work with objects of different types through
a common interface.
2. Code Reusability:
o You can use the same method names in different classes, which makes the code easier
to extend and maintain.
3. Simplification:
o It simplifies the code by allowing you to use base class references/pointers to manage
derived class objects, without needing to know their exact type.
Real-Life Example:
Consider a payment system where different payment methods are used, like CreditCard,
PayPal, and BankTransfer.
Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Payment {
public:
virtual void processPayment() {
cout << "Processing payment..." << endl;
}
};
int main() {
Payment* paymentPtr;
CreditCard creditCard;
PayPal paypal;
paymentPtr = &creditCard;
paymentPtr->processPayment(); // Calls CreditCard's method
paymentPtr = &paypal;
paymentPtr->processPayment(); // Calls PayPal's method
return 0;
}
Output:
CopyEdit
Processing Credit Card payment.
Processing PayPal payment.
Summary:
Definition:
An abstract class is a class that cannot be instantiated directly. It is designed to be used as a base
class for other classes. It contains at least one pure virtual function, which makes it impossible
to create objects of the abstract class. The derived classes must override the pure virtual
functions to provide their implementation.
Key Concepts:
2. Abstract Class:
o A class with at least one pure virtual function is called an abstract class.
o You cannot instantiate an object of an abstract class, but you can create pointers or
references to it.
3. Derived Class:
o Derived classes that inherit from an abstract class must override all pure virtual
functions to become concrete (non-abstract) classes.
// Abstract Class
class Shape {
public:
virtual void draw() = 0; // Pure virtual function (abstract)
// Derived class
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
shapePtr = &circle;
shapePtr->draw(); // Calls Circle's draw method
cout << "Circle area: " << shapePtr->area() << endl;
shapePtr = &rectangle;
shapePtr->draw(); // Calls Rectangle's draw method
cout << "Rectangle area: " << shapePtr->area() << endl;
return 0;
}
Output:
mathematica
CopyEdit
Drawing a Circle.
Circle area: 78.5
Drawing a Rectangle.
Rectangle area: 24
2. For Polymorphism:
o Abstract classes provide a foundation for polymorphic behavior, where a base class
pointer or reference can be used to interact with objects of different derived classes.
3. Code Structure:
o They help in organizing code and ensuring that certain methods are implemented in
derived classes, which provides a clear structure for your program.
Real-Life Example:
You can define an abstract class PaymentMethod with a pure virtual function
processPayment().
Different payment methods, like CreditCard, PayPal, and BankTransfer, can then inherit
from PaymentMethod and implement the processPayment() method according to their own
requirements.
// Abstract class
class PaymentMethod {
public:
virtual void processPayment() = 0; // Pure virtual function
};
// Derived classes
class CreditCard : public PaymentMethod {
public:
void processPayment() override {
cout << "Processing payment through Credit Card." << endl;
}
};
int main() {
PaymentMethod* paymentPtr;
CreditCard creditCard;
PayPal paypal;
paymentPtr = &creditCard;
paymentPtr->processPayment(); // Calls CreditCard's method
paymentPtr = &paypal;
paymentPtr->processPayment(); // Calls PayPal's method
return 0;
}
Output:
CopyEdit
Processing payment through Credit Card.
Processing payment through PayPal.
Summary:
An abstract class cannot be instantiated and contains at least one pure virtual function.
Derived classes must provide implementations for all pure virtual functions.
Abstract classes allow for defining common interfaces, promoting code reusability and
polymorphism.
Virtual Methods:
A virtual method is a method in the base class that you expect to override in a derived class.
The main purpose of a virtual method is to support polymorphism, allowing the correct method
to be called based on the actual object type at runtime, even when using pointers or references to
the base class.
Pure Virtual Methods:
A pure virtual method is a method that must be implemented by derived classes. It has no
implementation in the base class and is declared by adding = 0 at the end of the method
signature. Any class containing a pure virtual function is considered an abstract class, which
means you cannot instantiate objects of that class directly.
Key Differences:
1. Virtual Method:
o Declared in the base class with the virtual keyword.
o Can have a default implementation in the base class.
o Derived classes can override it.
// Derived class that overrides the virtual function and implements the pure
virtual function
class Dog : public Animal {
public:
void speak() override { // Override virtual function
cout << "Dog barks." << endl;
}
int main() {
// Animal animal; // Error: Cannot instantiate an abstract class
Dog dog;
Cat cat;
animalPtr = &dog;
animalPtr->speak(); // Calls Dog's speak method
animalPtr->move(); // Calls Dog's move method
animalPtr = &cat;
animalPtr->speak(); // Calls Cat's speak method
animalPtr->move(); // Calls Cat's move method
return 0;
}
Output:
CopyEdit
Dog barks.
Dog runs.
Cat meows.
Cat jumps.
Explanation of the Example:
3. Using Polymorphism:
o The Animal* animalPtr pointer can point to objects of Dog and Cat, allowing us to
call the correct version of the speak() and move() methods based on the actual
object type at runtime.
1. Polymorphism:
o Virtual methods enable polymorphism by allowing derived classes to override the base
class methods. The correct method is called based on the actual object type, even when
using base class pointers or references.
2. Abstract Classes:
o Pure virtual methods are used to create abstract classes, ensuring that derived classes
implement specific methods, thus providing a structured interface for the objects of
those classes.
3. Design Flexibility:
o Virtual methods and pure virtual methods give you the flexibility to design flexible and
extensible systems, where new classes can be added easily without modifying existing
code.
Consider a scenario where you're designing a system for animals and vehicles:
Both Animal and Vehicle can have a method move(), but the implementation of the movement
will differ. For animals, the move() method could be overridden for different types of animals
like birds, dogs, or cats. For vehicles, move() could be implemented differently for cars, trucks,
and bicycles.
In this case, move() could be a pure virtual method, and each derived class would implement it
differently.
Summary:
Virtual Method: A function in the base class that can be overridden by derived classes, allowing
for runtime polymorphism.
Pure Virtual Method: A function that has no implementation in the base class and must be
implemented by derived classes. It makes the base class abstract.
Definition:
In C++, streams are used for input and output operations. A stream is an abstraction that allows
the transfer of data between programs and devices (such as keyboard, screen, files, etc.). The C+
+ Standard Library provides classes to handle input and output through streams.
1. Output:
o << (Insertion operator) is used to send data to the output stream.
2. Input:
o >> (Extraction operator) is used to extract data from the input stream.
int main() {
int age;
string name;
Output Example:
mathematica
CopyEdit
Enter your name: John
Enter your age: 25
Hello, John!
You are 25 years old.
You can control the format of the output using manipulators and flags in C++.
int main() {
double price = 123.456;
return 0;
}
Output Example:
bash
CopyEdit
Price: $123.46
*****45
setprecision(2): This sets the precision of the floating-point number to 2 decimal places.
setw(10): This sets the width of the next output to 10 characters.
setfill('*'): This fills the empty space in the field with the * character.
int main() {
int number = -5;
return 0;
}
Output Example:
javascript
CopyEdit
Error: Number cannot be negative!
This is a log message.
C++ also provides the ability to handle file input and output using streams. You can read from
and write to files using ifstream and ofstream.
int main() {
// Writing to a file
ofstream outFile("example.txt");
if (outFile.is_open()) {
outFile << "This is an example text in the file." << endl;
outFile.close();
} else {
cout << "Unable to open file for writing!" << endl;
}
return 0;
}
Output Example:
vbnet
CopyEdit
This is an example text in the file.
Summary:
Definition:
Dynamic Binding (also called Late Binding) refers to the process where the method to be called
is decided at runtime, not at compile time. It is used primarily in the context of polymorphism
in object-oriented programming (OOP). This mechanism allows a program to determine the
correct function or method to invoke based on the type of object that calls the method, rather
than the type of reference or pointer that is used.
class Animal {
public:
virtual void sound() { // Virtual function
cout << "Some animal sound" << endl;
}
};
int main() {
Animal* animalPtr;
Dog dog;
Cat cat;
return 0;
}
Explanation of the Example:
Output:
CopyEdit
Woof! Woof!
Meow! Meow!
To enable dynamic binding, the function in the base class should be declared as virtual. This
tells the compiler to use the vtable (virtual table) mechanism to resolve the function call at
runtime. The vtable is a table of function pointers maintained by the compiler for classes that use
virtual functions.
Polymorphism: It allows you to write more flexible and reusable code. With dynamic binding,
the same base class pointer can be used to refer to objects of different derived classes, and the
correct function will be invoked based on the actual type of the object.
Extensibility: New derived classes can be added, and existing code can continue to work without
modification, thanks to dynamic binding.
Summary:
Definition:
A virtual destructor is a destructor that is declared with the virtual keyword in a base class. It
ensures that the destructors of derived classes are called when an object is deleted through a base
class pointer. This is important in the context of polymorphism to prevent memory leaks and
ensure that the resources held by the derived class objects are properly cleaned up.
Without a virtual destructor, if you delete a derived class object using a base class pointer, the
destructor of the base class will be called, and the derived class’s destructor won’t be invoked,
potentially leading to resource leaks (e.g., memory not being freed).
When you use a base class pointer to point to a derived class object and delete it, the proper
destructor (i.e., the destructor of the derived class) must be called to release the resources
allocated by that derived class. If the destructor in the base class is not virtual, only the base class
destructor will be called, potentially skipping the cleanup in the derived class.
class Base {
public:
// Virtual Destructor
virtual ~Base() {
cout << "Base class Destructor called!" << endl;
}
};
int main() {
Base* basePtr = new Derived();
return 0;
}
Output:
kotlin
CopyEdit
Derived class Destructor called!
Base class Destructor called!
Without the virtual destructor in the base class, the output would have been:
kotlin
CopyEdit
Base class Destructor called!
This is because the base class destructor would be called directly without invoking the derived
class's destructor.
Important Notes:
Always declare destructors as virtual in base classes if you intend to use inheritance and
polymorphism.
If the destructor of the base class is not virtual, the derived class destructor will not be called
when deleting an object through a base class pointer.
If a class has a virtual destructor, the destructor of every derived class should also be virtual
(either explicitly or inherited).
Summary:
A virtual destructor ensures that the derived class destructor is called when deleting an object
through a base class pointer.
It is important in preventing resource leaks and properly cleaning up resources in derived
classes.
Always use a virtual destructor in base classes when using polymorphism.
Definition:
Operator overloading is the process of defining a new behavior for operators in C++ for user-
defined data types (such as classes). By overloading operators, you can use operators like +, -, *,
<<, >>, etc., with objects of custom classes in the same way they are used with built-in types like
int or double.
When you overload an operator, you specify what it should do when applied to objects of a class.
For example, you can define what happens when two Complex number objects are added
together using the + operator.
Operator overloading enhances code readability and makes the use of user-defined types more
intuitive. Instead of using member functions or helper functions to perform operations,
overloaded operators allow you to perform operations directly on objects in a more natural way.
Where:
Here’s a simple example of overloading the + operator for a Complex Number class:
cpp
CopyEdit
#include <iostream>
using namespace std;
class Complex {
public:
int real;
int imag;
int main() {
Complex c1(2, 3), c2(4, 5), c3;
return 0;
}
Explanation of the Example:
1. Complex class has two data members: real and imag to represent the real and imaginary parts
of a complex number.
2. The + operator is overloaded to add two Complex objects.
o Inside the overloaded + operator, a new Complex object (temp) is created, and the real
and imaginary parts of the two objects are added together.
3. The display function prints the complex number in the form a + bi.
4. In the main() function, we create two Complex objects, c1 and c2, and add them using the
overloaded + operator.
Output:
mathematica
CopyEdit
Sum of Complex Numbers: 6 + 8i
1. Unary Operators: Operators that work on a single operand (e.g., ++, --, !).
2. Binary Operators: Operators that work on two operands (e.g., +, -, *, /).
3. Relational Operators: Operators that compare two operands (e.g., ==, !=, >, <).
4. Stream Insertion (<<) and Extraction (>>) Operators: Overloaded to handle input and output
for custom data types.
class Counter {
public:
int count;
Counter(int c = 0) : count(c) {}
void display() {
cout << "Count: " << count << endl;
}
};
int main() {
Counter c1(5);
++c1; // Using overloaded prefix ++ operator
c1.display(); // Output: Count: 6
return 0;
}
Output:
makefile
CopyEdit
Count: 6
Things to Remember:
1. Unary operators take a single operand, while binary operators take two operands.
2. Overloading operators doesn’t change the precedence or associativity of the operators.
3. Some operators cannot be overloaded, such as the scope resolution operator ::, the size-of
operator sizeof, and the member access operator ..
4. Overloading should be meaningful. For instance, overloading + for an object that doesn’t
logically support addition can confuse users of your class.
Summary:
Operator overloading allows you to define custom behavior for operators in user-defined
classes.
It improves code readability and makes the use of your class objects more natural.
You can overload unary, binary, and relational operators for your classes.
Operator overloading requires careful design to ensure that it makes sense for the class and
operator being overloaded.
Exception handling in C++ allows you to deal with runtime errors in a structured way,
preventing the program from crashing. It involves detecting and responding to exceptional
situations (errors) in a program through the use of try, catch, and throw blocks.
The try block contains the code that may cause an exception.
The catch blocks follow, handling specific types of exceptions.
The catch (...) block is a catch-all handler that catches any type of exception.
int main() {
int num1 = 10, num2 = 0;
try {
int result = divide(num1, num2); // This will throw an exception
cout << "Result: " << result << endl;
}
catch (const char* msg) {
cout << "Error: " << msg << endl; // Handle exception
}
return 0;
}
Output:
vbnet
CopyEdit
Error: Division by zero is not allowed!
You can have multiple catch blocks to handle different types of exceptions, such as numeric
exceptions, out-of-bounds exceptions, etc.
cpp
CopyEdit
#include <iostream>
using namespace std;
void testFunction(int a) {
if (a < 0) {
throw "Negative number error!";
} else if (a == 0) {
throw 0;
} else {
cout << "Valid number: " << a << endl;
}
}
int main() {
try {
testFunction(-5); // This will throw a string
} catch (const char* e) {
cout << "Caught exception: " << e << endl;
}
try {
testFunction(0); // This will throw an integer
} catch (int e) {
cout << "Caught exception: Integer " << e << endl;
}
return 0;
}
Output:
mathematica
CopyEdit
Caught exception: Negative number error!
Caught exception: Integer 0
Throwing Exceptions:
You can throw any type of object or value in C++. Typically, objects of standard exception
types or user-defined types are thrown. You can throw a variety of objects, including:
void riskyFunction() {
throw CustomException(); // Throwing a custom exception
}
int main() {
try {
riskyFunction();
} catch (const CustomException& e) {
cout << e.what() << endl; // Handle the custom exception
}
return 0;
}
Output:
php
CopyEdit
Custom Exception occurred!
Important Notes:
try block should not contain code that might throw exceptions without handling them.
You can throw exceptions explicitly using the throw keyword.
Exceptions are caught in the order of the catch blocks. Once a matching catch block is found,
the other blocks are ignored.
If no catch block matches the thrown exception, the program will terminate with an uncaught
exception.
Exception safety is crucial in complex programs to ensure that resources (like memory, files) are
released even if exceptions are thrown.
Summary:
Definition:
Generic Classes and Functions in C++ refer to the ability to write code that can operate with
any data type. This is achieved using templates, which allow a class or a function to work with
any type that the programmer specifies when calling or instantiating the class or function.
Generic Class: A class that can work with any data type.
Generic Function: A function that can work with any data type.
In C++, templates are used to create generic classes and functions. Templates allow you to write
code that is flexible and reusable for different types, without having to rewrite it for each type.
T is the generic type placeholder that can be replaced by any data type (e.g., int, float,
double, etc.).
typename tells the compiler that T is a data type.
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
int x = 10, y = 20;
cout << "Before swapping: x = " << x << ", y = " << y << endl;
cout << "After swapping: x = " << x << ", y = " << y << endl;
cout << "After swapping: a = " << a << ", b = " << b << endl;
return 0;
}
Output:
less
CopyEdit
Before swapping: x = 10, y = 20
After swapping: x = 20, y = 10
Before swapping: a = 1.5, b = 2.5
After swapping: a = 2.5, b = 1.5
The swapValues() function is a generic function that swaps two values of any data type.
The T type is replaced by the actual type when calling the function.
o The first call swaps two int values.
o The second call swaps two double values.
This demonstrates that the same function can be reused for different data types by using
templates.
Here's an example of a generic class for a simple Box that can hold any type of data:
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
Box<int> intBox;
intBox.setValue(100);
cout << "Box holds an integer: " << intBox.getValue() << endl;
Box<string> strBox;
strBox.setValue("Hello, World!");
cout << "Box holds a string: " << strBox.getValue() << endl;
return 0;
}
Output:
mathematica
CopyEdit
Box holds an integer: 100
Box holds a string: Hello, World!
Advantages of Templates:
1. Code Reusability: You can write functions and classes that work with any data type, avoiding
redundancy.
2. Type Safety: Templates provide strong type checking at compile time, ensuring that only valid
operations can be performed on the types.
3. Performance: Templates allow the code to be optimized by the compiler for each specific type.
Specialization of Templates:
In some cases, you might want to have a specialized version of a template for a particular type.
This can be done using template specialization.
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
Box<int> intBox;
intBox.setValue(5);
cout << "Box holds an integer: " << intBox.getValue() << endl; // Custom
behavior for int
Box<double> doubleBox;
doubleBox.setValue(3.5);
cout << "Box holds a double: " << doubleBox.getValue() << endl; // Normal
behavior for double
return 0;
}
Output:
mathematica
CopyEdit
Box holds an integer: 50
Box holds a double: 3.5
Summary:
Generic classes and generic functions are used in C++ to create flexible, reusable code that can
operate on any data type.
Templates are the key mechanism for writing generic code.
Templates provide the advantage of code reusability and type safety.
You can also use template specialization to provide custom behavior for specific types.
Templates in C++
Definition:
In C++, templates are a feature that allows you to write generic code. Templates enable you to
define classes and functions that can work with any data type, providing a mechanism to create
generic functions and generic classes.
Templates provide a way to define a function or class with a placeholder for the data type, and
the actual data type will be specified when the function or class is used.
Types of Templates:
1. Function Templates: A template for a function that can operate with any data type.
2. Class Templates: A template for a class that can operate with any data type.
Function Templates:
A function template defines a blueprint for functions that can operate with any data type. Here's
how you can define and use a function template:
Syntax:
cpp
CopyEdit
template <typename T>
ReturnType functionName(T param) {
// body of the function
}
Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
cout << "Sum of integers: " << add(10, 20) << endl; // Works with
integers
cout << "Sum of doubles: " << add(10.5, 20.5) << endl; // Works with
doubles
return 0;
}
Output:
mathematica
CopyEdit
Sum of integers: 30
Sum of doubles: 31
Class Templates:
Class templates allow you to define a class that can work with any data type. When you define a
class template, you specify a placeholder data type that can later be replaced with any type when
you create an object of that class.
Syntax:
cpp
CopyEdit
template <typename T>
class ClassName {
T data;
public:
void setData(T value) {
data = value;
}
T getData() {
return data;
}
};
Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
Box<int> intBox;
intBox.setValue(100);
cout << "Box holds an integer: " << intBox.getValue() << endl;
Box<string> strBox;
strBox.setValue("Hello");
cout << "Box holds a string: " << strBox.getValue() << endl;
return 0;
}
Output:
mathematica
CopyEdit
Box holds an integer: 100
Box holds a string: Hello
Template Specialization:
Sometimes, you might need to provide a specialized version of a template for a specific data
type. This can be done using template specialization, where you write a specific version of the
template for certain data types.
Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
Box<int> intBox;
intBox.setValue(5);
cout << "Box holds an integer: " << intBox.getValue() << endl; // Custom
behavior for int
Box<double> doubleBox;
doubleBox.setValue(3.5);
cout << "Box holds a double: " << doubleBox.getValue() << endl; // Normal
behavior for double
return 0;
}
Output:
mathematica
CopyEdit
Box holds an integer: 50
Box holds a double: 3.5
You can use not only typename but also class as the keyword to declare template parameters.
Both keywords are interchangeable.
cpp
CopyEdit
template <class T>
T multiply(T a, T b) {
return a * b;
}
You can define templates with more than one parameter. This is useful when a function or class
needs to work with more than one type.
Example:
cpp
CopyEdit
#include <iostream>
using namespace std;
int main() {
cout << "Sum of integer and double: " << add(5, 4.5) << endl;
return 0;
}
Output:
php
CopyEdit
Sum of integer and double: 9.5
Advantages of Templates:
1. Code Reusability: Templates allow writing a function or class once and using it for any data type.
2. Type Safety: Templates provide compile-time type checking.
3. Flexibility: With templates, you can create functions and classes that adapt to different data
types.
Summary:
Templates in C++ allow you to create generic classes and functions that can operate on any data
type.
Function templates and class templates provide the flexibility to write reusable code.
Template specialization allows customization for specific types.
Templates enhance code reusability, type safety, and provide flexibility in handling various data
types.