Unit 4 - Notes
CSE202
Unit 4: Operator Overloading, Type Conversion and Inheritance
1. Operator Overloading
Operator overloading allows C++ operators to work with user-defined data types (objects) in the same way they work with primitive types (int, float, etc.). It is a type of compile-time polymorphism.
General Syntax
return_type operator symbol (arguments) {
// ... body ...
}
Rules for Operator Overloading
- Existing operators cannot be created (e.g., you cannot create
**). - The precedence and associativity of operators cannot be changed.
- At least one operand must be a user-defined type.
- Operators that cannot be overloaded:
.(Member access).*(Member pointer access)::(Scope resolution)?:(Ternary operator)sizeof
Unary Operator Overloading
Unary operators act on a single operand (e.g., ++, --, - negation).
- When overloaded as a member function, it takes no arguments.
- When overloaded as a friend function, it takes one argument.
Example: Overloading Unary Minus (-)
class Space {
int x, y, z;
public:
Space(int a, int b, int c) : x(a), y(b), z(c) {}
// Member function
void operator-() {
x = -x;
y = -y;
z = -z;
}
};
int main() {
Space S(10, -20, 30);
-S; // Activates operator-() function
}
Binary Operator Overloading
Binary operators act on two operands (e.g., +, -, *, /, >).
- When overloaded as a member function, it takes one argument (the right-hand operand). The left-hand operand is the calling object (
this). - When overloaded as a friend function, it takes two arguments.
Example: Overloading Binary Plus (+)
class Complex {
float real, imag;
public:
Complex(float r = 0, float i = 0) : real(r), imag(i) {}
// Overloading + operator
Complex operator+(const Complex &obj) {
Complex temp;
temp.real = real + obj.real;
temp.imag = imag + obj.imag;
return temp;
}
};
int main() {
Complex c1(2.5, 3.5), c2(1.6, 2.7);
Complex c3 = c1 + c2; // Equivalent to c1.operator+(c2)
}
2. Type Conversions
C++ handles automatic conversion for built-in types (e.g., int to float). For user-defined types, we must define the conversion logic.
A. Basic Type to Class Type
This is achieved using a Constructor. The constructor must accept a single argument of the basic type.
class Time {
int hrs, mins;
public:
// Constructor converts int (duration in mins) to Time object
Time(int t) {
hrs = t / 60;
mins = t % 60;
}
};
int main() {
int duration = 85;
Time T1 = duration; // Calls constructor Time(int)
}
B. Class Type to Basic Type
This is achieved using a Casting Operator Function.
- Syntax:
operator typeName() - It must be a member function.
- It must not specify a return type (the return type is inferred from the function name).
- It should not take arguments.
class Vector {
int magnitude;
public:
Vector(int m) : magnitude(m) {}
// Conversion function: Vector to int
operator int() {
return magnitude;
}
};
int main() {
Vector v1(100);
int val = v1; // Calls operator int()
}
3. Inheritance Basics
Inheritance is the process by which objects of one class acquire the properties and behaviors of another class. It supports the concept of Reusability.
- Base Class (Parent): The existing class.
- Derived Class (Child): The new class created from the base class.
Syntax:
class DerivedClassName : visibility_mode BaseClassName {
// members of derived class
};
Visibility Modes (Access Specifiers)
The visibility mode determines how the base class members are inherited into the derived class.
| Mode | Base: Public Members | Base: Protected Members | Base: Private Members |
|---|---|---|---|
| Public | Public in Derived | Protected in Derived | Not Inherited |
| Protected | Protected in Derived | Protected in Derived | Not Inherited |
| Private | Private in Derived | Private in Derived | Not Inherited |
Note: Private members of the base class are never directly accessible in the derived class, regardless of the inheritance mode.
4. Types of Inheritance
1. Single Inheritance
A derived class inherits from only one base class.
- Structure: A B
2. Multilevel Inheritance
A class is derived from another derived class.
- Structure: A B C
- B inherits from A; C inherits from B.
3. Multiple Inheritance
A derived class inherits from more than one base class simultaneously.
- Structure: A + B C
- Syntax:
class C : public A, public B { ... };
4. Hierarchical Inheritance
Multiple derived classes inherit from a single base class.
- Structure: A B, A C, A D
5. Hybrid Inheritance
A combination of two or more types of inheritance (e.g., Multilevel + Multiple). This often leads to the "Diamond Problem."
5. Overriding Member Functions
Function Overriding occurs when a derived class defines a function with the exact same name and signature as a function in the base class. The derived class version "hides" the base class version.
Accessing the Overridden Function:
To access the base class function from the derived class or main, use the Scope Resolution Operator (::).
class Base {
public:
void show() { cout << "Base"; }
};
class Derived : public Base {
public:
void show() { cout << "Derived"; } // Overrides Base::show()
};
int main() {
Derived d;
d.show(); // Prints "Derived"
d.Base::show(); // Prints "Base"
}
6. Order of Execution: Constructors and Destructors
Constructors
Constructors are executed in the order of inheritance.
- Base Class Constructor is called first.
- Derived Class Constructor is called second.
In Multiple Inheritance (Class C : A, B), Constructor A runs, then B, then C.
Passing Arguments to Base Constructor:
If the base class constructor is parameterized, the derived class must pass the arguments using an initializer list.
Derived(int x, int y) : Base(x) {
// Derived initialization using y
}
Destructors
Destructors are executed in the reverse order of construction.
- Derived Class Destructor is called first.
- Base Class Destructor is called second.
7. Resolving Ambiguities in Inheritance
Ambiguity arises in Multiple Inheritance when two base classes have a function with the same name. The compiler cannot distinguish which function to call.
Example:
class A { public: void display() { ... } };
class B { public: void display() { ... } };
class C : public A, public B { ... };
// In main:
C obj;
obj.display(); // Error: Ambiguous
Solution: Use the scope resolution operator to specify the class.
obj.A::display(); // Calls A's version
obj.B::display(); // Calls B's version
8. Virtual Base Class (The Diamond Problem)
The Problem:
In Hybrid Inheritance (e.g., A B, A C, B+C D), class D inherits from both B and C. Since both B and C inherit from A, class D ends up with two copies of class A's data members. This causes ambiguity and memory waste.
The Solution:
Make the inheritance of the common ancestor virtual. This ensures only one copy of the base class member variables is inherited by the grandchild class.
Syntax:
class A { public: int x; };
class B : virtual public A { ... }; // Virtual inheritance
class C : public virtual A { ... }; // Virtual inheritance (order of keywords doesn't matter)
class D : public B, public C { ... };
int main() {
D obj;
obj.x = 10; // Valid (no ambiguity because only one 'x' exists)
}
9. Aggregation and Composition
These concepts represent a "Has-A" relationship, as opposed to Inheritance which is an "Is-A" relationship. They involve embedding objects of one class into another.
Composition (Strong Association)
- Concept: The contained object cannot exist without the container object. If the parent is destroyed, the child is destroyed.
- Example: A
Househas aRoom. A room cannot exist without the house. - Implementation: Usually implemented using direct object members.
CPPclass Room { ... }; class House { Room r1; // House creates and manages Room directly };
Aggregation (Weak Association)
- Concept: The contained object can exist independently of the container object. The parent has a reference to the child, but does not exclusively own it.
- Example: A
Carhas aDriver. The driver can exist without the car. - Implementation: Usually implemented using pointers or references.
CPPclass Driver { ... }; class Car { Driver* d1; // Car points to a Driver public: void setDriver(Driver* d) { d1 = d; } };