0% found this document useful (0 votes)
13 views

Advanced Programming c412

Uploaded by

yousser1bedeer
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
13 views

Advanced Programming c412

Uploaded by

yousser1bedeer
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 133

Port Said University Faculty of Science Department of

Math. & Comp. Sci.

Advanced Programming
C412
For Fourth Level

Information Technology and


Computer Science
Prepared by
DR. MOSTAFA HERAJY

2023-2024
2
Advanced Programming (C412)

Compiled by
Dr. Mostafa Herajy
Faculty of Science, Port Said University

September 28, 2024


Contents

Lecture 1: Introduction 5
Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Encapsulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Advantages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

Lecture 2: Pointers 7
Allocating and Deallocating Pointers in C++ . . . . . . . . . . . . . . . . . . . . 8
Allocating Memory: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Deallocating Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
C++ pointers and arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Pointers and Structures . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Pointer to Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Arrays of Structures and Pointers . . . . . . . . . . . . . . . . . . . . . . . 12
Structures Containing Pointers . . . . . . . . . . . . . . . . . . . . . . . . 12
Smart Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
std::unique_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
std::shared_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
std::weak_ptr . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
More examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
Pointers to functions in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
Declaring a Pointer to a Function: . . . . . . . . . . . . . . . . . . . . . . 15
Initializing a Pointer to a Function: . . . . . . . . . . . . . . . . . . . . . 15
Calling a Function Through a Pointer: . . . . . . . . . . . . . . . . . . . . 15
Passing Pointers to Functions as Arguments: . . . . . . . . . . . . . . . . 16
Lab 2: C++ Pointers Practice . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
Practice Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18

Lecture 3: C++ Classes 19


Object Oriented Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
What are classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Relationship between Objects and classes . . . . . . . . . . . . . . . . . . . . . 23
Constructor: revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Default Constructor: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Parameterized Constructor: . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Copy Constructor: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
2 CO N TENTS

Initializer List Constructor: . . . . . . . . . . . . . . . . . . . . . . . . . . 26


Move Constructor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Destructor: revisited . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
Classes and Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Pointer to Objects: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Passing Objects by Pointer: . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Dynamically Allocated Objects: . . . . . . . . . . . . . . . . . . . . . . . . 30
Pointer to Class Members: . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Structures and Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Structures: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Classes: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31
Static Members . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Static Member Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
Static Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
Constant Member Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
Lab 3: Introduction to C++ Classes . . . . . . . . . . . . . . . . . . . . . . . . . 36
Practice Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

Lecture 4: Inheritance and Polymorphism 41


Base Class (Parent Class) . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Derived Class (Child Class) . . . . . . . . . . . . . . . . . . . . . . . . . . 41
Access Specifiers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Constructor and Destructor Inheritance . . . . . . . . . . . . . . . . . . . 42
Method Overriding: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
The Shape Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
Multiple Inheritance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Polymorphism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
Compile-Time Polymorphism (Static Polymorphism) . . . . . . . . . . . 45
Runtime Polymorphism (Dynamic Polymorphism) . . . . . . . . . . . . . 46
Shape drawing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
Abstract Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
Interface Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
Best practices and guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Lab 4: Inheritance in C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

Lecture 5: Operator Overloading 55


Introduction to Operator Overloading . . . . . . . . . . . . . . . . . . . . . . . 55
Overloading Unary Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Overloading Binary Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
Overloading Comparison Operators . . . . . . . . . . . . . . . . . . . . . . . . 59
Overloading Subscript Operator ([]) . . . . . . . . . . . . . . . . . . . . . . . 60
Overloading Function Call Operator (()) . . . . . . . . . . . . . . . . . . . . . . 61
Overloading Type Conversion Operators . . . . . . . . . . . . . . . . . . . . . . 63
Common Pitfalls and Best Practices . . . . . . . . . . . . . . . . . . . . . . . . 64
Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
Practice Lab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

Lecture 6: Writing Big Programs 69


Header and Implementation Files . . . . . . . . . . . . . . . . . . . . . . . . . 69
Header Files (.h or .hpp) . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Implementation Files (.cpp) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
Make and CMake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Make . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
Example Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
Using the Makefile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
CMake . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73

Lecture 7: Design Patterns 75


Creational Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Singleton Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75
Factory Method Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
Abstract Factory Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Builder Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Structural Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Adapter Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
Decorator Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
Composite Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
Proxy Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
Behavioral Patterns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Observer Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Strategy Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
Command Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
State Pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Practice Lab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99

Lecture 9: Standard Template Library (STL) 101


What is the Standard Template Library (STL)? . . . . . . . . . . . . . . . . . . . 101
Key Components of the STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Function Objects (Functors) . . . . . . . . . . . . . . . . . . . . . . . . . 102
Allocators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Benefits of Using the STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Code Reusability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Standardization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Performance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
Safety . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
4 CO N TENTS

Using the STL in Your Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102


Example of STL Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Vectors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103
Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
List . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106
Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Maps . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Pairs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
Tuples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 112
Queues . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Optional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115
Example of STL Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Find . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 116
Find_if . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
for_each . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
Count . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
Lab 9: Exploring C++ Standard Library Containers . . . . . . . . . . . . . . . . 121
Practice Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122

Lecture 10: Files and Streams in C++ 123


Understanding Streams . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
What Are Streams? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Stream Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 123
Stream States and Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
File Input and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Opening and Closing Files . . . . . . . . . . . . . . . . . . . . . . . . . . 124
Reading from Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Writing to Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Error Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Checking for Errors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 125
Exception Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Working with Binary files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126
Writing Binary Data to a File . . . . . . . . . . . . . . . . . . . . . . . . . 126
Reading Binary Data from a File . . . . . . . . . . . . . . . . . . . . . . . 127
Binary File Considerations . . . . . . . . . . . . . . . . . . . . . . . . . . 128
Lab 10: C++ Streams: Text and Binary File Operations . . . . . . . . . . . . . . 128
Practice Questions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 130
Lecture 1: Introduction

Object-Oriented Programming, often abbreviated as OOP, is a widely adopted and power-


ful programming paradigm that revolutionized the way software is designed, organized,
and maintained. It is based on the concept of "objects," which represent real-world
entities, concepts, or things, and the interactions between them. OOP encourages a
more intuitive and natural way of thinking about software, making it easier to model
complex systems and manage code complexity.
At its core, OOP revolves around the following key principles and concepts:

Objects
Objects are instances of classes and serve as the building blocks of OOP. They encapsulate
both data (attributes or properties) and behavior (methods or functions) related to
a specific entity or concept. Objects are self-contained, allowing for modularity and
reusability.

Classes
Classes are blueprints or templates for creating objects. They define the structure and
behavior that objects of the class will have. Classes encapsulate data and methods,
providing a clear and organized way to model entities in a program.

Encapsulation
Encapsulation is the concept of bundling data (attributes) and methods (functions) that
operate on that data into a single unit (i.e., an object or class). It enforces data protec-
tion by specifying access control (public, private, protected), allowing for controlled
interaction with object internals.

Inheritance
Inheritance allows one class (the derived or child class) to inherit properties and be-
haviors from another class (the base or parent class). It promotes code reuse and the
creation of hierarchical class structures.

Polymorphism
Polymorphism enables objects of different classes to be treated as objects of a common
base class. It allows for dynamic method invocation based on the actual type of the
object, leading to flexible and extensible code.
6 L E C T URE 1: INTRODUCTION

Advantages
Object-Oriented Programming offers several benefits, including:

• Modularity: OOP promotes breaking down complex systems into smaller, manage-
able pieces (objects and classes), making it easier to develop and maintain large
software projects.

• Reusability: By creating reusable classes and objects, OOP reduces redundant code
and accelerates development, as well as reducing the likelihood of errors.

• Flexibility: OOP allows for easy modification and extension of code by adding new
classes and altering existing ones without affecting other parts of the system.

• Abstraction and Clarity: OOP encourages modeling software based on real-world


entities and concepts, improving code readability and comprehension.

OOP is not limited to any specific programming language and can be implemented
in various languages, including C++, Java, Python, and many others. Its principles and
concepts provide a versatile and scalable approach to software development, making it
a cornerstone of modern programming.
Lecture 2: Pointers

In C++, a pointer is a variable that stores the memory address of another variable. Point-
ers allow you to indirectly manipulate data by referencing the memory location where
data is stored. They are a powerful feature in C++ and provide fine-grained control
over memory allocation and data manipulation. Here are some key points about C++
pointers:

1. Memory Address: A pointer contains the memory address of a variable or an object.


This address represents where the data is stored in memory.

2. Declaration: To declare a pointer, you use an asterisk (*) in the declaration. For
example, int* ptr; declares a pointer to an integer.

3. Initialization: Pointers should be initialized with a valid memory address before


use. Uninitialized pointers can lead to undefined behavior.

4. Assignment: You can assign the address of a variable to a pointer using the address-
of operator &. For example, ptr = &myVariable; assigns the address of myVariable
to ptr.

5. Dereferencing: To access the value stored at the memory location pointed to by a


pointer, you use the dereference operator *. For example, int x = *ptr; assigns the
value at the address stored in ptr to x.

6. Pointer Arithmetic: Pointers can be incremented and decremented, which allows


you to navigate through memory locations. For example, ptr++; increments the
pointer to point to the next memory location.

7. Pointer Types: Pointers have types that indicate the type of data they point to. For
instance, int* is a pointer to an integer, char* is a pointer to a character, and so on.

8. Null Pointers: C++ allows you to create null pointers (pointers that don’t point to
any valid memory location) using nullptr or NULL.

9. Dynamic Memory Allocation: Pointers are often used in dynamic memory allo-
cation with operators like new and delete. For instance, int* arr = new int[10];
dynamically allocates an array of 10 integers on the heap.

10. Pointers to Objects: You can have pointers to objects and classes. These pointers
allow you to access and manipulate objects indirectly.

Here’s a simple example of a pointer in C++:


8 L E C T URE 2: POINTERS

# include <iostream >

i n t main ( ) {
int x = 42; / / D e c l a r e an i n t e g e r v a r i a b l e
int * ptr ; / / D e c l a r e a p o i n t e r t o an i n t e g e r

p t r = &x ; / / Assign the address of x to ptr

/ / Access the value using the pointer


s t d : : c o u t << " V a l u e o f x : " << * p t r << s t d : : e n d l ;

return 0 ;
}

In this example, ptr is a pointer to an integer, and it’s assigned the address of the x
variable. By dereferencing ptr with *ptr, we can access the value stored in x
The next program demonstrates the usage of a pointer to a float variable, allowing
you to indirectly access and manipulate the float data.
# include <iostream >

i n t main ( ) {
f l o a t myFloat = 3 . 1 4 ; / / Declare a f l o a t variable
float * ptr ; / / Declare a pointer to a f l o a t

p t r = &myFloat ; / / A s s i g n t h e a d d r e s s o f myFloat t o p t r

/ / Access the value using the pointer


s t d : : c o u t << " V a l u e o f myFloat : " << * p t r << s t d : : e n d l ;

return 0 ;
}

In this example:

1. We declare a float variable called myFloat and initialize it with the value 3.14.

2. We declare a pointer to a float named ptr.

3. We assign the address of the myFloat variable to the ptr pointer using the address-
of operator &

4. Finally, we access the value of myFloat through the ptr pointer by dereferencing it
with *ptr and print it.

Allocating and Deallocating Pointers in C++


In C++, pointers can be allocated and deallocated dynamically using memory manage-
ment operators like new and delete. Dynamic memory allocation is especially useful
when you need to create and manage memory at runtime. Here’s how you allocate and
deallocate pointers in C++:
Allocating Memory:
Using new Operator: The new operator allocates memory on the heap and returns a
pointer to the allocated memory. You can use it to allocate memory for single objects or
arrays.
/ / A l l o c a t e memory f o r a s i n g l e i n t e g e r
i n t * s i n g l e I n t = new i n t ;

/ / A l l o c a t e memory f o r an a r r a y o f i n t e g e r s
i n t * i n t A r r a y = new i n t [ 1 0 ] ;

Remember to check if memory allocation was successful, as new can throw an ex-
ception (std::bad_alloc) if memory is not available.

Deallocating Memory
Using delete Operator: The delete operator frees the memory allocated with new. When
you’re done with the dynamically allocated memory, you should use delete to release it
to prevent memory leaks.
/ / D e a l l o c a t e memory f o r a s i n g l e i n t e g e r
delete s i n g l e I n t ;

/ / D e a l l o c a t e memory f o r an a r r a y o f i n t e g e r s
delete [ ] intArray ;

Not deallocating memory correctly can lead to memory leaks, where your program
consumes memory that it can’t release, potentially causing your program to run out of
memory.
Using delete vs. delete[]: It’s crucial to use delete[] for arrays allocated with new[]
and delete for single objects allocated with new. Mixing them can lead to undefined
behavior. Here’s an example illustrating dynamic memory allocation and deallocation:
# include <iostream >

i n t main ( ) {
/ / A l l o c a t e memory f o r a s i n g l e i n t e g e r
i n t * s i n g l e I n t = new i n t ;
* singleInt = 42;

/ / A l l o c a t e memory f o r an a r r a y o f i n t e g e r s
i n t * i n t A r r a y = new i n t [ 5 ] ;
f o r ( i n t i = 0 ; i < 5 ; ++ i ) {
intArray [ i ] = i * 10;
}

/ / D e a l l o c a t e memory f o r t h e s i n g l e i n t e g e r
delete s i n g l e I n t ;

/ / D e a l l o c a t e memory f o r t h e a r r a y o f i n t e g e r s
delete [ ] intArray ;

return 0 ;
}
10 L E C T URE 2: POINTERS

In this example, we allocate memory for a single integer and an array of integers
using new. After using the memory, we deallocate it using delete and delete[] to release
the allocated memory.
Proper memory allocation and deallocation are essential for efficient and reliable
C++ programming, as it helps prevent memory leaks and ensures that memory is used
efficiently.

C++ pointers and arrays


In C++, pointers and arrays have a close relationship. Arrays are essentially pointers to
the first element of the array, and pointers can be used to access elements of an array.
Understanding this relationship is essential for working effectively with arrays in C++.
Here are some key points about C++ pointers and arrays:
Array Name as a Pointer: In C++, the name of an array can be used as a pointer to
the first element of the array. For example, if you have an integer array named myArray,
myArray is equivalent to a pointer to the first integer in the array.
i n t myArray [ 5 ] = { 1 , 2 , 3 , 4 , 5 } ;
i n t * p t r = myArray ; / / p t r p o i n t s t o t h e f i r s t e l e m e n t o f myArray

Pointer Arithmetic: You can use pointer arithmetic to access elements of an array by
incrementing or decrementing a pointer. For example:
i n t * p t r = myArray ; / / P o i n t s t o t h e f i r s t e l e m e n t
int value1 = * ptr ; / / A c c e s s e s the f i r s t element ( value1 = 1 )

/ / Move t o t h e n e x t e l e m e n t
ptr ++;
int value2 = * ptr ; / / A c c e s s e s the second element ( value2 = 2)

Array Access Using Pointer Notation: You can also use pointer notation to access
array elements.
i n t * p t r = myArray ;
int value3 = ptr [ 2 ] ; / / A c c e s s e s the t h i r d element ( value3 = 3)

Pointer and Array Size: The size of the array affects pointer arithmetic. If you move a
pointer beyond the bounds of the array, it can lead to undefined behavior or segmenta-
tion faults.
Pointers and Dynamic Arrays: Pointers are commonly used with dynamic arrays
allocated on the heap. For example
i n t * dynamicArray = new i n t [ 1 0 ] ;
/ / A c c e s s and manipulate dynamicArray u s i n g p o i n t e r n o t a t i o n
d e l e t e [ ] dynamicArray ; / / D e a l l o c a t e t h e dynamic a r r a y

Passing Arrays to Functions: When you pass an array to a function, it’s actually
passing a pointer to the first element of the array. This allows functions to work with
arrays efficiently.
void p r o c e s s A r r a y ( i n t * a r r , i n t s i z e ) {
/ / Process the elements of the array using pointer notation
}

i n t main ( ) {
i n t myArray [ 5 ] = { 1 , 2 , 3 , 4 , 5 } ;
p r o c e s s A r r a y ( myArray , 5 ) ; / / P a s s myArray t o t h e f u n c t i o n
return 0 ;
}

Pointers and Multidimensional Arrays: Pointers can be used to work with multidi-
mensional arrays by creating pointers to arrays. This is particularly useful for dynamically
allocated multidimensional arrays.
i n t * * m a t r i x = new i n t * [ 3 ] ;
for ( i n t i = 0 ; i < 3 ; i ++) {
m a t r i x [ i ] = new i n t [ 3 ] ;
}
/ / A c c e s s matrix elements using p o i n t e r notation

Understanding the relationship between pointers and arrays is crucial for effective
memory management and data manipulation in C++. It allows you to work efficiently
with arrays, both static and dynamic, and pass arrays to functions without copying large
amounts of data.

Pointers and Structures


Pointers and structures in C++ can be used together to create more complex data struc-
tures and manage memory more efficiently. When you use pointers with structures,
you can access and manipulate data within structures dynamically. Here are some key
concepts and examples of using pointers with structures in C++:

Pointer to Structure
Pointer Declaration: You can declare a pointer to a structure by using the structure’s type
name followed by an asterisk (*). For example, if you have a structure named Person,
you can declare a pointer to it like this:
s t r u c t Person {
s t d : : s t r i n g name ;
i n t age ;
};

Person * p e r s o n P t r ;

Dynamic Allocation: Pointers to structures are often used with dynamic memory
allocation (e.g., new operator) to create instances of structures on the heap:
Person * p e r s o n P t r = new Person ; / / A l l o c a t e memory f o r a P e r s o n s t r u c t u r e

Accessing Structure Members: To access members of a structure through a pointer,


you can use the arrow operator (->). This operator combines dereferencing the pointer
and accessing the member:
p e r s o n P t r −>name = " John " ;
p e r s o n P t r −>age = 3 0 ;
12 L E C T URE 2: POINTERS

Arrays of Structures and Pointers


Array of Structures: You can create arrays of structures and use pointers to iterate through
them:
Person people [ 3 ] ; / / A r r a y o f P e r s o n s t r u c t u r e s
Person * p e r s o n P t r = people ; / / P o i n t e r t o t h e f i r s t e l e m e n t o f t h e a r r a y

p e r s o n P t r −>name = " A l i c e " ; / / A c c e s s f i r s t e l e m e n t


( p e r s o n P t r + 1) − >name = " Bob " ; / / A c c e s s s e c o n d e l e m e n t u s i n g p o i n t e r a r i t h m e t i c

Dynamic Allocation of Arrays: You can dynamically allocate arrays of structures using
pointers:
Person * p e o p l e A r r a y = new Person [ 5 ] ; / / A l l o c a t e an a r r a y o f 5 P e r s o n s t r u c t u r e s

Accessing Array Elements: You can access individual elements of an array of struc-
tures through pointers:
p e o p l e A r r a y [ 2 ] . name = " C h a r l i e " ;
( p e o p l e A r r a y + 3)−>name = " David " ;

Structures Containing Pointers


Pointers Within Structures: Structures can contain pointers as members. This is often
used when you want to store dynamically allocated data within a structure:
stru ct Student {
s t d : : s t r i n g name ;
i n t age ;
i n t * g r a d e s ; / / P o i n t e r t o an a r r a y o f g r a d e s
};

Student student1 ;
s t u d e n t 1 . g r a d e s = new i n t [ 5 ] ; / / A l l o c a t e an a r r a y f o r g r a d e s

Memory Deallocation: When structures contain pointers to dynamically allocated


memory, remember to deallocate that memory when you’re done to prevent memory
leaks:
delete [ ] student1 . grades ; / / Deallocate the dynamically a l l o c a t e d array

Using pointers with structures allows you to create more flexible and memory-
efficient data structures, particularly when working with dynamic data or arrays of
structures. It’s important to manage memory properly, especially when using pointers
within structures, to avoid memory leaks and undefined behavior.

Smart Pointers
Smart pointers are a C++ feature that helps manage dynamic memory allocation and
deallocation more safely and conveniently. They are objects that act like pointers but
provide automatic memory management. Smart pointers handle the allocation and
deallocation of memory, which can help prevent common issues like memory leaks and
dangling pointers. In C++, there are three primary types of smart pointers:
std::unique_ptr
std::unique_ptr: This smart pointer type represents ownership of a dynamically allocated
object. It ensures that there is only one unique pointer pointing to the object. When a
std::unique_ptr goes out of scope, it automatically deallocates the memory associated
with the object it owns. Example:
s t d : : u n i q u e _ p t r < i n t > u n i q u e P t r = s t d : : make_unique < i n t > ( 4 2 ) ;

std::shared_ptr
std::shared_ptr: This smart pointer type allows multiple shared pointers to share owner-
ship of the same dynamically allocated object. The object is only deallocated when the
last shared pointer that owns it is destroyed. Example:
s t d : : s h a r e d _ p t r < i n t > s h a r e d P t r 1 = s t d : : make_shared < i n t > ( 4 2 ) ;
s t d : : s h a r e d _ p t r < i n t > s h a r e d P t r 2 = s h a r e d P t r 1 ; / / Both s h a r e d P t r 1 and s h a r e d P t r 2 s h a r e o

std::weak_ptr
std::weak_ptr: This smart pointer type is used in conjunction with std::shared_ptr to
break potential reference cycles. A std::weak_ptr does not increase the reference count
of the object it points to, and it allows you to check if the object is still alive. Example:
s t d : : s h a r e d _ p t r < i n t > s h a r e d P t r = s t d : : make_shared < i n t > ( 4 2 ) ;
s t d : : weak_ptr < i n t > weakPtr = s h a r e d P t r ;

Using smart pointers, particularly std::unique_ptr and std::shared_ptr, is recom-


mended over raw pointers when dealing with dynamic memory allocation because they
provide several benefits:
Automatic deallocation: They automatically release memory when it’s no longer
needed, preventing memory leaks. Improved safety: They reduce the chances of derefer-
encing dangling pointers (pointers to deallocated memory). Improved code readability:
They make ownership and resource management more explicit in the code.
Here’s a simple example of std::unique_ptr:
# include <iostream >
# i n c l u d e <memory>

i n t main ( ) {
s t d : : u n i q u e _ p t r < i n t > u n i q u e P t r = s t d : : make_unique < i n t > ( 4 2 ) ;

s t d : : c o u t << " V a l u e : " << * u n i q u e P t r << s t d : : e n d l ;

/ / Memory i s a u t o m a t i c a l l y d e a l l o c a t e d when u n i q u e P t r g o e s out o f s c o p e

return 0 ;
}

Smart pointers are an essential part of modern C++ and contribute to safer and more
robust code by automating many aspects of memory management.
14 L E C T URE 2: POINTERS

More examples
std::shared_ptr with Basic Data Types:
# include <iostream >
# i n c l u d e <memory>

i n t main ( ) {
/ / C r e a t e a s h a r e d _ p t r t o an i n t e g e r
s t d : : s h a r e d _ p t r < i n t > s h a r e d I n t = s t d : : make_shared < i n t > ( 4 2 ) ;

/ / C r e a t e a n o t h e r s h a r e d _ p t r t h a t s h a r e s owner ship
std : : shared_ptr < int > sharedInt2 = sharedInt ;

s t d : : c o u t << " V a l u e o f s h a r e d I n t : " << * s h a r e d I n t << s t d : : e n d l ;


s t d : : c o u t << " V a l u e o f s h a r e d I n t 2 : " << * s h a r e d I n t 2 << s t d : : e n d l ;

/ / The i n t e g e r i s a u t o m a t i c a l l y d e a l l o c a t e d when t h e l a s t s h a r e d _ p t r owning i t i s d

return 0 ;
}

In this example, std::shared_ptr is used to manage an integer value.


std::unique_ptr with Arrays:
# include <iostream >
# i n c l u d e <memory>

i n t main ( ) {
/ / C r e a t e a u n i q u e _ p t r f o r an i n t e g e r a r r a y
s t d : : u n i q u e _ p t r < i n t [ ] > u n i q u e I n t A r r a y ( new i n t [ 5 ] ) ;

f o r ( i n t i = 0 ; i < 5 ; ++ i ) {
uniqueIntArray [ i ] = i * 1 0 ;
}

f o r ( i n t i = 0 ; i < 5 ; ++ i ) {
s t d : : c o u t << " V a l u e a t i n d e x " << i << " : " << u n i q u e I n t A r r a y [ i ] << s t d : : e n d l ;
}

/ / Memory f o r t h e i n t e g e r a r r a y i s a u t o m a t i c a l l y d e a l l o c a t e d when u n i q u e I n t A r r a y g o

return 0 ;
}

Here, std::unique_ptr is used to manage an array of integers.


std::weak_ptr with Basic Data Types:
# include <iostream >
# i n c l u d e <memory>

i n t main ( ) {
s t d : : s h a r e d _ p t r < i n t > s h a r e d I n t = s t d : : make_shared < i n t > ( 4 2 ) ;
s t d : : weak_ptr < i n t > weakInt = s h a r e d I n t ;

/ / Check i f t h e s h a r e d _ p t r i s s t i l l a l i v e
i f ( auto l o c k e d I n t = weakInt . l o c k ( ) ) {
s t d : : c o u t << "Weak p o i n t e r i s a l i v e . " << s t d : : e n d l ;
s t d : : c o u t << " V a l u e : " << * l o c k e d I n t << s t d : : e n d l ;
} else {
s t d : : c o u t << "Weak p o i n t e r i s e x p i r e d . " << s t d : : e n d l ;
}

/ / When s h a r e d I n t g o e s out o f s c o p e , t h e i n t e g e r i s d e a l l o c a t e d

return 0 ;
}
In this example, a std::weak_ptr is used to check if a std::shared_ptr is still alive and
access the integer it points to.

Pointers to functions in C++


Pointers to functions in C++ are pointers that can hold the memory address of a function.
They allow you to dynamically choose and call a function during program execution,
making it possible to implement functionality like callbacks and dynamic function
dispatch. Here’s how to work with pointers to functions in C++:

Declaring a Pointer to a Function:


To declare a pointer to a function, you need to specify the function’s signature, including
its return type and parameter types. For example, consider a function that takes two
integers and returns an integer:
i n t Add ( i n t a , i n t b ) ;
To declare a pointer to this function, you can use the following syntax:
int ( * f u n c t i o n P t r ) ( int , int ) ;
Here, functionPtr is a pointer to a function that takes two integers and returns an
integer.

Initializing a Pointer to a Function:


To initialize a pointer to a function, you can assign it the address of a compatible function.
For example:
f u n c t i o n P t r = Add ;
Now, functionPtr points to the Add function.

Calling a Function Through a Pointer:


You can call the function using the pointer just like you would call the function directly,
using the (*functionPtr) notation:
int result = (* functionPtr ) ( 2 , 3 ) ; / / C a l l s Add ( 2 , 3 ) t h r o u g h t h e p o i n t e r
Alternatively, you can use the pointer as if it were the function itself, without the
(*ptr) notation:
int result = functionPtr (2 , 3 ) ; / / A l s o c a l l s Add ( 2 , 3 ) t h r o u g h t h e p o i n t e r
16 L E C T URE 2: POINTERS

Passing Pointers to Functions as Arguments:


Pointers to functions can be passed as arguments to other functions. This is often used
to implement callback mechanisms, where one function takes a pointer to a callback
function as an argument.
void P r o c e s s ( i n t a , i n t b , i n t ( * o p e r a t i o n ) ( i n t , i n t ) ) {
int r e s u l t = operation ( a , b ) ;
/ / Do s o m e t h i n g with t h e r e s u l t
}

i n t Add ( i n t a , i n t b ) {
return a + b ;
}

int Subtract ( int a , int b) {


return a − b ;
}

i n t main ( ) {
P r o c e s s ( 5 , 3 , Add ) ; / / C a l l s Add ( 5 , 3 ) t h r o u g h P r o c e s s
Process (5 , 3 , Subtract ) ; / / C a l l s S u b t r a c t ( 5 , 3) through P r o c e s s
return 0 ;
}

Lab 2: C++ Pointers Practice


Objective: Practice working with pointers in C++ to understand their usage and common
scenarios.

1. Problem 1: Basic Pointer Manipulation Write a C++ program that does the following:

• Declare an integer variable x and initialize it with a value.


• Declare a pointer to an integer and assign the address of x to it.
• Print the value of x and the value pointed to by the pointer.
• Modify the value pointed to by the pointer and print the updated value.

2. Problem 2: Dynamic Memory Allocation Write a C++ program that:

• Dynamically allocates memory for an array of 5 integers.


• Initializes each element of the array with user input.
• Calculates the sum, average, and maximum of the elements and prints the
results.
• Deallocates the dynamically allocated memory.

3. Problem 3: Pointer Arithmetic Write a C++ program that:

• Declares an integer array of 10 elements and initializes it with values.


• Uses a pointer to iterate through the array and print the elements in reverse
order (starting from the last element).
4. Problem 4: Pointer to Function

• Write a C++ program that defines a function square which takes an integer
as a parameter and returns the square of that integer. Then, use a pointer to
this function to calculate the squares of several integers and print the results.

5. Problem 5: Double Pointer (Pointer to Pointer)


Write a C++ program that uses a double pointer to:

• Dynamically create a 2D integer array (matrix).


• Prompt the user for the number of rows and columns.
• Allocate memory for the matrix.
• Fill the matrix with user input values.
• Print the matrix.

6. Problem 6: Smart Pointers


Write a C++ program that demonstrates the use of smart pointers:

• Declare a std::unique_ptr to a dynamically allocated integer.


• Prompt the user for an integer value and store it in the allocated memory.
• Print the value from the std::unique_ptr.
• Observe how the memory is automatically deallocated when the std::unique_ptr
goes out of scope.

7. Problem 8: Pointer Casting Write a C++ program that demonstrates pointer casting:

• Declare an integer pointer and a floating-point pointer.


• Initialize the integer pointer with the address of an integer variable and the
floating-point pointer with the address of a float variable.
• Cast both pointers to void* and then back to their original types.
• Print the values to observe any changes.

8. Problem 9: Array of Pointers Write a C++ program that:

• Declares an array of five character pointers.


• Initializes each pointer with a string (e.g., names of cities).
• Prints the strings using pointer notation.

9. Problem 10: Memory Leak Detection (Optional) Incorporate a memory leak de-
tection tool like Valgrind to check your program for memory leaks in any of the
previous problems. Make any necessary adjustments to your code to ensure proper
memory deallocation.
18 L E C T URE 2: POINTERS

Practice Questions
1. How do you declare a pointer variable for an integer?

2. Explain the difference between a pointer and a regular variable.

3. How do you initialize a pointer with the address of a variable?

4. What is a null pointer, and how is it initialized?

5. Explain the relationship between arrays and pointers in C++.

6. What are smart pointers, and why are they used?

7. Explain the difference between std::shared_ptr, std::unique_ptr, and std::weak_ptr.

8. When should you use a std::unique_ptr instead of a std::shared_ptr?

9. How can you handle errors related to dynamic memory allocation with pointers?

10. Explain common pitfalls and best practices to avoid errors when working with
pointers.

11. Write a C++ program that:


Declares an array of five character pointers. Initializes each pointer with a string
(e.g., names of cities). Prints the strings using pointer notation.

12. Write a C++ program that uses a double pointer to dynamically create a 2D integer
array (matrix). Prompt the user for the number of rows and columns, allocate
memory for the matrix, fill it with values, and then print the matrix.

13. Write a C++ program that defines a function square which takes an integer as
a parameter and returns the square of that integer. Then, use a pointer to this
function to calculate the squares of several integers and print the results.

14. Write a C++ program that does the following: Declares an integer variable x and
initializes it with a value. Declares a pointer to an integer and assigns the address
of x to it. Prints the value of x and the value pointed to by the pointer.
Lecture 3: C++ Classes

Object Oriented Programming


Object-Oriented Programming (OOP) is a programming paradigm that is centered around
the concept of "objects." It’s a way of designing software by organizing code into objects
that represent real-world entities and modeling their interactions. OOP is based on
several key principles:

• Objects: Objects are instances of classes, and they encapsulate both data (attributes
or properties) and behavior (methods or functions). These objects represent real-
world entities and can interact with each other.

• Classes: Classes are blueprints or templates for creating objects. They define the
structure and behavior that objects of the class will have. A class specifies the
attributes and methods that its objects will possess.

• Encapsulation: Encapsulation is the concept of bundling data (attributes) and


methods (functions) that operate on that data into a single unit, i.e., an object.
It hides the internal details of how an object works, exposing only the necessary
interfaces.

• Inheritance: Inheritance allows you to create a new class (a derived or child class)
based on an existing class (a base or parent class). The derived class inherits
the attributes and methods of the base class, enabling code reuse and creating a
hierarchical structure of classes.

• Polymorphism: Polymorphism enables objects of different classes to be treated


as objects of a common base class. It allows you to write code that can work with
objects of various types, making the code more flexible and adaptable.

• Abstraction: Abstraction is the process of simplifying complex systems by breaking


them down into smaller, more manageable parts. In OOP, it involves defining
abstract classes or interfaces that define a set of methods without providing their
implementation.

• OOP promotes code reusability, modularity, and a more intuitive way of thinking
about and structuring software, making it a widely used and powerful programming
paradigm. It is commonly employed in languages like Java, C++, Python, and many
others.

Object-Oriented Programming (OOP) is guided by several key principles that help


developers design and structure their software in a modular and maintainable way. The
20 L E C T URE 3: C++ CL ASSES

five fundamental principles of OOP are often referred to as SOLID, an acronym formed
from the first letter of each principle:

• Single Responsibility Principle (SRP): This principle states that a class should
have only one reason to change, meaning it should have a single responsibility or
purpose. A class should encapsulate a single piece of functionality, and if it has
multiple responsibilities, it may become difficult to maintain and modify.

• Open/Closed Principle (OCP): The Open/Closed Principle emphasizes that soft-


ware entities (e.g., classes, modules, functions) should be open for extension but
closed for modification. Instead of altering existing code to add new features, you
should extend or inherit from existing classes and build upon their functionality
through new code.

• Liskov Substitution Principle (LSP): he Liskov Substitution Principle states that


objects of a derived class should be able to replace objects of the base class with-
out affecting the correctness of the program. his principle ensures that derived
classes maintain the same contract (i.e., behavior) as their base classes, allowing
for polymorphism and substitutability.

• Interface Segregation Principle (ISP): The Interface Segregation Principle suggests


that clients should not be forced to depend on interfaces they do not use. t encour-
ages the creation of smaller, more specific interfaces rather than large, monolithic
ones, so that classes can implement only the methods they need.

• Dependency Inversion Principle (DIP): The Dependency Inversion Principle pro-


motes the idea that high-level modules (e.g., classes, components) should not
depend on low-level modules. Instead, both should depend on abstractions (inter-
faces or abstract classes). It encourages the use of dependency injection to achieve
loose coupling and make software components more flexible and easier to test.

These SOLID principles guide the design and development of object-oriented sys-
tems, helping developers create software that is more modular, extensible, and main-
tainable. By adhering to these principles, developers can reduce the complexity of their
code, improve its readability, and make it more resilient to changes and updates.

What are classes


C++ is an object-oriented programming (OOP) language, and classes are a fundamental
concept in C++ that enable you to implement object-oriented design. Classes provide a
blueprint for creating objects, which are instances of a class. Here’s an introduction to
C++ classes:

1. Class Definition: A class in C++ is a user-defined data type that defines a template
for creating objects. It defines the properties (data members or attributes) and
behaviors (member functions or methods) that objects of the class will have.
2. Encapsulation One of the key principles of OOP is encapsulation, and classes
facilitate this. Encapsulation means bundling data and methods that operate on
that data into a single unit (i.e., an object). In C++, you can specify the access level
of data members (public, private, protected) to control who can access them.

3. Constructors Constructors are special member functions used to initialize objects


of a class. They have the same name as the class and are automatically called when
an object is created. Constructors allow you to set initial values for the object’s
data members.

4. Destructors Destructors are used to clean up resources or perform necessary ac-


tions when an object is destroyed (e.g., when it goes out of scope or is explicitly
deleted). They have the same name as the class preceded by a tilde ( ).

5. Member Functions: Member functions are functions defined within a class that
operate on the class’s data members. These functions can be public, private, or
protected. Public member functions can be called from outside the class and are
used to interact with objects.

6. Data Members: Data members are variables declared within a class that represent
the object’s state. They define what data an object of the class can store.

7. Access Specifiers: C++ provides three access specifiers: public, private, and pro-
tected. These specifiers determine the visibility and accessibility of class members.

• public: Members are accessible from any part of the program.


• private: Members are accessible only within the class.
• protected: Members are accessible within the class and its subclasses.

Here is an example about classes:


/ / Class definition
c l a s s Car {
public :
/ / Constructor
Car ( i n t year , s t r i n g make ) {
t h i s −>y e a r = y e a r ;
t h i s −>make = make ;
}

/ / Member f u n c t i o n
void s t a r t E n g i n e ( ) {
c o u t << " Engine s t a r t e d ! " << e n d l ;
}

private :
/ / Data members
int year ;
s t r i n g make ;
};
22 L E C T URE 3: C++ CL ASSES

In this example, we define a Car class with a constructor, a member function startEngine,
and private data members year and make. You can create Car objects, call member func-
tions, and access data members to work with instances of the class.
Objects are instances of a class. To create an object, you declare a variable of the
class type. For example:
Car myCar ; / / C r e a t e s an o b j e c t o f t h e c l a s s Car

You can access the data members and member functions of an object using the dot
(.) operator. For example:
Car myCar ; / / C r e a t e s an o b j e c t o f t h e c l a s s Car
myCar . s t a r t E n g i n e ( ) ; / / C a l l s a member f u n c t i o n

Here is a complete C++ class and how the class is called from inside main():
# include <iostream >
# include < s t r i n g >

using namespace s t d ;

/ / Class definition
c l a s s Car {
public :
/ / Constructor
Car ( i n t year , s t r i n g make ) {
t h i s −>y e a r = y e a r ;
t h i s −>make = make ;
engineRunning = f a l s e ;
}

/ / Member f u n c t i o n t o s t a r t t h e e n g i n e
void s t a r t E n g i n e ( ) {
engineRunning = t r u e ;
c o u t << " Engine s t a r t e d ! " << e n d l ;
}

/ / Member f u n c t i o n t o s t o p t h e e n g i n e
void s t o p E n g i n e ( ) {
engineRunning = f a l s e ;
c o u t << " Engine stopped . " << e n d l ;
}

/ / Member f u n c t i o n t o d i s p l a y c a r i n f o
void d i s p l a y I n f o ( ) {
c o u t << " Y e a r : " << y e a r << e n d l ;
c o u t << "Make : " << make << e n d l ;
c o u t << " Engine s t a t u s : " << ( engineRunning ? " Running " : " Stopped " ) << e n d l ;
}

private :
/ / Data members
int year ;
s t r i n g make ;
bool engineRunning ;
};
i n t main ( ) {
/ / C r e a t e an o b j e c t o f t h e Car c l a s s
Car myCar ( 2 0 2 3 , " Toyota " ) ;

/ / Display car info


c o u t << " Car I n f o : " << e n d l ;
myCar . d i s p l a y I n f o ( ) ;

/ / S t a r t the engine
myCar . s t a r t E n g i n e ( ) ;
c o u t << " Car I n f o a f t e r s t a r t i n g t h e e n g i n e : " << e n d l ;
myCar . d i s p l a y I n f o ( ) ;

/ / Stop the engine


myCar . s t o p E n g i n e ( ) ;
c o u t << " Car I n f o a f t e r s t o p p i n g t h e e n g i n e : " << e n d l ;
myCar . d i s p l a y I n f o ( ) ;

return 0 ;
}

In this example, we define a Car class with a constructor, member functions (startEngine,
stopEngine, and displayInfo), and private data members (year, make, and engineRun-
ning). In the main() function, we create an instance of the Car class, call its member
functions, and display information about the car object.
When you run this program, it will create a Car object, manipulate its state (start and
stop the engine), and display information about the car’s year, make, and engine status.

Relationship between Objects and classes


In object-oriented programming (OOP), objects and classes are closely related and have
a hierarchical relationship. Here’s how they are connected:

• Classes Define Objects:

– Classes act as blueprints or templates for creating objects. A class defines the
structure (data members or attributes) and behavior (member functions or
methods) that objects of that class will have.
– Objects are instances of classes. When you create an object based on a class,
you’re essentially creating a specific instance that adheres to the blueprint
defined by that class.

• Objects Are Instances of Classes:

– Objects are concrete instances of a class. They represent real-world entities


or concepts based on the design defined by the class.
– Each object created from a class has its own set of data members and can
perform actions defined by the class’s methods.

• Multiple Objects from a Single Class:


24 L E C T URE 3: C++ CL ASSES

– Multiple Objects from a Single Class:


– You can create multiple objects from the same class. Each object will have its
own unique data values and can independently execute the methods defined
by the class.
• Polymorphism
– Polymorphism is a concept in OOP where objects of different classes can be
treated as objects of a common base class. This allows you to write code that
can work with objects of various types, making it more flexible and adaptable.
– Polymorphism enables you to create functions or methods that can accept
objects of different classes that share a common base class.

Constructor: revisited
In C++, constructors are special member functions used for initializing objects when they
are created. Constructors have the same name as the class and are called automatically
when an object is instantiated. There are several types of constructors in C++, each
serving different purposes. Here are the main types of constructors with examples:

Default Constructor:
A default constructor is called when an object is created without any arguments. It
initializes the object’s data members to default values.
c l a s s MyClass {
public :
MyClass ( ) {
/ / Default constructor
value = 0 ;
message = " H e l l o , World ! " ;
}

void d i s p l a y ( ) {
c o u t << message << " V a l u e : " << v a l u e << e n d l ;
}

private :
int value ;
s t r i n g message ;
};
Example usage:
MyClass o b j ; / / C a l l s t h e d e f a u l t c o n s t r u c t o r
o b j . d i s p l a y ( ) ; / / Output : H e l l o , World ! Value : 0

Parameterized Constructor:
A parameterized constructor takes one or more arguments and initializes the object’s
data members with the provided values.
c l a s s Student {
public :
Student ( s t r i n g n , int r ) {
/ / Parameterized constructor
name = n ;
rollNumber = r ;
}

void d i s p l a y ( ) {
c o u t << "Name : " << name << " R o l l Number : " << rollNumber << e n d l ;
}

private :
s t r i n g name ;
i n t rollNumber ;
};

Example usage:
Student s 1 ( " A l i c e " , 1 0 1 ) ; / / C a l l s the parameterized c o n s t r u c t o r
s 1 . d i s p l a y ( ) ; / / Output : Name : A l i c e R o l l Number : 1 0 1

Copy Constructor:
A copy constructor creates a new object as a copy of an existing object of the same class.
It is used for deep copying data members.
c l a s s Person {
public :
Person ( s t r i n g n , i n t a ) {
name = n ;
age = a ;
}

/ / Copy c o n s t r u c t o r
Person ( const Person& o t h e r ) {
name = o t h e r . name ;
age = o t h e r . age ;
}

void d i s p l a y ( ) {
c o u t << "Name : " << name << " Age : " << age << e n d l ;
}

private :
s t r i n g name ;
i n t age ;
};

Example usage:
Person p1 ( " Bob " , 3 0 ) ;
Person p2 = p1 ; / / C a l l s t h e copy c o n s t r u c t o r t o c r e a t e p2 a s a copy o f p1
p2 . d i s p l a y ( ) ; / / Output : Name : Bob Age : 30
26 L E C T URE 3: C++ CL ASSES

Initializer List Constructor:


An initializer list constructor allows you to initialize data members directly through an
initializer list in the constructor’s parameter list.
c l a s s Book {
public :
Book ( s t r i n g t , s t r i n g a ) : t i t l e ( t ) , a u t h o r ( a ) {
/ / I n i t i a l i z e r l i s t constructor
}

void d i s p l a y ( ) {
c o u t << " T i t l e : " << t i t l e << " Author : " << a u t h o r << e n d l ;
}

private :
string t i t l e ;
s t r i n g author ;
};

Example usage:

Move Constructor
A move constructor is a special constructor in C++ that allows for the efficient transfer
of ownership of resources from one object to another. It is used to move the contents
of one object (often a temporary or soon-to-be-destroyed object) into another object,
typically without copying data. This is particularly useful for optimizing memory and
performance when working with expensive-to-copy resources like dynamically allocated
memory or large data structures.
The move constructor has the following signature:
c l a s s MyClass {
public :
MyClass ( MyClass&& o t h e r ) noe xc ep t {
/ / Move c o n s t r u c t o r
/ / T r a n s f e r owners hip o f r e s o u r c e s from ’ o t h e r ’ t o ’ t h i s ’
/ / U s u a l l y i n v o l v e s s h a l l o w c o p y i n g and r e s e t t i n g ’ o t h e r ’
}
};

Here’s an example illustrating the use of a move constructor with a simple class:
# include <iostream >
# include < s t r i n g >

c l a s s M yS t r i n g {
public :
M y S t r i n g ( const char * s ) : d a t a ( n u l l p t r ) {
size = strlen ( s ) ;
d a t a = new char [ s i z e + 1 ] ;
s t r c p y ( data , s ) ;
}

/ / Move c o n s t r u c t o r
M y S t r i n g ( M y S t r i n g&& o t h e r ) no exc ep t : d a t a ( n u l l p t r ) , s i z e ( 0 ) {
s t d : : swap ( data , o t h e r . d a t a ) ; / / E f f i c i e n t l y t r a n s f e r owners hip
s t d : : swap ( s i z e , o t h e r . s i z e ) ;
}

~ MyString ( ) {
delete [ ] data ;
}

void d i s p l a y ( ) {
s t d : : c o u t << " S t r i n g : " << d a t a << " Length : " << s i z e << s t d : : e n d l ;
}

private :
char * d a t a ;
size_t size ;
};

i n t main ( ) {
M y S t r i n g s t r 1 ( " H e l l o , World ! " ) ;

/ / Use t h e move c o n s t r u c t o r t o e f f i c i e n t l y t r a n s f e r owne rship


M y S t r i n g s t r 2 ( s t d : : move ( s t r 1 ) ) ;

s t d : : c o u t << " A f t e r moving : " << s t d : : e n d l ;


s t r 1 . d i s p l a y ( ) ; / / Output : S t r i n g : L e n g t h : 0
s t r 2 . d i s p l a y ( ) ; / / Output : S t r i n g : H e l l o , World ! L e n g t h : 1 3

return 0 ;
}
In this example, we define a MyString class with a move constructor. When str2 is
constructed using std::move(str1), the move constructor efficiently transfers ownership
of the data and size resources from str1 to str2, leaving str1 in a valid but unspecified
state.
Move constructors are particularly valuable when working with large data structures,
managing memory, and optimizing performance in C++ programs. They play a crucial
role in enabling the efficient use of move semantics and avoiding unnecessary data
copying.

Destructor: revisited
In C++, a destructor is a special member function of a class that is responsible for
cleaning up resources used by objects of that class when they go out of scope or are
explicitly destroyed. Destructors have the same name as the class, preceded by a tilde
( ).
Here’s more information about C++ destructors:
1. Purpose of Destructors:
• The primary purpose of a destructor is to release any resources held by an
object, such as dynamic memory allocations, file handles, or other external
resources.
28 L E C T URE 3: C++ CL ASSES

• Destructors are called automatically when an object goes out of scope or


is explicitly destroyed using the delete operator (for dynamically allocated
objects) or when it goes out of scope.

2. Destructor Syntax:

• The syntax of a destructor is as follows:


c l a s s MyClass {
public :
/ / Constructor ( s ) here
MyClass ( ) { / * C o n s t r u c t o r c o d e * / }
// ...

/ / Destructor
~ MyClass ( ) { / * D e s t r u c t o r c o d e * / }
};

• Destructors do not take any parameters, and their name is the class name
preceded by a tilde ( ).

3. Resource Cleanup:

• Inside the destructor, you should release any resources that the object ac-
quired during its lifetime.
• Common tasks in a destructor include deleting dynamically allocated memory
(e.g., with delete or delete[]), closing files, releasing network connections, or
freeing any other acquired resources.

4. Automatic Invocation:

• Destructors are called automatically when an object goes out of scope or is


destroyed. For locally defined objects, this happens when they leave their
scope. For dynamically allocated objects, you must use the delete operator to
trigger the destructor.
• Destructors are called in the reverse order of object creation.

5. Default Destructor:

• If you don’t provide a destructor in your class, C++ will generate a default
destructor that does nothing. This is usually sufficient for classes that do not
manage any resources.

6. Destructor in Inheritance:

• When working with inheritance, derived classes can have their own destruc-
tors, which are called after the base class destructor. Make sure to call the
base class destructor explicitly if necessary.

Here’s an example of a class with a destructor:


# include <iostream >

c l a s s ResourceHolder {
public :
ResourceHolder ( ) {
/ / Constructor acquires a resource
r e s o u r c e = new i n t [ 1 0 0 ] ;
s t d : : c o u t << " Resource a c q u i r e d . " << s t d : : e n d l ;
}

~ ResourceHolder ( ) {
/ / Destructor r e l e a s e s the resource
delete [ ] resource ;
s t d : : c o u t << " Resource r e l e a s e d . " << s t d : : e n d l ;
}

private :
int * resource ;
};

i n t main ( ) {
{
ResourceHolder holder ; / / O b j e c t c r e a t e d within a scope
} / / D e s t r u c t o r c a l l e d when ’ h o l d e r ’ g o e s out o f s c o p e

// ...
return 0 ; / / Program ends , and ’ h o l d e r ’ d e s t r u c t o r i s c a l l e d a g a i n
}

In this example, the ResourceHolder class acquires and releases a dynamically allo-
cated resource in its constructor and destructor, respectively. When the holder object
goes out of scope, its destructor is automatically called, releasing the resource. The
destructor is called a second time when the program exits.

Classes and Pointers


Pointers and classes are closely related in C++. Pointers provide a way to work with
objects of a class dynamically, and they are especially useful when dealing with objects
that are created dynamically on the heap or when you want to pass objects as arguments
to functions efficiently. Here’s how pointers and classes interact in C++:

Pointer to Objects:
You can declare pointers to objects of a class just like you declare pointers to other data
types. For example:
MyClass * my Ob je ctP tr ; / / P o i n t e r t o an o b j e c t o f c l a s s MyClass

Once you have a pointer, you can use it to dynamically allocate objects on the heap:
my Ob je ctP tr = new MyClass ; / / D y n a m i c a l l y a l l o c a t e an o b j e c t on t h e heap

You can then access the object’s members (data members and member functions)
through the pointer:
30 L E C T URE 3: C++ CL ASSES

myObjectPtr −>memberFunction ( ) ; / / C a l l a member f u n c t i o n u s i n g t h e p o i n t e r


myObjectPtr −>dataMember = 4 2 ; / / A c c e s s a data member u s i n g t h e p o i n t e r

Passing Objects by Pointer:


Pointers are often used to pass objects as function arguments, especially when you want
to avoid the overhead of copying the object. By passing a pointer to the object, you can
modify the original object inside the function.
void m o d i f y O b j e c t ( MyClass * o b j P t r ) {
o b j P t r −>dataMember = 9 9 ;
}

Example usage:
MyClass myObj ;
m o d i f y O b j e c t (&myObj ) ; / / P a s s a p o i n t e r t o myObj

Dynamically Allocated Objects:


Pointers are essential for working with objects that are dynamically allocated on the
heap using new. It’s your responsibility to delete such objects to avoid memory leaks
when they are no longer needed.
MyClass * dynamicObj = new MyClass ;
// ...
d e l e t e dynamicObj ; / / D e a l l o c a t e t h e o b j e c t t o r e l e a s e memory

Pointer to Class Members:


C++ allows you to create pointers to members of a class, including data members and
member functions. These pointers can be useful for various advanced use cases, such
as implementing callback mechanisms.
i n t MyClass : : * dataMemberPtr = &MyClass : : dataMember ; / / P o i n t e r t o a data member
void ( MyClass : : * f u n c P t r ) ( ) = &MyClass : : memberFunction ; / / P o i n t e r t o a member f u n c t i o n

Example usage:
MyClass myObj ;
myObj . * dataMemberPtr = 4 2 ; / / A c c e s s data member t h r o u g h t h e p o i n t e r
( myObj . * f u n c P t r ) ( ) ; / / C a l l member f u n c t i o n t h r o u g h t h e p o i n t e r

Structures and Classes


In C++, both structures and classes are used to define user-defined data types that can
group together data members and member functions into a single unit. However, there
are some key differences between structures and classes. Here’s an overview of both:
Structures:
• Default Member Accessibility: In a C++ structure, all data members have public
accessibility by default. This means that you can access the data members directly
without any restrictions.
struct Point {
i n t x ; / / P u b l i c by d e f a u l t
i n t y ; / / P u b l i c by d e f a u l t
};

• Usage for Data Containers: Structures are often used when you need a simple
data container to group related data elements together. They don’t typically have
member functions but focus on storing and organizing data.
Point p ;
p. x = 10;
p. y = 20;

• No Member Functions: While C++ allows you to add member functions to struc-
tures, it’s not a common practice. By convention, structures are primarily used for
holding data.
struct Rectangle {
i n t width ;
int height ;
int area ( ) {
return width * h e i g h t ; / / Member f u n c t i o n t o c a l c u l a t e a r e a
}
};

Classes:
• Default Member Accessibility: In a C++ class, all data members have private acces-
sibility by default. This means that data members are not directly accessible from
outside the class. You need member functions (getters and setters) to access or
modify them.
c l a s s Person {
private :
s t d : : s t r i n g name ; / / P r i v a t e by d e f a u l t
i n t age ; / / P r i v a t e by d e f a u l t
public :
/ / Member f u n c t i o n s t o a c c e s s and modify p r i v a t e members
void setName ( const s t d : : s t r i n g & n ) { name = n ; }
void s e t A g e ( i n t a ) { age = a ; }
};

• Usage for Object-Oriented Programming: Classes are the foundation of object-


oriented programming (OOP). They encapsulate data members and member func-
tions, allowing you to define behavior (methods) associated with the data (at-
tributes).
32 L E C T URE 3: C++ CL ASSES

Person p ;
p . setName ( " A l i c e " ) ;
p . setAge ( 3 0 ) ;

• Encapsulation: Classes support encapsulation, a key OOP concept. This means you
can control access to data members, hiding implementation details and providing
a public interface for interacting with objects.
Person p ;
p . setName ( " A l i c e " ) ; / / A c c e s s t h r o u g h a p u b l i c member f u n c t i o n

In summary, structures are primarily used for grouping data members together, and
their members are typically public by default. Classes, on the other hand, are used for
defining complex data types with private data members and member functions that
provide an interface for interacting with the data. Classes are central to object-oriented
programming and encapsulation, allowing you to model real-world entities with behavior
and data.

Static Members
Static Member Variables
In C++, a static member variable is a variable that is associated with a class rather
than with instances (objects) of the class. It is shared among all instances of the class
and can be accessed without creating an object of the class. Here are some important
characteristics and usage guidelines for static member variables:

1. Declaration and Initialization: Static member variables are declared inside a class
like any other class member but with the static keyword. They are typically initial-
ized outside the class definition in a source file.
c l a s s MyClass {
public :
s t a t i c i n t count ; / / Declaration
};

i n t MyClass : : count = 0 ; / / Initialization

2. Shared Among All Objects: All instances (objects) of the class share the same static
member variable. Modifying the static member variable in one object will affect
its value in all other objects of the class.

3. Accessing Static Member Variables: You can access a static member variable using
the class name followed by the scope resolution operator (::) or by using an object
of the class. However, it’s a good practice to access static members through the
class name to emphasize that they are shared among all instances.
i n t main ( ) {
MyClass o b j 1 , o b j 2 ;
MyClass : : count = 1 0 ; / / A c c e s s t h r o u g h c l a s s name
o b j 1 . count = 2 0 ; / / A l s o v a l i d but not recommended f o r s t a t i c members
c o u t << o b j 2 . count << e n d l ; / / A c c e s s t h r o u g h o b j e c t ( not recommended )
return 0 ;
}

4. Initialization: Static member variables should be explicitly initialized, either inside


the class or in a source file outside the class definition.

5. Use Cases: Static member variables are often used to maintain data that is shared
among all instances of a class. For example, you might use a static member variable
to keep count of the number of objects created from a class.
c l a s s MyClass {
public :
s t a t i c i n t o b j e c t C o u n t ; / / S t a t i c member v a r i a b l e
MyClass ( ) {
objectCount ++; / / Increment count in the c o n s t r u c t o r
}
};

i n t MyClass : : o b j e c t C o u n t = 0 ; / / I n i t i a l i z e t h e s t a t i c member v a r i a b l e

6. Constant Static Member Variables: You can also have static member variables
that are constants, declared as static const. These values are fixed and cannot be
modified once initialized.
c la ss Constants {
public :
s t a t i c const i n t MAX_VALUE = 1 0 0 ; / / C o n s t a n t s t a t i c member
};

7. Thread Safety: Be cautious when using static member variables in a multithreaded


environment. Concurrent access without proper synchronization can lead to race
conditions. If thread safety is a concern, consider using mutexes or thread-safe
constructs.

Static member variables are a powerful feature in C++ and can be used for various
purposes, including counting instances, sharing configuration settings, and managing
shared resources among class instances. They provide a way to store class-level data
that is independent of object-specific data.

Static Member Functions


In C++, a static member function is a member function of a class that is associated with
the class itself rather than with instances (objects) of the class. They are called on the
class itself, not on individual objects of the class. Static member functions have some
distinctive characteristics:

1. A static member function is declared inside a class like any other member function,
but it is also preceded by the static keyword. It is defined outside the class just like
a regular function.
34 L E C T URE 3: C++ CL ASSES

c l a s s MyClass {
public :
s t a t i c void s t a t i c F u n c t i o n ( ) ; / / Declaration
};

void MyClass : : s t a t i c F u n c t i o n ( ) {
/ / Definition
/ / S t a t i c member f u n c t i o n s do not have a c c e s s t o i n s t a n c e − s p e c i f i c data
}

2. No Access to Instance Data: Static member functions do not have access to instance-
specific data (non-static data members) or this pointer because they are not called
on instances. They can only access static members (variables or functions) of the
class.
3. Accessing Static Member Functions: You can call a static member function using
the class name followed by the scope resolution operator (::) or using an object
of the class. However, it’s more common to call them through the class name to
emphasize that they are class-level functions.
i n t main ( ) {
MyClass : : s t a t i c F u n c t i o n ( ) ; / / A c c e s s t h r o u g h c l a s s name
MyClass o b j ;
o b j . s t a t i c F u n c t i o n ( ) ; / / A l s o v a l i d but not common
return 0 ;
}

4. Use Cases: Static member functions are often used for tasks that are not related to
specific instances but are associated with the class as a whole. Common use cases
include utility functions, factory methods, and functions that operate on static
data members.
class MathUtility {
public :
s t a t i c i n t add ( i n t a , i n t b ) {
return a + b ;
}
};

i n t r e s u l t = M a t h U t i l i t y : : add ( 5 , 3 ) ; / / C a l l i n g a s t a t i c member f u n c t i o n

5. Benefits: Static member functions offer several benefits, such as encapsulating


class-level functionality, improving code organization, and avoiding the need to
create instances for certain operations.
6. Thread Safety: Static member functions, like static member variables, should be
used with caution in multithreaded programs. Ensure proper synchronization
mechanisms are in place if they access shared resources or data.

Static member functions are a valuable feature in C++ for organizing code and pro-
viding class-level functionality. They are a key part of the language’s support for encap-
sulation and object-oriented design.
Constant Member Functions
In C++, a const member function is a member function of a class that promises not
to modify the state of the object on which it is called. It is a way of declaring that the
function does not modify the data members of the object. This promise is enforced
by the compiler, which generates an error if you attempt to modify non-mutable data
members within a const member function.
Here’s how const member functions work and why they are useful:

1. Declaration and Syntax: To declare a member function as const, you add the
const keyword after the function’s parameter list but before its body in the class
declaration.
c l a s s MyClass {
public :
i n t g e t D a t a ( ) const ; / / D e c l a r a t i o n o f a c o n s t member f u n c t i o n
};

When defining the function outside the class, you include const again to indicate
that it’s a const member function.
i n t MyClass : : g e t D a t a ( ) const {
/ / I m p l e m e n t a t i o n o f t h e c o n s t member f u n c t i o n
/ / Cannot modify non−mutable data members h e r e
}

2. Promise of Non-Modification: By declaring a member function as const, you


promise that it won’t modify the state of the object on which it is called. This
includes not modifying any non-mutable data members of the class.

3. Access to const and Non-const Objects: You can call a const member function
on both const and non-const objects of the class. However, you cannot call a
non-const member function on a const object, as it could potentially modify the
object’s state.
const MyClass o b j 1 ;
MyClass o b j 2 ;

int data1 = obj1 . getData ( ) ; / / C a l l i n g a c o n s t member f u n c t i o n on a c o n s t o b j e c t


int data2 = obj2 . getData ( ) ; / / C a l l i n g a c o n s t member f u n c t i o n on a non−c o n s t o b j e

4. Use Cases: const member functions are particularly useful when you want to
provide read-only access to the object’s data without allowing modifications. They
are commonly used for getter methods that return the value of data members
without altering them.
c l a s s TemperatureSensor {
public :
double g e t T e m p e r a t u r e ( ) const {
return t e m p e r a t u r e ;
}
36 L E C T URE 3: C++ CL ASSES

private :
double t e m p e r a t u r e ;
};

5. Mutable Data Members: If you have data members that you want to modify even
within const member functions, you can declare them as mutable. These mutable
members are exempt from the promise of non-modification.
c l a s s MyClass {
public :
i n t g e t D a t a ( ) const {
return d a t a + mutableData ;
}

private :
int data ;
mutable i n t mutableData ; / / Can be m o d i f i e d i n c o n s t member f u n c t i o n s
};

6. Error Prevention: Using const member functions helps prevent unintentional


modifications to objects and promotes good coding practices by clearly indicating
which functions can modify the object’s state and which cannot.

const member functions play a crucial role in ensuring data integrity and providing
safe access to object data within C++ classes. They are an important feature for designing
classes with well-defined interfaces and controlling object behavior.

Lab 3: Introduction to C++ Classes


• Lab Objectives:

1. Define a class with data members and member functions.


2. Implement constructors and destructor.
3. Create objects and use class member functions.
4. Create objects and use class member functions.
5. Understand and practice encapsulation.
6. Apply object-oriented programming principles.

• Task 1: Create a Class

1. Define a class named Student with the following private data members:
– std::string name (Student’s name)
– int age (Student’s age)
– double gpa (Student’s GPA)
2. Add public member functions to set and get the values of these data members.

• Task 2: Constructors and Destructor


1. Implement a default constructor that initializes the data members with de-
fault values (e.g., empty string, age of 0, and GPA of 0.0).
2. Implement a parameterized constructor that accepts values for the data mem-
bers and initializes them accordingly.
3. Implement a destructor that prints a message when an object is destroyed.

• Task 3: Create Objects

1. Create two Student objects: one using the default constructor and another
using the parameterized constructor.
2. Set and display values for the data members of these objects using member
functions.

• Task 4: Encapsulation

1. Change the access level of the data members to be private.


2. Modify the Student class to use public member functions to set and get the
values of the data members.

• Task 5: Object-Oriented Principles

1. Implement a member function void study() in the Student class that prints a
message indicating that the student is studying.
2. Implement a member function void displayInfo() in the Student class that
displays the student’s name, age, and GPA.

• Task 6: Main Program

1. In the main() function, create a menu-driven program that allows the user to
perform the following actions:
– Create a new Student object using the parameterized constructor and set
its values.
– Display the information of a student using the displayInfo() function.
– Make a student study using the study() function.
– Make a student study using the study() function.
2. Lab Completion:
– Test your program thoroughly by creating and manipulating Student
objects.
– Ensure that constructors and destructors are called correctly.
– Verify that encapsulation principles are followed.
– Demonstrate the use of member functions to access and modify data
members.
38 L E C T URE 3: C++ CL ASSES

Practice Questions
1. Define a C++ class named Rectangle with private data members for length and
width. Provide public member functions to set and get the values of these data
members. Create objects of this class and demonstrate the use of these member
functions.

2. Create a C++ class named Person with private data members for name and age.
Implement a parameterized constructor to initialize these members and a member
function to display the person’s details. Create objects of this class and display the
details of multiple persons.

3. Define a C++ class called BankAccount with private data members for account-
Number, accountHolderName, and balance. Implement member functions to:

• Initialize the account with a constructor.


• Deposit money into the account.
• Withdraw money from the account.
• Display the account details (account number, holder name, and balance).

4. Create a C++ class called Circle with a private data member for the radius. Imple-
ment member functions to calculate and return the area and circumference of the
circle. Demonstrate the use of this class to calculate the area and circumference of
multiple circles.

5. Define a C++ class named Book to represent a book with private data members
for title, author, and publicationYear. Implement a parameterized constructor
to initialize these members and a member function to display the book’s details.
Create an array of Book objects and display the details of all the books.

6. Create a C++ class called Date with private data members for day, month, and
year. Implement a parameterized constructor to initialize these members. Write a
member function to display the date in the format "dd/mm/yyyy." Create objects
of this class to represent various dates and display them.

7. Implement a C++ class called Employee with private data members for name,
employeeId, and salary. Provide a parameterized constructor to initialize these
members. Implement a member function to give an annual salary raise to an
employee. Create objects of this class and apply a salary raise to some employees.

8. Define a C++ class named Car with private data members for make, model, and
year. Implement a parameterized constructor to initialize these members. Write
a member function to display the car’s details. Create objects of this class to
represent different cars and display their details.

9. Create a C++ class called Student with private data members for name, rollNumber,
and marks. Implement a parameterized constructor to initialize these members.
Write a member function to calculate and return the grade based on the marks (A,
B, C, D, or F). Create objects of this class to represent students and display their
grades.

10. Implement a C++ class named Complex to represent complex numbers with private
data members for the real and imaginary parts. Provide member functions to
perform addition and multiplication of complex numbers. Create objects of this
class and demonstrate these operations.
40 L E C T URE 3: C++ CL ASSES
Lecture 4: Inheritance and Polymorphism

In C++, classes are the building blocks of object-oriented programming, and they play a
pivotal role in inheritance. Inheritance allows you to create new classes (derived or child
classes) by inheriting the properties and behaviors of existing classes (base or parent
classes). Let’s explore how classes are used in inheritance within C++:

Base Class (Parent Class)


The base class serves as the foundation for one or more derived classes. It defines
common attributes and methods that can be shared among multiple derived classes.
Inheritance allows the base class to pass on its characteristics to the derived classes.
c l a s s Animal {
public :
void e a t ( ) {
/ / Common b e h a v i o r f o r a l l animals
}

void s l e e p ( ) {
/ / Common b e h a v i o r f o r a l l animals
}
};

Derived Class (Child Class)


Derived Class (Child Class): A derived class inherits the properties and behaviors of
the base class. It can also add its own unique attributes and methods, making it more
specialized. Derived classes extend the functionality of the base class.
c l a s s Dog : p u b l i c Animal {
public :
void bark ( ) {
/ / Unique b e h a v i o r f o r d o g s
}
};

c l a s s Cat : p u b l i c Animal {
public :
void meow ( ) {
/ / Unique b e h a v i o r f o r c a t s
}
};
42 L E C T URE 4: INHERITANCE AND POLYMORPHISM

Access Specifiers
In C++, you can specify the access level for the inherited members from the base class in
the derived class. The most common access specifiers are public, protected, and private.
The choice of access specifier determines how the derived class and external code can
access the inherited members.
public: Inherited members are accessible to everyone. protected: Inherited members
are accessible within the derived class and its subclasses but not directly from external
code. private: Inherited members are not accessible in the derived class (unless a friend
relationship is established), making them effectively hidden.

Constructor and Destructor Inheritance


Constructors and destructors are also inherited from the base class. The derived class
can call the base class’s constructor explicitly to initialize the inherited members.
c l a s s Dog : p u b l i c Animal {
public :
Dog ( ) : Animal ( ) {
/ / C o n s t r u c t o r f o r Dog
}

~Dog ( ) {
/ / D e s t r u c t o r f o r Dog
}
};

Method Overriding:
Method Overriding: Derived classes can override (replace) base class methods with their
own implementations. This allows for polymorphism, where different objects of the
same base class type can exhibit different behavior based on their actual derived class
type.
c l a s s B i r d : p u b l i c Animal {
public :
void e a t ( ) {
/ / O v e r r i d e t h e e a t method f o r b i r d s
/ / Custom b e h a v i o r f o r b i r d s
}
};

The Shape Example


Let’s illustrate inheritance in C++ using a simple example involving geometric shapes.
We’ll create a base class called Shape and two derived classes, Circle and Rectangle, to
showcase how they inherit properties and behaviors from the base class.
# include <iostream >
# i n c l u d e <cmath >

/ / Base c l a s s : Shape
c l a s s Shape {
public :
/ / Common method t o c a l c u l a t e a r e a ( v i r t u a l , t o be o v e r r i d d e n )
v i r t u a l double c a l c u l a t e A r e a ( ) {
return 0 . 0 ; / / D e f a u l t i m p l e m e n t a t i o n f o r b a s e c l a s s
}
};

/ / D e r i v e d c l a s s : C i r c l e ( i n h e r i t s from Shape )
c l a s s C i r c l e : p u b l i c Shape {
private :
double r a d i u s ;

public :
/ / Constructor for Circle
C i r c l e ( double r ) : r a d i u s ( r ) { }

/ / O v e r r i d e t h e c a l c u l a t e A r e a method f o r c i r c l e s
double c a l c u l a t e A r e a ( ) o v e r r i d e {
return 3 . 1 4 1 5 9 2 6 5 3 5 9 * r a d i u s * r a d i u s ; / / Area o f a c i r c l e
}
};

/ / D e r i v e d c l a s s : R e c t a n g l e ( i n h e r i t s from Shape )
c l a s s R e c t a n g l e : p u b l i c Shape {
private :
double l e n g t h ;
double width ;

public :
/ / Constructor for Rectangle
R e c t a n g l e ( double l , double w) : l e n g t h ( l ) , width (w) { }

/ / O v e r r i d e t h e c a l c u l a t e A r e a method f o r r e c t a n g l e s
double c a l c u l a t e A r e a ( ) o v e r r i d e {
return l e n g t h * width ; / / Area o f a r e c t a n g l e
}
};

i n t main ( ) {
/ / C r e a t e o b j e c t s o f C i r c l e and R e c t a n g l e
Circle circle ( 5 . 0 ) ;
Rectangle rectangle ( 4 . 0 , 6 . 0 ) ;

/ / C a l c u l a t e and d i s p l a y a r e a s
s t d : : c o u t << " Area o f t h e C i r c l e : " << c i r c l e . c a l c u l a t e A r e a ( ) << s t d : : e n d l ;
s t d : : c o u t << " Area o f t h e R e c t a n g l e : " << r e c t a n g l e . c a l c u l a t e A r e a ( ) << s t d : : e n d l ;

return 0 ;
}

In this example:

1. We define a base class called Shape, which includes a virtual method calculateArea().
This method will be overridden in the derived classes to calculate the area of spe-
44 L E C T URE 4: INHERITANCE AND POLYMORPHISM

cific shapes.

2. We create two derived classes, Circle and Rectangle, each inheriting from the
Shape base class. These derived classes have their own specific attributes (radius
for circles and length and width for rectangles) and override the calculateArea()
method to provide their own implementation of area calculation.

3. In the main() function, we create instances of Circle and Rectangle. When we call
the calculateArea() method on these objects, the appropriate overridden method
is executed, and it calculates and returns the area specific to each shape.

Multiple Inheritance
Multiple inheritance is a feature in object-oriented programming languages, including
C++, that allows a class to inherit properties and behaviors from more than one base
class. This means a derived class can inherit from multiple parent classes. While multiple
inheritance can be powerful, it also introduces complexities and challenges that need
to be managed carefully. In C++, you can achieve multiple inheritance by specifying
multiple base classes separated by commas in the declaration of a derived class.
Here’s a basic example to illustrate multiple inheritance in C++:
# include <iostream >

/ / F i r s t base c l a s s
class A {
public :
void methodA ( ) {
s t d : : c o u t << " Method A from c l a s s A" << s t d : : e n d l ;
}
};

/ / Second b a s e c l a s s
class B {
public :
void methodB ( ) {
s t d : : c o u t << " Method B from c l a s s B" << s t d : : e n d l ;
}
};

/ / D e r i v e d c l a s s i n h e r i t i n g from both A and B


c l a s s C : public A , public B {
public :
void methodC ( ) {
s t d : : c o u t << " Method C from c l a s s C" << s t d : : e n d l ;
}
};

i n t main ( ) {
C cObj ;

/ / A c c e s s i n g methods from both b a s e c l a s s e s


cObj . methodA ( ) ; / / C a l l s methodA from c l a s s A
cObj . methodB ( ) ; / / C a l l s methodB from c l a s s B

/ / A c c e s s i n g a method from t h e d e r i v e d c l a s s
cObj . methodC ( ) ; / / C a l l s methodC from c l a s s C

return 0 ;
}

In this example:

• We have two base classes, A and B, each with its own method.

• The C class is derived from both A and B using multiple inheritance. As a result, it
inherits the methods methodA from class A and methodB from class B.

• In the main() function, we create an instance of the C class and can access methods
from both base classes (A and B) as well as its own method (methodC).

Multiple inheritance can be a powerful tool, allowing you to model complex rela-
tionships between classes and reuse code effectively. However, it can also introduce
ambiguity and the "diamond problem" (a situation where multiple base classes have a
common ancestor) in more complex scenarios. Careful design and usage of multiple in-
heritance, along with virtual inheritance and explicit scope resolution, can help mitigate
these issues when they arise.

Polymorphism
Polymorphism is a fundamental concept in object-oriented programming, and it plays
a significant role in C++. It allows objects of different classes to be treated as objects
of a common base class, providing a way to work with objects in a more abstract and
generic manner. There are two primary forms of polymorphism in C++: compile-time
(or static) polymorphism and runtime (or dynamic) polymorphism.

Compile-Time Polymorphism (Static Polymorphism)


• Function Overloading: This is a form of compile-time polymorphism where multi-
ple functions in the same scope have the same name but different parameter lists.
The appropriate function to be called is determined at compile time based on the
number and types of arguments provided.
class Calculator {
public :
i n t add ( i n t a , i n t b ) {
return a + b ;
}

double add ( double a , double b ) {


return a + b ;
}
};
46 L E C T URE 4: INHERITANCE AND POLYMORPHISM

• Operator Overloading: C++ allows you to redefine the behavior of operators for
user-defined classes by overloading them. This enables you to use operators like +,
-, *, etc., with user-defined types.
c l a s s Complex {
public :
Complex operator + ( const Complex& o t h e r ) {
Complex r e s u l t ;
r e s u l t . r e a l = t h i s −> r e a l + o t h e r . r e a l ;
r e s u l t . i m a g i n a r y = t h i s −>i m a g i n a r y + o t h e r . i m a g i n a r y ;
return r e s u l t ;
}
};

Runtime Polymorphism (Dynamic Polymorphism)


• Function Overriding: This form of polymorphism occurs when a derived class
provides a specific implementation for a function that is already defined in its base
class. The function to be executed is determined at runtime based on the actual
object’s type.
c l a s s Shape {
public :
v i r t u a l void draw ( ) {
/ / Common i m p l e m e n t a t i o n i n t h e b a s e c l a s s
}
};

c l a s s C i r c l e : p u b l i c Shape {
public :
void draw ( ) o v e r r i d e {
/ / S p e c i f i c implementation f o r c i r c l e s
}
};

c l a s s Square : p u b l i c Shape {
public :
void draw ( ) o v e r r i d e {
/ / S p e c i f i c implementation f o r squares
}
};

• Polymorphic Function Calls: You can use pointers or references to the base class
to achieve polymorphism. When a base class pointer/reference is used to call a
virtual function, the appropriate derived class function is invoked at runtime.
Shape * s h a p e P t r ;
Circle circle ;
Square s q u a r e ;

shapePtr = & c i r c l e ;
s h a p e P t r −>draw ( ) ; / / C a l l s C i r c l e ’ s draw ( ) method

s h a p e P t r = &s q u a r e ;
s h a p e P t r −>draw ( ) ; / / C a l l s Square ’ s draw ( ) method

• Dynamic Binding: The mechanism by which the correct function is called at run-
time is called dynamic binding or late binding. It allows for more flexible and
extensible code as new derived classes can be added without changing existing
code that uses the base class interface.

Polymorphism is a powerful concept that enhances code flexibility and reusability


in C++. It enables you to write more generic and maintainable code by working with
objects based on their common interfaces rather than their specific types. It’s a key
feature of object-oriented programming and is widely used in C++ programs.

Shape drawing
A common real-world example of dynamic polymorphism in C++ is the use of polymor-
phic behavior through virtual functions when dealing with various shapes in a drawing
application. Let’s create a simplified example to illustrate dynamic polymorphism with
shapes:
# include <iostream >
# include < vector >

c l a s s Shape {
public :
v i r t u a l void draw ( ) {
s t d : : c o u t << " Drawing a g e n e r i c shape " << s t d : : e n d l ;
}
};

c l a s s C i r c l e : p u b l i c Shape {
public :
void draw ( ) o v e r r i d e {
s t d : : c o u t << " Drawing a c i r c l e " << s t d : : e n d l ;
}
};

c l a s s Square : p u b l i c Shape {
public :
void draw ( ) o v e r r i d e {
s t d : : c o u t << " Drawing a s q u a r e " << s t d : : e n d l ;
}
};

i n t main ( ) {
s t d : : v e c t o r <Shape * > shapes ;

Circle circle ;
Square s q u a r e ;

shapes . push_back (& c i r c l e ) ;


shapes . push_back (& s q u a r e ) ;

f o r ( Shape * shape : shapes ) {


48 L E C T URE 4: INHERITANCE AND POLYMORPHISM

shape −>draw ( ) ; / / C a l l s t h e a p p r o p r i a t e draw ( ) method b a s e d on t h e a c t u a l o b j e c


}

return 0 ;
}
In this example:
• We define a base class Shape with a virtual draw() method. This method has a
common interface for all shapes but is meant to be overridden by derived classes.
• We create two derived classes, Circle and Square, each of which overrides the
draw() method to provide a specific implementation for drawing a circle or square.
• In the main() function, we create instances of Circle and Square. We then store
pointers to these objects in a vector of Shape*, which is possible because of the
polymorphic relationship between the base class and derived classes.
We loop through the vector of Shape* and call the draw() method on each object.
Because the draw() method is marked as virtual and overridden in the derived classes, the
appropriate draw() method for each shape is called at runtime, demonstrating dynamic
polymorphism.

Abstract Classes
Abstract classes are a fundamental concept in object-oriented programming, and they
play an essential role in C++ and similar languages. An abstract class is a class that
cannot be instantiated directly; instead, it is meant to serve as a blueprint for other
classes. Abstract classes are often used to define a common interface and provide some
default behavior that derived classes must implement.
Here are some key characteristics and uses of abstract classes in C++:
• Cannot be Instantiated: You cannot create objects of an abstract class. Attempting
to do so will result in a compilation error. Abstract classes are meant to be used as
base classes for other classes.
• May Contain Pure Virtual Functions: Abstract classes often contain at least one
pure virtual function. A pure virtual function is declared with the virtual keyword
and is followed by = 0;. It has no implementation in the abstract class and must be
implemented by any concrete (i.e., non-abstract) derived class. This enforces that
derived classes provide their own implementation of these functions.
class AbstractShape {
public :
v i r t u a l void draw ( ) = 0 ; / / Pure v i r t u a l f u n c t i o n
v i r t u a l double a r e a ( ) const = 0 ; / / A n o t h e r pure v i r t u a l f u n c t i o n
};

• Can Contain Concrete (Non-Virtual) Functions: Abstract classes can have regular
(non-virtual) member functions that provide a default implementation that can
be shared among derived classes. These functions can be overridden in derived
classes if needed.
class AbstractShape {
public :
/ / C o n c r e t e ( non−v i r t u a l ) f u n c t i o n with d e f a u l t i m p l e m e n t a t i o n
void s e t C o l o r ( const s t d : : s t r i n g & c o l o r ) {
t h i s −> c o l o r = c o l o r ;
}

/ / Pure v i r t u a l f u n c t i o n s
v i r t u a l void draw ( ) = 0 ;
v i r t u a l double a r e a ( ) const = 0 ;

protected :
std : : s t r i n g color ;
};

• Derived Classes Must Implement Pure Virtual Functions: Any class derived from
an abstract class must implement all the pure virtual functions declared in the
base class. Failure to do so will also result in a compilation error.
c l a s s C i r c l e : public AbstractShape {
public :
void draw ( ) o v e r r i d e {
/ / Implement drawing f o r c i r c l e s
}

double a r e a ( ) const o v e r r i d e {
/ / Implement a r e a c a l c u l a t i o n f o r c i r c l e s
}
};

• Useful for Creating Common Interfaces: Abstract classes are useful for defining
common interfaces in situations where multiple classes share a set of functions
but implement them differently. They help ensure consistency and conformity
among derived classes.

In summary, abstract classes in C++ serve as a way to define a contract or interface


that derived classes must adhere to. They allow you to create a hierarchy of related
classes with a shared interface while providing some default behavior in the base class.
Abstract classes are an essential part of C++’s support for polymorphism and design by
contract.

Interface Classes
In C++, there is no direct language construct called "interface" as you might find in
some other programming languages like Java or C#. However, you can achieve similar
functionality using abstract classes and pure virtual functions, which serve as interfaces
in C++. These classes act as contracts that derived classes must adhere to by providing
implementations for all the pure virtual functions declared in the base class. This is
often referred to as "interface-like" behavior in C++.
Here’s how you can create an interface-like structure in C++:
50 L E C T URE 4: INHERITANCE AND POLYMORPHISM

c l a s s Shape {
public :
virtual void draw ( ) = 0 ; / / Pure v i r t u a l f u n c t i o n ( i n t e r f a c e method )
virtual double a r e a ( ) const = 0 ; / / A n o t h e r pure v i r t u a l f u n c t i o n ( i n t e r f a c e method
virtual ~Shape ( ) { } / / V i r t u a l d e s t r u c t o r ( recommended f o r polymorphism )
};

In this example:

• Shape is an abstract class that defines an interface with two pure virtual functions,
draw() and area(). Any class inheriting from Shape must provide implementations
for these methods.

• The destructor Shape() is also declared as virtual. This is recommended when


you’re working with polymorphism to ensure that derived class destructors are
called correctly when objects are deleted through a base class pointer or reference.

Here’s how you can create a concrete class that implements the Shape interface:
c l a s s C i r c l e : p u b l i c Shape {
public :
void draw ( ) o v e r r i d e {
/ / Implement drawing f o r c i r c l e s
}

double a r e a ( ) const o v e r r i d e {
/ / Implement a r e a c a l c u l a t i o n f o r c i r c l e s
}
};

In this example, the Circle class inherits from Shape and provides implementations
for both draw() and area(), adhering to the interface defined by the Shape class.
You can create other classes like Rectangle, Triangle, etc., following the same pattern
to implement the Shape interface.
Although C++ doesn’t have a native keyword for defining interfaces like some other
languages, this approach using abstract classes and pure virtual functions provides simi-
lar functionality. It allows you to define and enforce contracts for classes that implement
specific interfaces, promoting code consistency and polymorphism.

Best practices and guidelines


1. Favor Composition Over Inheritance: Before using inheritance, consider whether
composition (using objects of other classes as members) might be a better choice.
Composition can often lead to more flexible and less tightly coupled designs.

2. Use Public Inheritance Sparingly: Public inheritance establishes an "is-a" relation-


ship. Use it only when derived classes truly inherit the interface and behavior of
the base class.

3. Prefer Protected or Private Inheritance: If you don’t want to expose the interface of
the base class to the outside world, consider using protected or private inheritance.
This is often a better choice for implementation inheritance.
4. Avoid Deep Inheritance Hierarchies: Deep hierarchies can lead to complexity and
make code harder to understand and maintain. Aim for shallow hierarchies with a
clear and focused purpose.

5. Declare Virtual Functions Properly: Declare virtual functions in the base class with
a clear understanding of which functions should be overridden by derived classes.

6. Always Provide a Virtual Destructor: When using inheritance with polymorphism,


always declare a virtual destructor in the base class. This ensures that derived
class destructors are called correctly when objects are deleted through base class
pointers.

7. Use the override Keyword: In derived classes, use the override keyword when
overriding virtual functions. This helps catch errors at compile time and makes
the code more readable.

8. Avoid Hiding Inherited Functions: When inheriting functions with the same name
as those in the derived class, use the using keyword to bring them into scope rather
than hiding them. Hiding can lead to confusion.

9. Implement Pure Virtual Functions Completely: Derived classes must provide com-
plete implementations for pure virtual functions. Failure to do so results in a
compilation error.

10. Don’t Rely on the Order of Constructor Calls: The order in which constructors are
called for base and derived classes can be complex. Avoid relying on a specific
order and design your classes to be independent of constructor order.

11. Use Static Polymorphism Where Appropriate: Not all polymorphism requires dy-
namic dispatch. If performance is critical, consider using function overloading
and templates for static polymorphism.

12. Document Your Inheritance Hierarchy: Provide clear documentation for your
inheritance hierarchy, including the purpose of each class and its relationship with
others.

13. Consider the Liskov Substitution Principle (LSP): Ensure that derived classes can be
substituted for their base classes without altering the correctness of the program.

14. Test and Verify: Thoroughly test your derived classes to ensure they behave as
expected when inheriting from a base class.

15. Refactor When Necessary: Be open to refactoring your inheritance hierarchy if you
discover design flaws or changing requirements.

16. Use Access Specifiers Thoughtfully: Choose access specifiers (public, protected,
private) carefully to control the visibility of inherited members based on your
design intent.
52 L E C T URE 4: INHERITANCE AND POLYMORPHISM

Adhering to these best practices and guidelines when using inheritance in C++
will help you create maintainable, extensible, and robust code while minimizing
potential pitfalls and design issues.

Summary
Inheritance and polymorphism are two fundamental concepts in C++ that enable the
creation of robust, reusable, and extensible object-oriented code. They play key roles in
structuring class hierarchies and enabling dynamic behavior in C++ programs.

• Inheritance:

1. Definition: Inheritance is a mechanism in C++ that allows a class (called the


derived or child class) to inherit properties and behaviors from another class
(called the base or parent class). It establishes an "is-a" relationship between
classes.
2. Key Points:
Base classes provide a common template for derived classes to build upon.
Derived classes inherit attributes and methods from their base classes. In-
heritance promotes code reuse, hierarchy, and modularity in software design.
Access specifiers (public, protected, private) control the visibility of inherited
members in derived classes. Constructors and destructors are inherited and
can be explicitly called in derived classes.

• Polymorphism

1. Definition: Polymorphism is a concept that allows objects of different classes


to be treated as objects of a common base class. It enables dynamic method
invocation and flexible code that adapts to the actual type of an object.
2. Key Points: Polymorphism comes in two forms: compile-time (static) poly-
morphism and runtime (dynamic) polymorphism. Compile-time polymor-
phism is achieved through function overloading and operator overloading.
Runtime polymorphism is achieved through function overriding and virtual
functions. Virtual functions are declared in base classes with the virtual key-
word and must be overridden in derived classes. Polymorphism enables the
development of generic code that can work with objects of different types
through a common interface. It promotes code extensibility and flexibility, as
new classes can be added without modifying existing code that relies on the
base class interface.

• Inheritance and Polymorphism Together:

1. Inheritance and polymorphism often go hand in hand. Inheritance allows


you to create class hierarchies where derived classes inherit from a common
base class.
2. Polymorphism enables dynamic behavior by allowing different derived classes
to provide their own implementations of base class methods.
3. Polymorphic function calls, using base class pointers or references, ensure
that the correct method is executed based on the actual object type at run-
time.
4. Together, inheritance and polymorphism facilitate the creation of flexible
and maintainable code, making C++ a powerful language for object-oriented
programming.

Lab 4: Inheritance in C++


1. Task 1: Create Base Class (Shape)
Create a C++ program that defines a base class called Shape.
In the Shape class, declare the following attributes: name (string): To store the
name of the shape. color (string): To store the color of the shape.
Declare a constructor in the Shape class that initializes name and color.
Declare a virtual function named display() in the Shape class. This function should
display the name and color of the shape.

2. Task 2: Create Derived Classes (Circle and Rectangle) Create two derived classes,
Circle and Rectangle, that inherit from the Shape class.
In the Circle class, declare an attribute radius (double) and a constructor that
initializes it.
In the Rectangle class, declare attributes length and width (both double) and a
constructor that initializes them.
Override the display() function in both the Circle and Rectangle classes to display
specific information about each shape, including its name, color, and relevant
attributes (radius for circles and length/width for rectangles).

3. Task 3: Create Objects and Test


In the main() function, create objects of both the Circle and Rectangle classes.
Set the name, color, and specific attributes (radius and length/width) for each
object.
Call the display() function for each object to verify that the correct information is
displayed based on the object’s type.

Questions
1. What is inheritance in C++? Explain its significance in object-oriented program-
ming.

2. How do you declare a base class in C++? Provide an example of a simple base class.
54 L E C T URE 4: INHERITANCE AND POLYMORPHISM

3. What is a derived class in C++? How does it relate to the base class?

4. Explain the "is-a" relationship between base and derived classes. Provide an ex-
ample to illustrate this relationship.

5. What is a virtual function in C++? Why is it used in inheritance?

6. How is method overriding achieved in C++? Provide an example of a base class


with a virtual function and a derived class that overrides it.

7. Describe the access specifiers (public, protected, private) in C++ inheritance. How
do they affect the visibility of inherited members?

8. What is the purpose of the virtual keyword in C++ when used with functions in a
base class?

9. How do you prevent a base class function from being overridden in a derived class
in C++?

10. Explain the concept of multiple inheritance in C++. What challenges and ambigui-
ties can arise when using it?

11. What is the "diamond problem" in the context of multiple inheritance, and how
can it be resolved in C++?

12. How can you create an instance of a derived class in C++, and what happens when
you call a base class method on it?

13. What is dynamic polymorphism, and how does it relate to virtual functions and
inheritance in C++?

14. Explain the role of destructors in C++ inheritance. When should a base class
destructor be declared as virtual?

15. Give an example of a real-world scenario where inheritance in C++ can be applied
to model relationships between objects.
Lecture 5: Operator Overloading

Introduction to Operator Overloading


Operator overloading is a feature in C++ that allows you to redefine the behavior of
operators when applied to user-defined data types, such as classes and structures. In
other words, it enables you to give new meanings to standard C++ operators when used
with your custom objects. This feature allows you to write more intuitive and expressive
code by extending the functionality of operators to work with user-defined types.
When you overload an operator, you provide a specific implementation for that
operator in the context of your custom class or data type. This means you can define
how operators like +, -, *, /, ==, !=, and others behave when used with objects of
your class.
For example, if you have a Complex class to represent complex numbers, you can
overload the + operator to add two Complex objects together, making the code more
natural and intuitive:
Complex operator + ( const Complex& l h s , const Complex& r h s ) {
Complex r e s u l t ;
r e s u l t . r e a l = lhs . r e a l + rhs . r e a l ;
r e s u l t . imaginary = lhs . imaginary + rhs . imaginary ;
return r e s u l t ;
}
Operator overloading is a useful feature in C++ for several reasons:

• Intuitive Syntax: Operator overloading allows you to use familiar operators (e.g.,
+, -, *, ==) with user-defined types. This makes code more intuitive and read-
able, as it resembles natural mathematical or logical expressions.
• Simplifies Complex Operations: For complex data types or mathematical structures
like matrices, complex numbers, or custom containers, operator overloading can
simplify complex operations, reducing the need for verbose function calls.
• Code Clarity: Overloaded operators can lead to code that closely resembles the
problem domain, making it easier for developers to understand and maintain.
This can improve code clarity and reduce the chances of errors.
• Consistency: Operator overloading allows you to maintain consistency in your
code. When you use operators like +, ==, or « with your custom types, you apply
the same logical meaning as with built-in types.
• Reusability: Well-designed operator overloads can be reused across different projects
and codebases. This reusability saves time and effort when implementing similar
functionality.
56 L E C T URE 5: OPERATOR OVERLOADING

• Custom Behavior: You can tailor the behavior of operators to your specific needs.
For example, you can redefine the + operator to perform vector addition for a
physics simulation or concatenate strings in a custom way.

• Expressiveness: Operator overloading can make code more expressive. For exam-
ple, overloading the « operator for output can allow you to print complex data
structures with a single line of code, enhancing the expressiveness of your code.

• Elegant Syntax: Operator overloading can lead to elegant and concise code, which
can be especially beneficial in math-heavy or scientific applications.

• Compatibility with Standard Library: When you overload operators consistently


with standard library types, you can seamlessly integrate your custom types into
the existing ecosystem of C++ libraries and algorithms.

• Object-Oriented Design: Operator overloading aligns well with object-oriented


programming (OOP) principles, where objects encapsulate both data and behavior.
It allows objects to interact with each other in a more natural and intuitive manner.

However, it’s important to use operator overloading judiciously and maintain clear
and consistent semantics. Overloading operators in a non-standard or confusing way
can lead to code that is difficult to understand and debug. Therefore, it’s crucial to follow
best practices
and conventions when employing operator overloading in your C++ code.

Overloading Unary Operators


Overloading unary operators in C++ allows you to redefine the behavior of unary opera-
tors like +, -, ++, –, and ! when applied to objects of your custom classes. This gives you
the flexibility to make your classes work with these operators in a way that is meaningful
for your specific application. To overload a unary operator, you need to define a special
member function or a non-member function that corresponds to the operator.
Here’s a step-by-step guide on how to overload unary operators in C++:

1. Choose the Operator to Overload: Decide which unary operator you want to over-
load based on the requirements of your class and the desired functionality.

2. Define the Operator Function:


To overload a unary operator, you need to define a special function with the fol-
lowing syntax:
r e t u r n _ t y p e operator < o p e r a t o r _ s y m b o l > ( p a r a m e t e r s ) ;

For example, to overload the unary minus operator -, you would define the function
as follows:
MyClass operator −() {
/ / D e f i n e custom b e h a v i o r f o r t h e unary minus o p e r a t o r
}
3. Implement the Custom Behavior: Inside the operator function, implement the
custom behavior you want for the unary operator. This behavior should make
sense in the context of your class.

4. Return the Result: Ensure that the operator function returns an object of the same
type as your class. This typically involves creating a new object with the modified
values or returning the current object with its values changed.

5. Example - Overloading Unary Minus Operator (-): Here’s an example of overloading


the unary minus operator for a Complex class representing complex numbers:
c l a s s Complex {
private :
double r e a l ;
double imag ;

public :
Complex ( double r , double i ) : r e a l ( r ) , imag ( i ) { }

/ / O v e r l o a d t h e unary minus o p e r a t o r ( −)
Complex operator −() {
return Complex(− r e a l , −imag ) ;
}

/ / O t h e r member f u n c t i o n s and data members . . .


};

In this example, when you apply the unary minus operator to a Complex object, it
negates both the real and imaginary parts of the complex number.

6. Usage Example:
Complex a ( 3 . 0 , 4 . 0 ) ;
Complex b = −a ; / / Now b c o n t a i n s ( − 3 .0 , −4 .0 )

Remember to follow the appropriate naming conventions for operator overload-


ing functions, and ensure that the operator is overloaded in a way that maintains the
semantic meaning of the operator in the context of your class.

Overloading Binary Operators


Overloading binary operators in C++ allows you to redefine the behavior of binary
operators like +, -, *, /, ==, and many others when applied to objects of your custom
classes. This enables you to make your classes work seamlessly with these operators,
providing meaningful functionality for your specific application. To overload a binary
operator, you need to define a special member function or a non-member function that
corresponds to the operator.
Here’s a step-by-step guide on how to overload binary operators in C++:
1. Choose the Operator to Overload: Decide which binary operator you want to
overload based on the requirements of your class and the desired functionality. 2.
Define the Operator Function: To overload a binary operator, you need to define a
special function with the following syntax:
58 L E C T URE 5: OPERATOR OVERLOADING

r e t u r n _ t y p e operator < o p e r a t o r _ s y m b o l > ( const Y o u r C l a s s& r h s ) const ;

For example, to overload the addition operator +, you would define the function
as follows:
Y o u r C l a s s operator + ( const Y o u r C l a s s& r h s ) const {
/ / D e f i n e custom b e h a v i o r f o r t h e a d d i t i o n o p e r a t o r
}

1. Implement the Custom Behavior: Inside the operator function, implement the
custom behavior you want for the binary operator. This behavior should make
sense in the context of your class and provide meaningful results when applied to
objects of your class.

2. Return the Result: Ensure that the operator function returns an object of the
same type as your class. This typically involves creating a new object with the
modified values or returning the current object with its values changed based on
the operation.

3. Example - Overloading Addition Operator (+): Here’s an example of overloading


the addition operator for a Vector class representing mathematical vectors:
class Vector {
private :
double x ;
double y ;

public :
V e c t o r ( double xV al , double y V a l ) : x ( x V a l ) , y ( y V a l ) { }

/ / Overload the addition operator ( + )


V e c t o r operator + ( const V e c t o r& r h s ) const {
return V e c t o r ( x + r h s . x , y + r h s . y ) ;
}

/ / O t h e r member f u n c t i o n s and data members . . .


};

In this example, when you apply the addition operator to two Vector objects, it
performs vector addition by adding their respective x and y components.

4. Usage Example:
Vector v1 ( 1 . 0 , 2 . 0 ) ;
V e c t o r v2 ( 2 . 0 , 3 . 0 ) ;
V e c t o r v3 = v 1 + v2 ; / / Now v3 c o n t a i n s ( 3 . 0 , 5 . 0 )

5. Overloading Assignment Operator (=)


When overloading binary operators, it’s often a good practice to overload the
assignment operator = as well, so you can assign the result of the operator overloads
to objects of your class.
Y o u r C l a s s& operator = ( const Y o u r C l a s s& r h s ) {
/ / D e f i n e custom a s s i g n m e n t b e h a v i o r
i f ( t h i s ! = &r h s ) {
/ / Copy v a l u e s from r h s t o t h i s o b j e c t
/ / Don ’ t f o r g e t t o handle r e s o u r c e management i f n e c e s s a r y
}
return * t h i s ;
}

Overloading Comparison Operators


Overloading comparison operators in C++ allows you to redefine the behavior of com-
parison operators like ==, !=, <, >, <=, and >= when applied to objects of your custom
classes. This enables you to make your classes work seamlessly with these operators,
providing meaningful functionality for comparing objects of your class. To overload a
comparison operator, you need to define a special member function or a non-member
function that corresponds to the operator.
Here’s a step-by-step guide on how to overload comparison operators in C++:

1. Choose the Operator to Overload: Decide which comparison operator you want to
overload based on the requirements of your class and the desired functionality.

2. Define the Operator Function:


To overload a comparison operator, you need to define a special function with the
following syntax:
bool operator < o p e r a t o r _ s y m b o l > ( const Y o u r C l a s s& r h s ) const ;

For example, to overload the equality operator ==, you would define the function
as follows:
bool operator = = ( const Y o u r C l a s s& r h s ) const {
/ / D e f i n e custom b e h a v i o r f o r t h e e q u a l i t y o p e r a t o r
}

3. Implement the Custom Behavior: Inside the operator function, implement the
custom behavior you want for the comparison operator. This behavior should
make sense in the context of your class and provide meaningful results when
comparing objects of your class.

4. Return the Result: Ensure that the operator function returns a bool value (true or
false) based on the result of the comparison.

5. Example - Overloading Equality Operator (==): Here’s an example of overloading


the equality operator for a Person class representing individuals:
c l a s s Person {
private :
s t d : : s t r i n g name ;
i n t age ;
60 L E C T URE 5: OPERATOR OVERLOADING

public :
Person ( const s t d : : s t r i n g & n , i n t a ) : name ( n ) , age ( a ) { }

/ / Overload the e q u a l i t y operator (==)


bool operator = = ( const Person& r h s ) const {
return ( name == r h s . name ) && ( age == r h s . age ) ;
}

/ / O t h e r member f u n c t i o n s and data members . . .


};

In this example, the operator== function checks whether two Person objects have
the same name and age.

6. Usage Example:
Person p e r s o n 1 ( " A l i c e " , 3 0 ) ;
Person pe rs on 2 ( " Bob " , 2 5 ) ;
Person p erson3 ( " A l i c e " , 3 0 ) ;

bool r e s u l t 1 = ( p e r s o n 1 == pe rs on 2 ) ; // false
bool r e s u l t 2 = ( p e r s o n 1 == pe rson3 ) ; / / true

7. Overloading Other Comparison Operators: You can similarly overload other com-
parison operators like !=, <, >, <=, and >= following the same principles.

Overloading Subscript Operator ([])


Overloading the subscript operator ([]) in C++ allows you to provide custom access to
elements of an object, typically when the object represents a collection or container,
like an array or a custom data structure. By overloading this operator, you can access
elements of your class in a more intuitive and controlled manner.
Here’s how to overload the subscript operator in C++:

1. Choose the Data Structure: First, determine the data structure that your class
represents and define it internally. It could be an array, a vector, a linked list, or
any other data structure you need.

2. Define the Operator Function:


To overload the subscript operator, you need to define a special member function
with the following syntax:
T& operator [ ] ( s i z e _ t i n d e x ) ;

Here, T is the type of the elements you want to access, and index is the position of
the element you want to access.

3. Implement the Custom Behavior: Inside the operator function, implement the
custom behavior for accessing elements based on the given index. Ensure that
your implementation correctly handles out-of-range indices, if applicable.
4. Return the Result: The subscript operator should return a reference to the element
you want to access. This allows you to both read and modify the element through
the subscript operator.

5. Example - Overloading Subscript Operator ([]): Here’s an example of overloading


the subscript operator for a simple dynamic array class:
c l a s s DynamicArray {
private :
int * data ;
size_t size ;

public :
DynamicArray ( s i z e _ t s z ) : s i z e ( s z ) {
d a t a = new i n t [ s i z e ] ;
}

/ / Overload the s u b s c r i p t operator [ ]


i n t & operator [ ] ( s i z e _ t i n d e x ) {
i f ( i n d e x >= s i z e ) {
throw s t d : : o u t _ o f _ r a n g e ( " Index out o f range " ) ;
}
return d a t a [ i n d e x ] ;
}

/ / D e s t r u c t o r t o r e l e a s e memory
~ DynamicArray ( ) {
delete [ ] data ;
}
};

In this example, the subscript operator allows you to access elements of the Dy-
namicArray as if it were a regular C++ array.

6. Usage Example:
DynamicArray a r r ( 5 ) ;
a r r [ 0 ] = 1 0 ; / / Assign value to the f i r s t element
i n t v a l u e = a r r [ 0 ] ; / / R e t r i e v e t h e v a l u e from t h e f i r s t e l e m e n t

7. Error Handling: Ensure that you handle out-of-range indices gracefully, either by
throwing an exception (as shown in the example) or using an alternative error-
handling mechanism that suits your needs.

Overloading Function Call Operator (())


Overloading the function call operator () in C++ allows you to make an object of your
class behave like a function when it is invoked. This is also known as creating a functor,
which is an object that can be called like a function. Overloading the function call
operator enables you to customize the behavior of your class when it is used as if it were
a function.
Here’s how to overload the function call operator in C++:
62 L E C T URE 5: OPERATOR OVERLOADING

1. Define the Operator Function:


To overload the function call operator, you need to define a special member func-
tion with the following syntax:
r e t u r n _ t y p e operator ( ) ( p a r a m e t e r _ l i s t ) ;

Here, return_type is the type of value you want the "function" to return, and
parameter_list represents any parameters you want to pass to the "function."

2. Implement the Custom Behavior: Inside the operator function, implement the
custom behavior that you want your object to exhibit when it is called like a
function. The behavior should be meaningful and relevant to your class’s purpose.

3. Return the Result: Ensure that the operator function returns a value of the specified
return_type if applicable

4. Example - Overloading the Function Call Operator (()): Here’s an example of over-
loading the function call operator for a Multiplier class, which multiplies an integer
by a specified factor:
class Multiplier {
private :
int factor ;

public :
Multiplier ( int f ) : factor ( f ) { }

/ / Overload the f u n c t i o n c a l l operator ( )


i n t operator ( ) ( i n t x ) {
return x * f a c t o r ;
}
};

In this example, an object of the Multiplier class can be used as a function that
multiplies an integer by the factor specified during its construction.

5. Usage Example:
Mu lt ip li er multiplyBy2 ( 2 ) ;
int r e s u l t = multiplyBy2 ( 5 ) ; / / C a l l s t h e M u l t i p l i e r o b j e c t a s i f i t were a f u n c t i o
/ / r e s u l t i s now 1 0

6. Customization: Overloading the function call operator allows you to customize the
behavior of your class objects in a way that makes sense for your specific applica-
tion. This can be particularly useful in scenarios where you want to encapsulate
complex or specialized functionality within an object.

7. Functor Use Cases: Functors are often used in algorithms that accept a function-
like object as a parameter, such as standard library algorithms (std::sort, std::transform)
or custom callback mechanisms.
Overloading Type Conversion Operators
Overloading type conversion operators in C++ allows you to define custom type conver-
sions between your user-defined class and other data types. This feature lets you control
how objects of your class can be implicitly or explicitly converted to other data types,
enhancing the usability and versatility of your class.
Here’s how to overload type conversion operators in C++:

1. Choose the Target Data Type: Determine the data type to which you want to
convert objects of your class. This could be a built-in type, another user-defined
type, or even a standard library type.

2. Define the Conversion Operator Function:


To overload a type conversion operator, you need to define a member function
named operator target_type() where target_type is the desired target data type.
For example, if you want to allow implicit conversion to an int, you would define
the conversion operator as follows:
operator i n t ( ) {
/ / D e f i n e custom c o n v e r s i o n l o g i c
}

3. Implement the Custom Conversion Logic: Inside the conversion operator function,
implement the custom logic to convert an object of your class to the target data
type. Ensure that the conversion makes sense in the context of your class.

4. Return the Result: Return the value of the target data type after the conversion

5. Example - Overloading Conversion to int: Here’s an example of overloading the type


conversion operator to allow objects of a Fraction class to be implicitly converted
to int by returning the truncated integer part of the fraction
class Fraction {
private :
i n t numerator ;
i n t denominator ;

public :
F r a c t i o n ( i n t num, i n t denom ) : numerator (num ) , denominator ( denom ) { }

/ / Overload the conversion operator to i n t


operator i n t ( ) {
return numerator / denominator ;
}
};

In this example, when an object of the Fraction class is used in a context that
expects an int, the conversion operator is automatically invoked to return the
integer part of the fraction.

6. Usage Example:
64 L E C T URE 5: OPERATOR OVERLOADING

Fraction fraction (5 , 2 ) ;
int intValue = fraction ; / / Implicit conversion to int
/ / i n t V a l u e i s now 2

7. Explicit Conversion: In addition to implicit conversions, you can also provide


explicit conversions by defining a constructor or a member function that performs
the conversion when explicitly called by the user.

8. Be Careful: When overloading type conversion operators, be mindful of potential


loss of data or precision and ensure that the conversion logic is well-defined and
safe.

Common Pitfalls and Best Practices


When working with operator overloading in C++, including overloading unary, binary,
and comparison operators, it’s essential to follow best practices to ensure code correct-
ness, maintainability, and readability. Here are some common pitfalls to avoid and best
practices to follow:
Common Pitfalls:

1. Inconsistent Behavior: Ensure that the overloaded operators exhibit consistent


behavior and maintain the expected properties of the operators. For example, if
you overload the == operator, the != operator should be overloaded accordingly to
maintain symmetry.

2. Changing Object State: Be cautious when overloading binary operators to modify


the internal state of the objects involved in the operation. Modifying object state
without clear documentation can lead to unexpected behavior.

3. Ambiguity: Avoid creating ambiguous situations where the compiler cannot deter-
mine which operator to use. This can occur when overloading multiple operators
with similar parameter types.

4. Inefficient Implementations: Be mindful of efficiency when overloading operators.


Inefficient implementations can lead to performance bottlenecks, especially in
critical sections of code.

5. Overloading Too Many Operators: Overloading many operators for a class can
lead to code that is difficult to understand and maintain. Focus on overloading
operators that make the most sense for your class’s purpose.

6. Incorrect Return Types: Ensure that the return type of the overloaded operator
matches the expected type based on the operator’s semantics.

7. Incomplete Overloading: Overload all relevant operators together. For example, if


you overload +, overload -, *, and / as well to maintain consistency.

Best Practices:
1. Follow Standard Semantics: Overloaded operators should maintain the standard
semantics of the operator as closely as possible. Users of your class should not be
surprised by unexpected behavior.

2. Use Comments and Documentation: Clearly document the behavior of overloaded


operators in your class documentation. Explain any non-standard behavior or
edge cases.

3. Overload with Purpose: Overload operators with a clear purpose that aligns with
the class’s intended use. Avoid overloading operators just for the sake of doing so.

4. Keep Operator Overloads Simple: Overloaded operators should have concise and
readable implementations. If the logic is complex, consider moving it to a separate
function and calling that function from the operator overload.

5. Use const Correctness: Whenever possible, use const correctly to indicate that an
operator overload does not modify the state of the object.

6. Test Thoroughly: Write comprehensive unit tests to ensure that overloaded opera-
tors work as expected in various scenarios.

7. Avoid Hidden Conversions: Be cautious when overloading operators that can lead
to hidden type conversions, as these can lead to unexpected behavior.

8. Consider Overload as Member or Non-Member: Depending on your class’s design,


you can overload operators as member functions or non-member functions. Mem-
ber functions provide access to private members, while non-member functions
offer more flexibility.

9. Implement Related Operators Together: When overloading operators for a class,


implement related operators together to maintain a coherent and consistent inter-
face.

10. Use Existing Conventions: Follow established naming conventions for operator
overloads to make your code more readable and maintainable.

By adhering to these best practices and avoiding common pitfalls, you can effectively
and safely overload operators in your C++ classes, enhancing code clarity and user
experience.

Summary
Operator overloading in C++ is a powerful feature that allows you to redefine the behavior
of standard C++ operators for user-defined types, such as classes and structures. Here’s
a summary of key points regarding operator overloading:

• Purpose: Operator overloading enables you to provide custom functionality for


operators when applied to objects of your classes. This makes your code more
intuitive and expressive.
66 L E C T URE 5: OPERATOR OVERLOADING

• Operators That Can Be Overloaded: Common operators that can be overloaded in-
clude arithmetic operators (+, -, *, /), comparison operators (==, !=, <, >,
<=, >=), logical operators (&&, ||, !), and many others.

• Syntax: To overload an operator, you define a special member function or a non-


member function with a specific name that corresponds to the operator you want
to overload.

• Consistency: Maintain consistency when overloading operators. For example, if


you overload == for equality, also overload != for inequality.

• Best Practices: Follow best practices, including clear documentation, efficient


implementations, and adherence to standard semantics, to ensure readable and
maintainable code.

• Use Cases: Operator overloading is commonly used to make user-defined classes


work seamlessly with standard operators, such as overloading + for custom numeric
types or == for custom comparison.

• Type Conversion: You can use operator overloading to define custom type conver-
sions between your class and other data types.

• Testing: Thoroughly test overloaded operators to ensure they behave as expected


in various scenarios.

• Hidden Conversions: Be cautious about potential hidden type conversions when


overloading operators, as these can lead to unexpected behavior.

• Member vs. Non-Member Overloading: Choose whether to overload operators as


member functions or non-member functions based on the design and accessibility
of your class.

• Code Clarity: Properly overloaded operators can enhance code clarity by allowing
you to write code that resembles natural mathematical or logical expressions.

• Considerations: Keep in mind the implications of overloading operators, as misuse


can lead to code that is hard to read, debug, and maintain.

Practice Lab
1. Exercise 1: Overloading the Arithmetic Operators In this exercise, you will create a
Complex class to represent complex numbers and overload arithmetic operators
(+, -, *, /) for it. Implement the following:
Define the Complex class with real and imaginary parts. Overload the arithmetic
operators to perform complex number operations. Implement a display() method
to print complex numbers.
2. Exercise 2: Overloading the Equality and Inequality Operators
In this exercise, you will create a Person class to represent individuals and overload
the equality (==) and inequality (!=) operators for it. Implement the following:
Define the Person class with name and age attributes. Overload the equality and
inequality operators to compare Person objects based on their attributes.

3. Exercise 3: Overloading the Index Operator In this exercise, you will create a Matrix
class to represent 2D matrices and overload the index operator ([]) to access matrix
elements. Implement the following:
Define the Matrix class with a 2D array. Overload the index operator to access
elements using row and column indices.

4. Exercise 4: Overloading the Stream Insertion Operator


n this exercise, you will create a Student class to represent students’ information
and overload the stream insertion operator («) to display student details. Imple-
ment the following:
Define the Student class with attributes like name, roll number, and GPA. Overload
the stream insertion operator to display student details in a user-friendly format.

5. Exercise 5: Overloading the Function Call Operator


In this exercise, you will create a Calculator class to perform mathematical opera-
tions and overload the function call operator () for it. Implement the following:
Define the Calculator class with methods for addition, subtraction, multiplication,
and division. Overload the function call operator to perform custom mathematical
operations.

6. Exercise 6: Overloading the Type Conversion Operator


In this exercise, you will create a Temperature class to represent temperatures
in Fahrenheit and Celsius and overload the type conversion operator to convert
temperatures between the two scales. Implement the following:
Define the Temperature class with attributes for Fahrenheit and Celsius. Overload
the type conversion operator to allow conversions between Fahrenheit and Celsius.

7. Exercise 7: Complex Scenario


Create a complex scenario where you define a custom class, overload multiple
operators, and use them in a meaningful way. For example, you could create a
class for a banking system and overload operators to handle account transactions.

Questions
1. What is operator overloading in C++? Provide a brief explanation.

2. Why is operator overloading useful in C++? Give an example scenario where oper-
ator overloading can improve code readability.
68 L E C T URE 5: OPERATOR OVERLOADING

3. What are the limitations or restrictions when it comes to operator overloading in


C++?

4. Create a Complex class to represent complex numbers (with real and imaginary
parts). Overload the addition operator (+) to perform complex number addition.

5. In the Complex class, overload the subtraction operator (-) to perform complex
number subtraction.

6. Define a Date class to represent dates with day, month, and year. Overload the
equality (==) operator to compare two Date objects for equality.

7. In the Date class, overload the less-than operator (<) to compare two Date objects
to determine which date comes first.

8. Create a Book class to represent books with attributes like title, author, and ISBN.
Overload the stream insertion operator («) to display book details.

9. Write a code snippet that demonstrates the use of the overloaded stream insertion
operator to display book details.

10. Design a Matrix class to represent 2D matrices with a dynamic array. Overload the
index operator ([]) to access matrix elements.

11. Provide an example of how to access an element in a Matrix object using the
overloaded index operator.

12. Define a Counter class that keeps track of a count value. Overload the function
call operator () to increment the count value by a specified amount.

13. Write a code snippet that demonstrates the use of the overloaded function call
operator to increment the count value.

14. Create a Temperature class to represent temperatures in Fahrenheit. Overload the


type conversion operator to convert temperatures to Celsius.

15. Provide an example where an object of the Temperature class is implicitly con-
verted to Celsius.

16. Design a class representing a 2D vector and overload the multiplication operator
(*) to implement scalar multiplication of vectors.

17. Create a class representing a complex number and overload the division operator
(/) to perform complex number division.

18. List three best practices to follow when overloading operators in C++.

19. Why is it essential to maintain consistency when overloading operators? Provide


an example scenario.
Lecture 6: Writing Big Programs

Header and Implementation Files


Header and implementation files are essential components in C++ programming for
organizing and structuring your code. They help separate the declaration (interface) of
classes, functions, and variables from their actual implementation, promoting modular-
ity, reusability, and maintainability of code. Let’s explore header and implementation
files in C++:

Header Files (.h or .hpp)


Header files contain the declarations (signatures) of classes, functions, and variables
that are meant to be used by other parts of your program, including other source files.
Here are some key points about header files:

• Purpose: Header files serve as the interface to your code. They provide a way for
other parts of your program to access and use the functionality you’ve defined.

• Contents: Typically, header files include class declarations, function prototypes,


constant definitions, and type aliases. They should not contain the actual imple-
mentation code.

• Extensions: Header files often have extensions like .h or .hpp. The choice of exten-
sion is not standardized, so you can use what you prefer.

• Include Guards: To prevent multiple inclusions of the same header file, you should
use include guards. These are preprocessor directives that ensure the header is
included only once in a source file. For example:
# i f n d e f MYHEADER_H
# d e f i n e MYHEADER_H

// . . . Contents of the header f i l e ...

# e n d i f / / MYHEADER_H

Implementation Files (.cpp)


Implementation files contain the actual code that defines the functionality declared in
header files. Here are some key points about implementation files:
70 L E C T URE 6: WRITING BIG PROGRAMS

• Purpose: Implementation files provide the details of how functions and classes
work. They contain the executable code that implements the behavior declared in
the header files.
• Contents: Implementation files include the function definitions, method imple-
mentations, variable initializations, and any other code that isn’t part of the inter-
face.
• Extensions: Implementation files often use the .cpp extension, although it’s not
strictly required.

Example
Let’s say you have a class called Calculator that you want to use in your program. You
would typically organize your code as follows:
Calculator.h (Header File):
# i f n d e f CALCULATOR_H
# d e f i n e CALCULATOR_H

class Calculator {
public :
Calculator ( ) ; / / Constructor
i n t add ( i n t a , i n t b ) ;
int subtract ( int a , int b ) ;
int multiply ( int a , int b ) ;
double d i v i d e ( i n t a , i n t b ) ;
};

# e n d i f / / CALCULATOR_H
Calculator.cpp (Implementation File):
# include " C a l c u l a t o r . h"

Calculator : : Calculator () {
/ / C o n s t r u c t o r implementation
}

i n t C a l c u l a t o r : : add ( i n t a , i n t b ) {
return a + b ;
}

int Calculator : : subtract ( int a , int b) {


return a − b ;
}

int Calculator : : multiply ( int a , int b) {


return a * b ;
}

double C a l c u l a t o r : : d i v i d e ( i n t a , i n t b ) {
i f ( b == 0 ) {
throw " D i v i s i o n by z e r o ! " ;
}
return s t a t i c _ c a s t <double > ( a ) / b ;
}

Main.cpp (Source File):


# include <iostream >
# include " C a l c u l a t o r . h"

i n t main ( ) {
Calculator calc ;
i n t r e s u l t = c a l c . add ( 5 , 3 ) ;
s t d : : c o u t << " 5 + 3 = " << r e s u l t << s t d : : e n d l ;

return 0 ;
}

In this example:

• Calculator.h defines the class Calculator and its member functions’ declarations.

• Calculator.cpp contains the implementations of the Calculator class member func-


tions.

• Main.cpp uses the Calculator class by including its header file and creating an
instance of the class.

Make and CMake


Make
A Makefile is a special text file used in Unix-like operating systems (including Linux)
and other development environments to automate the build process of a project. It
specifies a set of rules and dependencies for compiling source code files, linking them
into executables, and performing other project-related tasks. Makefiles are often used
in C and C++ projects, but they can be applied to other programming languages and
projects as well.
Here’s a basic overview of how a Makefile works and how to create one:
Makefile Structure
A Makefile typically consists of a set of rules, each defined in the following format:
t a r g e t : dependencies
commands

1. target: The target is the name of the file or action you want to build. It could be an
executable file, an object file, or even just a label for a particular task.

2. dependencies: Dependencies are the files or prerequisites required to build the


target. If any of the dependencies change, the target is rebuilt.

3. commands: Commands are the shell commands or instructions to execute in order


to build the target from its dependencies.
72 L E C T URE 6: WRITING BIG PROGRAMS

Example Makefile
Here’s a simple example of a Makefile for a C++ program with two source files, main.cpp
and utils.cpp, and one header file, utils.h:
CXX = g++
CXXFLAGS = −s t d =c + + 1 1 −W a l l
SRCDIR = s r c
BINDIR = bin

TARGET = myapp
SOURCES = $ ( SRCDIR ) / main . cpp $ ( SRCDIR ) / u t i l s . cpp
OBJECTS = $ ( SOURCES : $ ( SRCDIR ) / % . cpp=$ ( BINDIR ) / % . o )

$ ( BINDIR ) / $ ( TARGET ) : $ ( OBJECTS )


$ ( CXX ) $ ( CXXFLAGS ) −o $@ $ ( OBJECTS )

$ ( BINDIR ) / % . o : $ ( SRCDIR ) / % . cpp


$ ( CXX ) $ ( CXXFLAGS ) −c −o $@ $ <

clean :
rm −f $ ( BINDIR ) / * . o $ ( BINDIR ) / $ ( TARGET )

In this example:

• CXX specifies the C++ compiler to use (in this case, g++).

• CXXFLAGS specifies compiler flags, such as the C++ version and warning options.

• SRCDIR and BINDIR define source code and binary directories, respectively.

• TARGET specifies the name of the final executable.

• SOURCES lists the source files.

• OBJECTS lists the corresponding object files.

• The first rule ((BIN DIR)/(TARGET)) specifies how to build the final executable
from object files.

• The second rule specifies how to compile each source file into an object file.

• The clean rule allows you to clean up generated files.

Using the Makefile


To build your project using the Makefile, navigate to the directory containing the Makefile
and your source code files, and then run make:
make

This will compile your program and create the executable. You can also use specific
targets, such as clean, to perform other tasks:
make c l e a n
CMake
CMake is an open-source cross-platform build system and project management tool
widely used in the C and C++ programming communities (although it supports many
other languages as well). CMake simplifies the process of configuring, building, testing,
and packaging software projects. It generates build files and scripts (e.g., Makefiles,
Visual Studio project files) for various build tools and platforms, allowing developers to
work on their projects across different operating systems with ease.
Here are the key features and concepts of CMake:

• Cross-Platform Build System: CMake is designed to work across different platforms,


including Unix/Linux, macOS, and Windows. It generates platform-specific build
files tailored to the developer’s system.

• CMakeLists.txt: The core of CMake-based projects is the CMakeLists.txt file. This


text file describes project details, dependencies, source files, and build instructions.
It’s used to configure the build process.

• Out-of-Source Builds: CMake encourages out-of-source builds, where build arti-


facts (e.g., object files, executables) are generated in a separate directory from the
source code. This keeps the source directory clean and allows for multiple build
configurations without clutter.

• Multiple Generators: CMake can generate build files for various build systems,
including Makefiles, Ninja, Visual Studio, and Xcode. Developers can choose the
generator that best suits their development environment.

• Dependency Management: CMake provides mechanisms to find and configure de-


pendencies, including libraries, header files, and external projects. This simplifies
the process of integrating third-party code.

• Configurable Builds: CMake allows developers to configure build options and


features through command-line arguments or a graphical user interface (CMake
GUI). This makes it easy to adapt the build process to different requirements.

• Testing: CMake includes built-in support for testing. It allows you to define test
cases, run tests, and generate test reports as part of the build process.

• Packaging: CMake supports packaging your project for distribution. It can generate
installer packages, compressed archives, or other distribution formats.

• Integration with IDEs: Many integrated development environments (IDEs), includ-


ing Visual Studio, CLion, and Qt Creator, have built-in support for CMake. This
simplifies project setup and development.

• Extensibility: CMake can be extended with custom CMake modules and scripts to
support specific project requirements.

Here’s a simplified example of a CMakeLists.txt file for a C++ project:


74 L E C T URE 6: WRITING BIG PROGRAMS

cmake_minimum_required ( VERSION 3 . 1 0 )
p r o j e c t ( MyProject )

s e t ( CMAKE_CXX_STANDARD 1 7 )

# Define source f i l e s
s e t ( SOURCES
main . cpp
u t i l s . cpp
)

# Define the executable


a d d _ e x e c u t a b l e ( myapp $ { SOURCES } )

# S p e c i f y dependencies ( e . g . , l i b r a r i e s )
t a r g e t _ l i n k _ l i b r a r i e s ( myapp
$ { DEPENDENCY_LIBRARIES }
)

# C o n f i g u r a t i o n o p t i o n s ( e . g . , compile f l a g s )
t a r g e t _ c o m p i l e _ o p t i o n s ( myapp PRIVATE
−W a l l
)

To build a project using CMake, you would typically run the following commands in
your project directory:
mkdir b u i l d # C r e a t e a b u i l d d i r e c t o r y ( out−of−s o u r c e b u i l d )
cd b u i l d
cmake . . # Generate build f i l e s
make # B u i l d t h e p r o j e c t ( or use t h e a p p r o p r i a t e b u i l d t o o l )
Lecture 7: Design Patterns

Design patterns are reusable solutions to common software design problems that occur
during the development of software applications. They provide best practices, guidelines,
and templates for structuring code and solving recurring design challenges. Design pat-
terns help improve code readability, maintainability, and extensibility while promoting
good software engineering practices. Here are some important design patterns:

Creational Patterns
Singleton Pattern
Ensures that a class has only one instance and provides a global point of access to that
instance.
The Singleton Pattern is a creational design pattern that ensures a class has only one
instance and provides a global point of access to that instance. It is one of the simplest
design patterns but can be quite powerful in scenarios where exactly one object should
coordinate actions across the system, such as a configuration manager, a logging service,
or a database connection pool.
Key characteristics of the Singleton Pattern:

• Single Instance: A Singleton class should have only one instance, which is created
lazily or at the time of the first request.

• Global Access: It provides a way to access the single instance from any part of the
application, typically through a static method.

• Initialization Control: The pattern allows for careful control over how and when
the singleton instance is created, ensuring that it meets specific requirements.

Here’s a typical implementation of the Singleton Pattern in C++:


class Singleton {
private :
/ / Private constructor to prevent external i n s t a n t i a t i o n
Singleton ( ) { }

/ / Private s t a t i c instance variable


static Singleton * instance ;

public :
/ / S t a t i c method t o a c c e s s t h e s i n g l e t o n i n s t a n c e
static Singleton * getInstance ( ) {
i f ( ! instance ) {
i n s t a n c e = new S i n g l e t o n ( ) ;
76 L E C T URE 7: DESIGN PAT TERNS

}
return i n s t a n c e ;
}

/ / O t h e r member methods and data . . .


};

/ / I n i t i a l i z e the s t a t i c instance variable


Singleton * Singleton : : instance = nullptr ;

In this example:

1. The constructor of the Singleton class is private, preventing external instantiation.

2. The getInstance method is used to get the single instance of the Singleton class. If
an instance does not exist, it is created lazily.

3. The static instance variable holds the reference to the single instance, and it is
initialized as nullptr.

Here’s how you can use the Singleton:


i n t main ( ) {
Singleton * singleton1 = Singleton : : getInstance ( ) ;
Singleton * singleton2 = Singleton : : getInstance ( ) ;

/ / Both p o i n t e r s p o i n t t o t h e same i n s t a n c e
i f ( s i n g l e t o n 1 == s i n g l e t o n 2 ) {
s t d : : c o u t << " S i n g l e t o n i n s t a n c e i s t h e same . " << s t d : : e n d l ;
} else {
s t d : : c o u t << " S i n g l e t o n i n s t a n c e i s d i f f e r e n t . " << s t d : : e n d l ;
}

/ / Clean up ( o p t i o n a l )
delete singleton1 ;
return 0 ;
}

It’s important to note that the Singleton Pattern can be implemented in various ways,
and the example provided above is a simple one. Depending on your requirements
and the complexity of your application, you may need to consider thread safety, lazy
initialization, or other aspects when implementing a Singleton. Additionally, some
modern C++ standards and libraries provide alternative approaches to ensure thread
safety and lazy initialization.

Factory Method Pattern


Defines an interface for creating an object, but leaves the choice of its type to subclasses,
creating a framework for object creation.
The Factory Method Pattern is a creational design pattern that provides an interface
for creating objects but allows subclasses to alter the type of objects that will be created.
It is particularly useful when you have a class hierarchy of related classes, and you want
to delegate the responsibility of object creation to subclasses, allowing for flexibility and
customization.
Key characteristics of the Factory Method Pattern:

1. Abstract Creator: The pattern defines an abstract class (or interface) known as the
"Creator" that declares a factory method. This method is responsible for creating
objects of a related "Product" class.

2. Concrete Creators: Concrete subclasses of the Creator implement the factory


method to produce specific instances of the Product class.

3. Product Class: The Product class is the common interface for all objects created
by the factory method. It defines the properties and behaviors that all concrete
product classes must implement.
Here’s a typical implementation of the Factory Method Pattern in C++:
/ / Product i n t e r f a c e
c l a s s Product {
public :
v i r t u a l void doSomething ( ) = 0 ;
v i r t u a l ~ Product ( ) { }
};

/ / Concrete Product A
c l a s s ConcreteProductA : public Product {
public :
void doSomething ( ) o v e r r i d e {
s t d : : c o u t << " P r o d u c t A does something . " << s t d : : e n d l ;
}
};

/ / Concrete Product B
c l a s s ConcreteProductB : public Product {
public :
void doSomething ( ) o v e r r i d e {
s t d : : c o u t << " P r o d u c t B does something . " << s t d : : e n d l ;
}
};

/ / Creator abstract c l a s s
class Creator {
public :
v i r t u a l Product * factoryMethod ( ) = 0 ;
};

/ / Concrete Creator A
c l a s s ConcreteCreatorA : public Creator {
public :
Product * factoryMethod ( ) o v e r r i d e {
return new C o n c r e t e P r o d u c t A ( ) ;
}
};
78 L E C T URE 7: DESIGN PAT TERNS

/ / Concrete Creator B
c l a s s ConcreteCreatorB : public Creator {
public :
Product * factoryMethod ( ) o v e r r i d e {
return new C o n c r e t e P r o d u c t B ( ) ;
}
};

In this example:

1. The Product interface defines the common interface for all products created by
the factory method.

2. Concrete products, such as ConcreteProductA and ConcreteProductB, implement


the Product interface.

3. The Creator abstract class declares the factory method, which returns a Prod-
uct object. Concrete creators, such as ConcreteCreatorA and ConcreteCreatorB,
implement this method to create specific products.

Here’s how you can use the Factory Method Pattern:


i n t main ( ) {
C r e a t o r * c r e a t o r A = new C o n c r e t e C r e a t o r A ( ) ;
P r o d u c t * productA = c r e a t o r A −>f a c t o r y M e t h o d ( ) ;
productA −>doSomething ( ) ;

C r e a t o r * c r e a t o r B = new C o n c r e t e C r e a t o r B ( ) ;
P r o d u c t * productB = c r e a t o r B −>f a c t o r y M e t h o d ( ) ;
productB −>doSomething ( ) ;

delete creatorA ;
delete creatorB ;
delete productA ;
delete productB ;

return 0 ;
}

Abstract Factory Pattern


Provides an interface for creating families of related or dependent objects without speci-
fying their concrete classes.
The Abstract Factory Pattern is a creational design pattern that provides an interface
for creating families of related or dependent objects without specifying their concrete
classes. It allows you to create objects that are part of a common theme or family,
ensuring that the created objects are compatible with each other.
Key characteristics of the Abstract Factory Pattern:

1. Abstract Factory: The pattern defines an abstract factory interface that declares a
set of creation methods for creating related objects, often organized into families.
2. Concrete Factories: Concrete subclasses of the abstract factory implement the
factory interface. Each concrete factory is responsible for creating a specific family
of related objects.
3. Abstract Products: The pattern defines abstract product interfaces for each type of
object that the factories can create. These interfaces declare the common methods
that all concrete products must implement.
4. Concrete Products: Concrete product classes implement the abstract product
interfaces. Each concrete factory creates its own set of concrete products.

Here’s a typical implementation of the Abstract Factory Pattern in C++:


/ / A b s t r a c t Product A
class AbstractProductA {
public :
v i r t u a l void o p e r a t i o n A ( ) = 0 ;
virtual ~AbstractProductA ( ) { }
};

/ / C o n c r e t e P r o d u c t A1
c l a s s ConcreteProductA1 : public AbstractProductA {
public :
void o p e r a t i o n A ( ) o v e r r i d e {
s t d : : c o u t << " P r o d u c t A1 does o p e r a t i o n A . " << s t d : : e n d l ;
}
};

/ / C o n c r e t e P r o d u c t A2
c l a s s ConcreteProductA2 : public AbstractProductA {
public :
void o p e r a t i o n A ( ) o v e r r i d e {
s t d : : c o u t << " P r o d u c t A2 does o p e r a t i o n A . " << s t d : : e n d l ;
}
};

/ / A b s t r a c t Product B
class AbstractProductB {
public :
v i r t u a l void o p e r a t i o n B ( ) = 0 ;
virtual ~AbstractProductB ( ) { }
};

/ / C o n c r e t e P r o d u c t B1
c l a s s ConcreteProductB1 : public AbstractProductB {
public :
void o p e r a t i o n B ( ) o v e r r i d e {
s t d : : c o u t << " P r o d u c t B1 does o p e r a t i o n B . " << s t d : : e n d l ;
}
};

/ / C o n c r e t e P r o d u c t B2
c l a s s ConcreteProductB2 : public AbstractProductB {
public :
void o p e r a t i o n B ( ) o v e r r i d e {
80 L E C T URE 7: DESIGN PAT TERNS

s t d : : c o u t << " P r o d u c t B2 does o p e r a t i o n B . " << s t d : : e n d l ;


}
};

/ / Abstract Factory
class AbstractFactory {
public :
virtual AbstractProductA * createProductA ( ) = 0;
virtual AbstractProductB * createProductB ( ) = 0 ;
virtual ~AbstractFactory () { }
};

/ / Concrete Factory 1
c l a s s ConcreteFactory1 : public A b s t r a c t F a c t o r y {
public :
AbstractProductA * createProductA ( ) override {
return new C o n c r e t e P r o d u c t A 1 ( ) ;
}

AbstractProductB * createProductB ( ) override {


return new C o n c r e t e P r o d u c t B 1 ( ) ;
}
};

/ / Concrete Factory 2
c l a s s ConcreteFactory2 : public A b s t r a c t F a c t o r y {
public :
AbstractProductA * createProductA ( ) override {
return new C o n c r e t e P r o d u c t A 2 ( ) ;
}

AbstractProductB * createProductB ( ) override {


return new C o n c r e t e P r o d u c t B 2 ( ) ;
}
};

In this example:

1. There are two families of products, A and B, each with two concrete implemen-
tations (e.g., ConcreteProductA1, ConcreteProductA2, ConcreteProductB1, Con-
creteProductB2).

2. The AbstractFactory interface declares the creation methods for product A and
product B.

3. Concrete factories (ConcreteFactory1 and ConcreteFactory2) implement the Ab-


stractFactory interface and create products from their respective families.

Here’s how you can use the Abstract Factory Pattern:


i n t main ( ) {
A b s t r a c t F a c t o r y * f a c t o r y 1 = new C o n c r e t e F a c t o r y 1 ( ) ;
A b s t r a c t P r o d u c t A * p r o d u c t A 1 = f a c t o r y 1 −>c r e a t e P r o d u c t A ( ) ;
A b s t r a c t P r o d u c t B * p r o d u c t B 1 = f a c t o r y 1 −>c r e a t e P r o d u c t B ( ) ;
A b s t r a c t F a c t o r y * f a c t o r y 2 = new C o n c r e t e F a c t o r y 2 ( ) ;
A b s t r a c t P r o d u c t A * productA2 = f a c t o r y 2 −>c r e a t e P r o d u c t A ( ) ;
A b s t r a c t P r o d u c t B * productB2 = f a c t o r y 2 −>c r e a t e P r o d u c t B ( ) ;

/ / Use t h e c r e a t e d p r o d u c t s . . .

delete factory1 ;
delete factory2 ;
delete productA1 ;
delete productB1 ;
delete productA2 ;
delete productB2 ;

return 0 ;
}

Builder Pattern
Separates the construction of a complex object from its representation, allowing the
same construction process to create different representations.
The Builder Pattern is a creational design pattern that separates the construction
of a complex object from its representation, allowing the same construction process to
create different representations of the object. It is particularly useful when you need to
create objects with many optional components or configurations.
Key characteristics of the Builder Pattern:

• Director: The pattern includes a director class that orchestrates the construction
process. The director uses a builder interface to construct the object step by step.

• Builder Interface: The builder interface defines a set of methods for building
various parts of the complex object. These methods typically include functions for
setting attributes, adding components, and finalizing the object.

• Concrete Builders: Concrete builder classes implement the builder interface and
provide specific implementations for constructing objects with different configu-
rations.

• Product: The complex object being constructed is referred to as the "product."


The builder constructs and returns the product once it’s complete.

• Client: The client code interacts with the director to initiate the construction
process, specifying which concrete builder to use. The client can also access the
constructed product when the builder is done.

Here’s a typical implementation of the Builder Pattern in C++:


# include <iostream >
# include < s t r i n g >

/ / Product c l a s s
c l a s s Computer {
82 L E C T URE 7: DESIGN PAT TERNS

public :
void setCPU ( const s t d : : s t r i n g & cpu ) {
cpu_ = cpu ;
}

void setRAM ( const s t d : : s t r i n g & ram ) {


ram_ = ram ;
}

void s e t S t o r a g e ( const s t d : : s t r i n g & s t o r a g e ) {


storage_ = storage ;
}

void d i s p l a y I n f o ( ) {
s t d : : c o u t << "CPU : " << cpu_ << s t d : : e n d l ;
s t d : : c o u t << "RAM : " << ram_ << s t d : : e n d l ;
s t d : : c o u t << " S t o r a g e : " << s t o r a g e _ << s t d : : e n d l ;
}

private :
std : : s t r i n g cpu_ ;
std : : s t r i n g ram_ ;
std : : string storage_ ;
};

/ / Builder interface
c l a s s ComputerBuilder {
public :
v i r t u a l void buildCPU ( ) = 0 ;
v i r t u a l void buildRAM ( ) = 0 ;
v i r t u a l void b u i l d S t o r a g e ( ) = 0 ;
v i r t u a l Computer * g e t R e s u l t ( ) = 0 ;
};

/ / C o n c r e t e b u i l d e r f o r a gaming computer
c l a s s GamingComputerBuilder : p u b l i c ComputerBuilder {
public :
void buildCPU ( ) o v e r r i d e {
computer_ −>setCPU ( " High−end Gaming CPU" ) ;
}

void buildRAM ( ) o v e r r i d e {
computer_ −>setRAM ( " 1 6GB Gaming RAM" ) ;
}

void b u i l d S t o r a g e ( ) o v e r r i d e {
computer_ −> s e t S t o r a g e ( " 5 1 2GB SSD" ) ;
}

Computer * g e t R e s u l t ( ) o v e r r i d e {
return computer_ ;
}

GamingComputerBuilder ( ) {
computer_ = new Computer ( ) ;
}

private :
Computer * computer_ ;
};

/ / Director
c l a s s Co mpu te rDi rec tor {
public :
Computer * b u i l d ( ComputerBuilder * b u i l d e r ) {
b u i l d e r −>buildCPU ( ) ;
b u i l d e r −>buildRAM ( ) ;
b u i l d e r −> b u i l d S t o r a g e ( ) ;
return b u i l d e r −> g e t R e s u l t ( ) ;
}
};

i n t main ( ) {
Co mpu te rDi rec tor d i r e c t o r ;
GamingComputerBuilder g a m i n g B u i l d e r ;

Computer * gamingComputer = d i r e c t o r . b u i l d (& g a m i n g B u i l d e r ) ;


gamingComputer−> d i s p l a y I n f o ( ) ;

d e l e t e gamingComputer ;

return 0 ;
}

In this example:

1. We have a Computer class representing the complex object to be built.

2. The ComputerBuilder interface defines methods for building various parts of the
computer.

3. GamingComputerBuilder is a concrete builder that implements the builder inter-


face to create a gaming computer with specific components.

4. The ComputerDirector orchestrates the construction process by using a builder to


create a computer.

5. The client code (in main) interacts with the director to create a gaming computer.

Structural Patterns
Adapter Pattern
Allows the interface of an existing class to be used as another interface.
The Adapter Pattern is a structural design pattern that allows objects with incompati-
ble interfaces to work together. It acts as a bridge between two interfaces, enabling them
to collaborate without changing their source code. The Adapter Pattern is particularly
84 L E C T URE 7: DESIGN PAT TERNS

useful when integrating existing classes or libraries with new code or when dealing with
legacy systems.
Key characteristics of the Adapter Pattern:

• Target: The "target" is the interface that the client code expects to work with. It
defines the operations or methods that the client code uses.

• Adaptee: The "adaptee" is the class or component that has a different interface
from the target. It is the class that needs to be integrated or adapted to work with
the client code.

• Adapter: The "adapter" is a class that implements the target interface while con-
taining an instance of the adaptee. It acts as an intermediary, translating requests
from the client code into calls to the adaptee’s methods.

Here’s a typical implementation of the Adapter Pattern in C++:


# include <iostream >

/ / Target i n t e r f a c e
class Target {
public :
v i r t u a l void r e q u e s t ( ) = 0 ;
};

/ / Adaptee
c l a s s Adaptee {
public :
void s p e c i f i c R e q u e s t ( ) {
s t d : : c o u t << " Adaptee ’ s s p e c i f i c r e q u e s t . " << s t d : : e n d l ;
}
};

/ / Adapter
c l a s s Adapter : public Target {
private :
Adaptee * a d a p t e e _ ;

public :
A d a p t e r ( Adaptee * a da pt ee ) : a d a p t e e _ ( a da pt ee ) { }

void r e q u e s t ( ) o v e r r i d e {
s t d : : c o u t << " A d a p t e r i s a d a p t i n g t h e r e q u e s t . " << s t d : : e n d l ;
adaptee_ −> s p e c i f i c R e q u e s t ( ) ;
}
};

i n t main ( ) {
Adaptee * a da pt ee = new Adaptee ( ) ;
T a r g e t * a d a p t e r = new A d a p t e r ( a da pt ee ) ;

a d a p t e r −>r e q u e s t ( ) ;

d e l e t e ad a pt ee ;
delete adapter ;

return 0 ;
}

In this example:

• Target is the interface that the client code expects, and it defines the request
method.

• Adaptee is the class with a different interface that needs to be integrated. It has a
specificRequest method.

• Adapter is a class that implements the Target interface and contains an instance
of Adaptee. It adapts the request method to call specificRequest internally.

• In the main function, an Adaptee object is created, and then an Adapter object
is created, passing the Adaptee as a parameter. The Adapter is used to make the
request.

Decorator Pattern
Attaches additional responsibilities to an object dynamically, providing a flexible alter-
native to subclassing.
The Decorator Pattern is a structural design pattern that allows you to attach addi-
tional responsibilities to an object dynamically. It is used to extend the functionality of
objects in a flexible and reusable way without altering their source code. This pattern is
based on the principle of composition rather than inheritance, which promotes open-
closed principle compliance (classes are open for extension but closed for modification).
Key characteristics of the Decorator Pattern:

• Component: The "component" is an abstract class or interface that defines the


common interface for concrete components and decorators.

• Concrete Component: A "concrete component" is a class that implements the com-


ponent interface. It represents the base object to which additional responsibilities
can be added.

• Decorator: The "decorator" is an abstract class that also implements the compo-
nent interface. It has an association with the component and can add or override
behaviors.

• Concrete Decorators: "Concrete decorators" are classes that extend the decorator
class. They provide specific implementations for adding responsibilities to the
component.

Here’s a typical implementation of the Decorator Pattern in C++:


86 L E C T URE 7: DESIGN PAT TERNS

# include <iostream >

/ / Component i n t e r f a c e
class Coffee {
public :
v i r t u a l s t d : : s t r i n g g e t D e s c r i p t i o n ( ) const = 0 ;
v i r t u a l double c o s t ( ) const = 0 ;
};

/ / C o n c r e t e Component
c l a s s Espresso : public Coffee {
public :
s t d : : s t r i n g g e t D e s c r i p t i o n ( ) const o v e r r i d e {
return " E s p r e s s o " ;
}

double c o s t ( ) const o v e r r i d e {
return 1 . 9 9 ;
}
};

/ / Decorator abstract c l a s s
c l a s s CoffeeDecorator : public Coffee {
protected :
Coffee * coffee ;

public :
CoffeeDecorator ( Coffee * coffee ) : coffee ( coffee ) { }

s t d : : s t r i n g g e t D e s c r i p t i o n ( ) const o v e r r i d e {
return c o f f e e −> g e t D e s c r i p t i o n ( ) ;
}

double c o s t ( ) const o v e r r i d e {
return c o f f e e −> c o s t ( ) ;
}
};

/ / Concrete Decorator 1
c l a s s MilkDecorator : public CoffeeDecorator {
public :
MilkDecorator ( Coffee * c o f f e e ) : CoffeeDecorator ( c o f f e e ) { }

s t d : : s t r i n g g e t D e s c r i p t i o n ( ) const o v e r r i d e {
return c o f f e e −> g e t D e s c r i p t i o n ( ) + " , Milk " ;
}

double c o s t ( ) const o v e r r i d e {
return c o f f e e −> c o s t ( ) + 0 . 5 0 ;
}
};

/ / Concrete Decorator 2
c l a s s SugarDecorator : public CoffeeDecorator {
public :
SugarDecorator ( Coffee * c o f f e e ) : CoffeeDecorator ( c o f f e e ) { }

s t d : : s t r i n g g e t D e s c r i p t i o n ( ) const o v e r r i d e {
return c o f f e e −> g e t D e s c r i p t i o n ( ) + " , Sugar " ;
}

double c o s t ( ) const o v e r r i d e {
return c o f f e e −> c o s t ( ) + 0 . 2 5 ;
}
};

i n t main ( ) {
C o f f e e * e s p r e s s o = new E s p r e s s o ( ) ;
s t d : : c o u t << " Order : " << e s p r e s s o −> g e t D e s c r i p t i o n ( )
<< " , Cost : $ " << e s p r e s s o −> c o s t ( ) << s t d : : e n d l ;

C o f f e e * m i l k E s p r e s s o = new M i l k D e c o r a t o r ( e s p r e s s o ) ;
s t d : : c o u t << " Order : " << m i l k E s p r e s s o −> g e t D e s c r i p t i o n ( )
<< " , Cost : $ " << m i l k E s p r e s s o −> c o s t ( ) << s t d : : e n d l ;

C o f f e e * s u g a r M i l k E s p r e s s o = new S u g a r D e c o r a t o r ( m i l k E s p r e s s o ) ;
s t d : : c o u t << " Order : " << s u g a r M i l k E s p r e s s o −> g e t D e s c r i p t i o n ( )
<< " , Cost : $ " << s u g a r M i l k E s p r e s s o −> c o s t ( ) << s t d : : e n d l ;

delete espresso ;
delete milkEspresso ;
delete sugarMilkEspresso ;

return 0 ;
}

In this example:

• Coffee is the component interface with getDescription and cost methods.

• Espresso is a concrete component that implements the Coffee interface.

• CoffeeDecorator is an abstract decorator class that also implements the Coffee


interface and has an association with a Coffee object.

• MilkDecorator and SugarDecorator are concrete decorators that extend CoffeeDec-


orator. They add milk and sugar to the coffee description and cost.

In the main function, decorators are used to enhance an Espresso object with addi-
tional responsibilities.

Composite Pattern
Composes objects into tree structures to represent part-whole hierarchies. Clients can
treat individual objects and compositions of objects uniformly.
The Composite Pattern is a structural design pattern that allows you to compose
objects into tree structures to represent part-whole hierarchies. It lets clients treat
individual objects and compositions of objects uniformly. The Composite Pattern is
88 L E C T URE 7: DESIGN PAT TERNS

particularly useful when you need to work with complex structures that have a recursive
or hierarchical nature.
Key characteristics of the Composite Pattern:

1. Component: The "component" is an abstract class or interface that defines the


common interface for both leaf and composite objects. It usually includes methods
for operations that can be performed on both types.

2. Leaf: A "leaf" is a concrete class that implements the component interface. Leaf
objects represent the individual objects that make up the composite.

3. Composite: A "composite" is a concrete class that also implements the compo-


nent interface but can contain child components. Composite objects represent
containers that can hold both leaf and other composite objects.

Here’s a typical implementation of the Composite Pattern in C++:


# include <iostream >
# include < vector >

/ / Component i n t e r f a c e
c l a s s Component {
public :
v i r t u a l void o p e r a t i o n ( ) const = 0 ;
v i r t u a l ~Component ( ) { }
};

/ / Leaf
c l a s s L e a f : p u b l i c Component {
public :
void o p e r a t i o n ( ) const o v e r r i d e {
s t d : : c o u t << " L e a f does something . " << s t d : : e n d l ;
}
};

/ / Composite
c l a s s Composite : p u b l i c Component {
private :
s t d : : v e c t o r <Component * > c h i l d r e n ;

public :
void add ( Component * component ) {
c h i l d r e n . push_back ( component ) ;
}

void o p e r a t i o n ( ) const o v e r r i d e {
s t d : : c o u t << " Composite does something . " << s t d : : e n d l ;
f o r ( const auto& c h i l d : c h i l d r e n ) {
c h i l d −>o p e r a t i o n ( ) ;
}
}

~ Composite ( ) {
f o r ( auto& c h i l d : c h i l d r e n ) {
delete child ;
}
}
};

i n t main ( ) {
L e a f * l e a f 1 = new L e a f ( ) ;
L e a f * l e a f 2 = new L e a f ( ) ;
Composite * composite = new Composite ( ) ;

composite −>add ( l e a f 1 ) ;
composite −>add ( l e a f 2 ) ;

composite −>o p e r a t i o n ( ) ;

d e l e t e composite ;

return 0 ;
}

In this example:

1. Component is the component interface with an operation method.

2. Leaf is a concrete leaf class that implements the Component interface. It represents
individual objects.

3. Composite is a concrete composite class that also implements the Component


interface and contains a collection of child components. It can represent complex
structures by recursively calling operation on its children.

4. In the main function, leaf and composite objects are created and organized into
a hierarchical structure. The composite’s operation method traverses the entire
hierarchy, executing the operation method of each component.

Proxy Pattern
Provides a surrogate or placeholder for another object to control access to it.
The Proxy Pattern is a structural design pattern that provides a surrogate or place-
holder for another object to control access to it. It allows you to add an extra layer of
control over the access to an object, typically to implement lazy initialization, access
control, logging, monitoring, or other functionality.
Key characteristics of the Proxy Pattern:

1. Real Subject: The "real subject" is the actual object that the proxy represents. It is
the object to which access is being controlled or augmented.

2. Proxy: The "proxy" is an interface or class that provides the same interface as the
real subject. It acts as an intermediary between the client and the real subject,
controlling access and adding behavior as needed.
90 L E C T URE 7: DESIGN PAT TERNS

3. Lazy Initialization: Proxies are often used for lazy initialization, meaning that the
real subject is only created or initialized when it is actually needed, not when the
proxy is created.

4. Access Control: Proxies can enforce access control rules, such as checking permis-
sions or authentication, before allowing the client to access the real subject.

5. Monitoring and Logging: Proxies can be used to monitor and log the interactions
between the client and the real subject for debugging or auditing purposes.

Here’s a typical implementation of the Proxy Pattern in C++:


# include <iostream >

/ / Subject interface
class Subject {
public :
v i r t u a l void r e q u e s t ( ) = 0 ;
virtual ~Subject ( ) { }
};

/ / Real Subject
c l a s s RealSubject : public Subject {
public :
void r e q u e s t ( ) o v e r r i d e {
s t d : : c o u t << " R e a l S u b j e c t h a n d l e s t h e r e q u e s t . " << s t d : : e n d l ;
}
};

/ / Proxy
c l a s s P ro x y : p u b l i c S u b j e c t {
private :
RealSubject * realSubject ;

public :
P ro x y ( ) : r e a l S u b j e c t ( n u l l p t r ) { }

void r e q u e s t ( ) o v e r r i d e {
/ / Lazy i n i t i a l i z a t i o n : C r e a t e t h e R e a l S u b j e c t o n l y when needed
if ( ! realSubject ) {
r e a l S u b j e c t = new R e a l S u b j e c t ( ) ;
}

/ / A d d i t i o n a l b e h a v i o r , i f n e c e s s a r y , can be added h e r e
s t d : : c o u t << " P r ox y h a n d l e s t h e r e q u e s t . " << s t d : : e n d l ;

/ / Delegate the request to the RealSubject


r e a l S u b j e c t −>r e q u e s t ( ) ;
}

~ P r ox y ( ) {
delete realSubject ;
}
};
i n t main ( ) {
P ro x y proxy ;

/ / The c l i e n t i n t e r a c t s with t h e Proxy , which c o n t r o l s a c c e s s t o t h e R e a l S u b j e c t


proxy . r e q u e s t ( ) ;

return 0 ;
}

In this example:
Subject is the subject interface that both RealSubject and Proxy implement. It defines
the request method.
RealSubject is the actual object that the proxy represents. It implements the Subject
interface and performs the real operation when requested.
Proxy is a proxy class that also implements the Subject interface. It contains an
instance of RealSubject and controls access to it. It performs lazy initialization and
additional behavior (printing a message) before delegating the request to the RealSubject.
In the main function, the client interacts with the Proxy object, which in turn controls
access to the RealSubject.

Behavioral Patterns
Observer Pattern
Defines a one-to-many dependency between objects so that when one object changes
state, all its dependents are notified and updated automatically.
The Observer Pattern is a behavioral design pattern that defines a one-to-many
dependency between objects. It allows multiple objects (observers) to be notified and
updated automatically when the state of a subject (the object being observed) changes.
This pattern is commonly used in software architectures where changes to one object
need to be reflected in other objects without them being tightly coupled.
Key characteristics of the Observer Pattern:

1. Subject: The "subject" (also known as the "observable") is the object being ob-
served. It maintains a list of observers and notifies them when its state changes.

2. Observer: An "observer" is an object that wants to be notified when the subject’s


state changes. Observers register themselves with the subject and implement an
update method to respond to notifications.

3. Registration and Deregistration: Observers can dynamically register and deregister


themselves with a subject to start or stop receiving notifications.

4. Notification: When the subject’s state changes, it notifies all registered observers
by calling their update methods.

5. Loose Coupling: The Observer Pattern promotes loose coupling between subjects
and observers. Subjects don’t need to know the specific classes of their observers,
and observers are not tightly coupled to the subject they observe.
92 L E C T URE 7: DESIGN PAT TERNS

Here’s a typical implementation of the Observer Pattern in C++:


# include <iostream >
# include < vector >

/ / Observer i n t e r f a c e
c l a s s Observer {
public :
v i r t u a l void update ( const s t d : : s t r i n g & message ) = 0 ;
};

/ / Concrete Observer
c l a s s ConcreteObserver : public Observer {
private :
s t d : : s t r i n g name ;

public :
C o n c r e t e O b s e r v e r ( const s t d : : s t r i n g & _name ) : name ( _name ) { }

void update ( const s t d : : s t r i n g & message ) o v e r r i d e {


s t d : : c o u t << " O b s e r v e r " << name << " r e c e i v e d message : " << message << s t d : : e n
}
};

/ / Subject interface
class Subject {
private :
std : : vector < Observer * > observers ;
std : : s t r i n g state ;

public :
void a t t a c h ( O b s e r v e r * o b s e r v e r ) {
o b s e r v e r s . push_back ( o b s e r v e r ) ;
}

void d e t a c h ( O b s e r v e r * o b s e r v e r ) {
/ / Implement o b s e r v e r removal l o g i c h e r e ( not shown i n t h i s s i m p l e example )
}

void s e t S t a t e ( const s t d : : s t r i n g & newState ) {


s t a t e = newState ;
notify ( ) ;
}

void n o t i f y ( ) {
for ( Observer * observer : observers ) {
o b s e r v e r −>update ( s t a t e ) ;
}
}
};

i n t main ( ) {
Subject subject ;

ConcreteObserver o b s e r v e r 1 ( " Observer 1 " ) ;


ConcreteObserver observer2 ( " Observer 2 " ) ;
s u b j e c t . a t t a c h (& o b s e r v e r 1 ) ;
s u b j e c t . a t t a c h (& o b s e r v e r 2 ) ;

s u b j e c t . s e t S t a t e ( "New s t a t e ! " ) ;

return 0 ;
}

In this example:

1. Observer is the observer interface that defines the update method.

2. ConcreteObserver is a concrete observer class that implements the Observer inter-


face. It responds to notifications by printing a message.

3. Subject is the subject class that maintains a list of observers and notifies them when
its state changes. It provides methods for attaching observers, detaching observers
(not shown in this simple example), setting its state, and notifying observers.

4. In the main function, two observers are created, attached to the subject, and
notified when the subject’s state changes.

Strategy Pattern
Defines a family of algorithms, encapsulates each one, and makes them interchangeable.
It allows the algorithm to vary independently from clients that use it.
The Strategy Pattern is a behavioral design pattern that defines a family of algorithms,
encapsulates each one, and makes them interchangeable. It allows a client to choose an
algorithm from a family of algorithms at runtime, providing flexibility in selecting the
appropriate strategy without altering the client code.
Key characteristics of the Strategy Pattern:

1. Context: The "context" is the class that contains the strategy and maintains a
reference to the selected strategy. The context is responsible for invoking the
strategy.

2. Strategy: The "strategy" is an interface or abstract class that defines a set of meth-
ods or operations. Concrete strategy classes implement these methods to provide
different algorithmic behaviors.

3. Concrete Strategies: "Concrete strategies" are the actual implementations of the


algorithms. Each concrete strategy class implements the strategy interface and
provides a specific algorithmic behavior.

4. Dynamic Switching: The Strategy Pattern allows the client (context) to switch
between different strategies at runtime, making it possible to choose the most
appropriate strategy for a given situation.

Here’s a typical implementation of the Strategy Pattern in C++:


94 L E C T URE 7: DESIGN PAT TERNS

# include <iostream >

/ / Strategy interface
class PaymentStrategy {
public :
v i r t u a l void pay ( i n t amount ) = 0 ;
};

/ / Concrete Strategy 1
c l a s s CreditCardPayment : p u b l i c P a y m e n t S t r a t e g y {
public :
void pay ( i n t amount ) o v e r r i d e {
s t d : : c o u t << " P a i d " << amount << " v i a C r e d i t Card . " << s t d : : e n d l ;
}
};

/ / Concrete Strategy 2
c l a s s PayPalPayment : p u b l i c P a y m e n t S t r a t e g y {
public :
void pay ( i n t amount ) o v e r r i d e {
s t d : : c o u t << " P a i d " << amount << " v i a P a y P a l . " << s t d : : e n d l ;
}
};

/ / Context
c l a s s ShoppingCart {
private :
PaymentStrategy * paymentStrategy ;

public :
void s e t P a y m e n t S t r a t e g y ( P a y m e n t S t r a t e g y * s t r a t e g y ) {
paymentStrategy = s t r a t e g y ;
}

void checkout ( i n t amount ) {


p a y m e n t S t r a t e g y −>pay ( amount ) ;
}
};

i n t main ( ) {
ShoppingCart c a r t ;

P a y m e n t S t r a t e g y * c r e d i t C a r d S t r a t e g y = new CreditCardPayment ( ) ;
P a y m e n t S t r a t e g y * p a y p a l S t r a t e g y = new PayPalPayment ( ) ;

cart . setPaymentStrategy ( creditCardStrategy ) ;


c a r t . checkout ( 1 0 0 ) ;

cart . setPaymentStrategy ( paypalStrategy ) ;


c a r t . checkout ( 2 0 0 ) ;

delete creditCardStrategy ;
delete paypalStrategy ;

return 0 ;
}

In this example:

1. PaymentStrategy is the strategy interface that defines the pay method.

2. CreditCardPayment and PayPalPayment are concrete strategy classes that imple-


ment the PaymentStrategy interface with specific payment methods.

3. ShoppingCart is the context class that maintains a reference to the selected pay-
ment strategy and uses it to perform the payment when the checkout method is
called.

4. In the main function, two different payment strategies are created, and the Shop-
pingCart object switches between them at runtime to complete the checkout
process.

Command Pattern
Encapsulates a request as an object, thereby allowing for parameterization of clients
with queues, requests, and operations.
The Command Pattern is a behavioral design pattern that encapsulates a request
as an object, thereby allowing for parameterization of clients with queues, requests,
and operations. It enables the separation of the sender (invoker) of a request from the
receiver (handler) of that request, providing flexibility and extensibility in how requests
are processed.
Key characteristics of the Command Pattern:

1. Command: The "command" is an object representing an action or request. It


typically includes a method (e.g., execute) that is called to perform the action.

2. Concrete Command: A "concrete command" is a class that implements the com-


mand interface and defines the specific operation to be performed. It holds a
reference to the receiver.

3. Receiver: The "receiver" is the object that knows how to perform the action. It has
the necessary methods and state to carry out the request.

4. Invoker: The "invoker" is responsible for storing and invoking commands. It


does not know the specifics of how a command is executed; it simply calls the
command’s execute method.

5. Client: The "client" creates and assembles the commands, receivers, and invokers,
thereby defining how the system’s behavior should be configured.

Here’s a typical implementation of the Command Pattern in C++:


96 L E C T URE 7: DESIGN PAT TERNS

# include <iostream >


# include < vector >

/ / Command i n t e r f a c e
c l a s s Command {
public :
v i r t u a l void e x e c u t e ( ) = 0 ;
};

/ / Receiver
class Light {
public :
void turnOn ( ) {
s t d : : c o u t << " L i g h t i s ON. " << s t d : : e n d l ;
}

void t u r n O f f ( ) {
s t d : : c o u t << " L i g h t i s OFF . " << s t d : : e n d l ;
}
};

/ / C o n c r e t e Command 1
c l a s s TurnOnCommand : p u b l i c Command {
private :
Light * l i g h t ;

public :
TurnOnCommand ( L i g h t * l ) : l i g h t ( l ) { }

void e x e c u t e ( ) o v e r r i d e {
l i g h t −>turnOn ( ) ;
}
};

/ / C o n c r e t e Command 2
c l a s s TurnOffCommand : p u b l i c Command {
private :
Light * l i g h t ;

public :
TurnOffCommand ( L i g h t * l ) : l i g h t ( l ) { }

void e x e c u t e ( ) o v e r r i d e {
l i g h t −> t u r n O f f ( ) ;
}
};

/ / Invoker
c l a s s RemoteControl {
private :
Command * command ;

public :
void setCommand (Command * cmd ) {
command = cmd ;
}

void p r e s s B u t t o n ( ) {
command−>e x e c u t e ( ) ;
}
};

i n t main ( ) {
Light livingRoomLight ;
Light kitchenLight ;

TurnOnCommand livingRoomOn (& l i v i n g R o o m L i g h t ) ;


TurnOffCommand l i v i n g R o o m O f f (& l i v i n g R o o m L i g h t ) ;

TurnOnCommand kitchenOn (& k i t c h e n L i g h t ) ;


TurnOffCommand k i t c h e n O f f (& k i t c h e n L i g h t ) ;

RemoteControl remote ;

/ / C o n f i g u r e t h e remote c o n t r o l
remote . setCommand(& livingRoomOn ) ;
remote . p r e s s B u t t o n ( ) ;

remote . setCommand(& kitchenOn ) ;


remote . p r e s s B u t t o n ( ) ;

remote . setCommand(& l i v i n g R o o m O f f ) ;
remote . p r e s s B u t t o n ( ) ;

return 0 ;
}
In this example:

1. Command is the command interface that defines the execute method.


2. Light is the receiver that knows how to perform the actions, in this case, turning
on and off the light.
3. TurnOnCommand and TurnOffCommand are concrete command classes that
implement the Command interface and specify which action to execute. They
hold references to the appropriate Light object.
4. RemoteControl is the invoker, which holds a reference to the command and can
press the button to execute the command.
5. In the main function, we create instances of Light, TurnOnCommand, TurnOff-
Command, and RemoteControl to configure and execute commands to control
the lights.

State Pattern
Allows an object to alter its behavior when its internal state changes. The object will
appear to change its class.
98 L E C T URE 7: DESIGN PAT TERNS

The State Pattern is a behavioral design pattern that allows an object to alter its
behavior when its internal state changes. The pattern represents each possible state
as an object and delegates the state-specific behavior to these objects. This enables an
object to appear as if it changes its class when its internal state changes.
Key characteristics of the State Pattern:

1. Context: The "context" is the class that contains the state and maintains a reference
to the current state object. The context delegates state-specific behavior to the
current state object.

2. State: "State" objects are classes that represent specific states and implement the
behavior associated with those states. Each state typically provides methods for
handling events or operations.

3. Context Interface: The context defines an interface that allows clients to request
operations. These operations are typically delegated to the current state object.

4. Concrete States: "Concrete states" are classes that implement the state interface.
They provide the specific behavior associated with a particular state.

Here’s a typical implementation of the State Pattern in C++:


# include <iostream >

/ / State interface
class State {
public :
v i r t u a l void handle ( ) = 0 ;
};

/ / Concrete State 1
c l a s s S t a t e 1 : public S t a t e {
public :
void handle ( ) o v e r r i d e {
s t d : : c o u t << " S t a t e 1 i s h a n d l i n g t h e r e q u e s t . " << s t d : : e n d l ;
}
};

/ / Concrete State 2
c l a s s S t a t e 2 : public S t a t e {
public :
void handle ( ) o v e r r i d e {
s t d : : c o u t << " S t a t e 2 i s h a n d l i n g t h e r e q u e s t . " << s t d : : e n d l ;
}
};

/ / Context
c l a s s Context {
private :
State * currentState ;

public :
Context ( S t a t e * i n i t i a l S t a t e ) : c u r r e n t S t a t e ( i n i t i a l S t a t e ) { }
void s e t S t a t e ( S t a t e * newState ) {
c u r r e n t S t a t e = newState ;
}

void r e q u e s t ( ) {
c u r r e n t S t a t e −>handle ( ) ;
}
};

i n t main ( ) {
State1 state1 ;
State2 state2 ;

C o n t e x t c o n t e x t (& s t a t e 1 ) ;

context . request ( ) ;

/ / Change t h e s t a t e d y n a m i c a l l y
c o n t e x t . s e t S t a t e (& s t a t e 2 ) ;
context . request ( ) ;

return 0 ;
}

In this example:

1. State is the state interface that defines the handle method for handling requests.

2. State1 and State2 are concrete state classes that implement the State interface.
They provide specific behavior associated with different states.

3. Context is the context class that maintains a reference to the current state and
delegates requests to the current state object.

4. In the main function, we create instances of State1, State2, and Context to demon-
strate how the context can change its behavior dynamically by switching between
different states.

Practice Lab
1. Exercise 1: Singleton Pattern
Implement a Singleton pattern to create a configuration manager that loads ap-
plication settings from a file. Ensure that only one instance of the configuration
manager exists.

2. Exercise 2: Factory Method Pattern


Create a simple factory method for creating different shapes (e.g., Circle, Rectangle,
Triangle). Implement classes for each shape and use the factory to create instances.
100 L E C T URE 7: DESIGN PAT TERNS

3. Exercise 3: Observer Pattern


Implement an observer pattern for a weather monitoring system. Create a subject
that generates random weather data and observers that display the weather.

4. Exercise 4: Decorator Pattern


Create a text editor application with a Text class. Implement decorators for adding
different formatting options (e.g., bold, italic, underline) to the text.

5. Exercise 5: Command Pattern


Design a remote control for a smart home system. Create command objects for
controlling lights, thermostat, and other devices. Use the command pattern to
execute actions.

6. Exercise 6: State Pattern


Implement a vending machine that dispenses different items based on its state.
Use the state pattern to represent the vending machine’s states (e.g., no coin, has
coin, dispensing).

7. Exercise 7: Strategy Pattern


Develop a payment processing system. Implement payment strategies (e.g., Credit
Card, PayPal, Bitcoin) that can be selected at runtime to process payments.

8. Exercise 8: Composite Pattern


Create a file system representation with directories and files. Implement a com-
posite pattern to manage the hierarchy and perform operations like calculating
the total size of a directory.

9. Exercise 9: Builder Pattern


Build a custom burger ordering system. Implement a builder pattern to create
complex burger orders with various components (e.g., patty type, toppings, sauces).

10. Exercise 10: Proxy Pattern


Design a proxy to restrict access to a sensitive database. Only authorized users
should be able to perform database operations through the proxy.

11. Exercise 11: Abstract Factory Pattern


Extend the factory method exercise (Exercise 2) to use the abstract factory pattern
to create families of related objects, such as different styles of shapes.

12. Exercise 12: Template Method Pattern


Create a template for generating reports (e.g., PDF report, HTML report). Imple-
ment subclasses that provide specific report content and formatting.
Lecture 9: Standard Template Library (STL)

What is the Standard Template Library (STL)?


The Standard Template Library (STL) is a powerful and versatile library in C++ that
provides a collection of generic template classes and functions to simplify common pro-
gramming tasks. It is a fundamental part of the C++ Standard Library, offering reusable
and efficient algorithms and data structures. The STL aims to promote code reusability,
maintainability, and performance in C++ applications.

Key Components of the STL


The STL consists of several key components, each designed to address specific program-
ming needs:

Containers
Containers are classes that hold collections of objects or data. STL provides a variety of
container classes, including:

• Vectors: Dynamic arrays that can grow or shrink in size.

• Lists: Doubly linked lists that allow efficient insertions and deletions.

• Deques: Double-ended queues that offer fast insertions and deletions at both
ends.

• Sets: Containers that store unique elements in a sorted order.

• Maps: Containers that store key-value pairs in a sorted order.

Algorithms
Algorithms in the STL are a set of reusable functions for performing various operations on
containers or sequences. These include sorting, searching, and manipulation operations.
Some commonly used algorithms include sort(), find(), for_each(), and more.

Iterators
Iterators are objects that provide a way to access elements in a container in a consistent
and abstract manner. They act as a bridge between algorithms and containers, allowing
you to traverse and manipulate container elements. Common types of iterators include
begin(), end(), rbegin(), and rend().
102 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

Function Objects (Functors)


Function objects, also known as functors, are objects that behave like functions. They
are often used as arguments to algorithms to customize their behavior. Functors are
implemented as classes with overloaded operator().

Allocators
Allocators are used to manage memory allocation and deallocation for containers. While
they are typically abstracted away in most cases, advanced users can customize memory
management using allocators.

Benefits of Using the STL


Using the STL offers several advantages to C++ programmers:

Code Reusability
The STL provides a wide range of generic classes and functions, reducing the need to
write custom data structures and algorithms. Developers can leverage pre-implemented
solutions for common tasks.

Standardization
The STL is part of the C++ Standard Library, ensuring that it is available and consistent
across different C++ compilers and platforms. This standardization simplifies cross-
platform development.

Performance
STL containers and algorithms are optimized for efficiency and speed. The library is
designed to be highly efficient, making it suitable for various applications, including
performance-critical ones.

Safety
STL containers and algorithms are designed to be safe to use. They perform bounds
checking and ensure memory safety, helping to prevent common programming errors.

Using the STL in Your Code


To utilize the STL in your C++ programs, include the appropriate headers and make use
of the provided classes and functions. Here’s a simple example that demonstrates the
usage of a vector, an algorithm, and an iterator:
# include <iostream >
# include < vector >
# include <algorithm >

i n t main ( ) {
/ / Create a vector of i n t e g e r s
s t d : : v e c t o r < i n t > numbers = { 5 , 2 , 9 , 1 , 5 , 6 } ;

/ / Use an STL a l g o r i t h m ( s o r t ) t o s o r t t h e v e c t o r
s t d : : s o r t ( numbers . b e g i n ( ) , numbers . end ( ) ) ;

/ / Use an STL i t e r a t o r t o p r i n t t h e s o r t e d v e c t o r
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}

return 0 ;
}

Example of STL Containers


Vectors
Imagine std::vector as a magical resizable array in C++. It’s like having a container that
can automatically grow or shrink as you add or remove elements, without you needing
to worry about the underlying memory management.

• Auto-Sizing: When you insert elements into a std::vector, it expands itself as needed
to accommodate the new data. You don’t have to specify the size upfront; the vector
adapts on the fly.

• Easy Access: You can access elements by their position just like in a regular array,
using indices. It’s like having a collection of numbered drawers, and you can open
any drawer to get what you want.

• Efficient Packing: std::vector packs its elements tightly in memory, making it ef-
ficient for tasks like looping through all the items. Elements are neighbors in
memory, so it’s like flipping through pages in a book.

• Self-Cleaning: When you’re done with a std::vector, it takes care of cleaning up the
memory it used. It’s like having a magical butler who cleans up your room when
you leave.

• Customizable: You can tell the vector how much space you expect to need (without
putting things in it), so it can prepare in advance. It’s like reserving seats in a
restaurant before you arrive.

• Adaptable: If you decide you need more room, you can add more elements, and
the vector expands to fit. If you want to slim it down, you can remove elements,
and it shrinks accordingly.

In essence, std::vector is your dynamic, self-adjusting, and memory-savvy array com-


panion in C++, always ready to accommodate your data as your program runs.
Here is an example:
104 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

# include <iostream >


# include < vector >

i n t main ( ) {
/ / Create a std : : vector to s t o r e a l i s t of i n t e g e r s
s t d : : v e c t o r < i n t > numbers ;

/ / Add some numbers t o t h e v e c t o r


numbers . push_back ( 5 ) ;
numbers . push_back ( 2 ) ;
numbers . push_back ( 9 ) ;
numbers . push_back ( 1 ) ;
numbers . push_back ( 5 ) ;
numbers . push_back ( 6 ) ;

/ / Display the contents of the vector


s t d : : c o u t << " Numbers i n t h e v e c t o r : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
s t d : : c o u t << s t d : : e n d l ;

/ / C a l c u l a t e t h e sum o f t h e numbers
i n t sum = 0 ;
f o r ( const i n t & num : numbers ) {
sum += num ;
}
s t d : : c o u t << "Sum o f t h e numbers : " << sum << s t d : : e n d l ;

/ / Find and d i s p l a y t h e maximum e l e m e n t


i n t max = numbers [ 0 ] ;
f o r ( const i n t & num : numbers ) {
i f (num > max ) {
max = num ;
}
}
s t d : : c o u t << "Maximum number : " << max << s t d : : e n d l ;

/ / Remove t h e f i r s t e l e m e n t from t h e v e c t o r
numbers . e r a s e ( numbers . b e g i n ( ) ) ;

/ / D i s p l a y t h e updated v e c t o r
s t d : : c o u t << " Numbers a f t e r e r a s i n g t h e f i r s t element : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
s t d : : c o u t << s t d : : e n d l ;

return 0 ;
}
In this example:
• We include the necessary headers for std::vector.
• We create a std::vector called numbers to store a list of integers.
• We use push_back() to add numbers to the vector.

• We iterate through the vector to display its contents, calculate the sum, and find
the maximum number.

• We remove the first element using erase().

• Finally, we display the updated vector.

This example showcases the basic operations you can perform with std::vector, such
as adding, accessing, iterating, and modifying elements. std::vector is a flexible and
convenient container for working with dynamic lists of data in C++.

Arrays
std::array is a container class in C++ that represents a fixed-size, static array with several
advantages over traditional arrays. It is part of the Standard Library and provides type
and bounds safety, making it a safer alternative to C-style arrays. Here are some key
characteristics and features of std::array:
• Fixed Size: std::array has a fixed size determined at compile time. Once you create
an std::array, its size cannot be changed. This feature ensures compile-time safety
and predictability

• Type and Bounds Safety: std::array provides type safety, meaning you can’t acci-
dentally mix different data types. It also performs bounds checking, preventing
you from accessing elements outside the valid range.

• Efficient: Like traditional arrays, std::array elements are stored in contiguous mem-
ory, making element access and iteration efficient.

• Iterators: You can use iterators with std::array just like with other STL containers,
allowing for a consistent way to traverse and manipulate elements.

• Standard Library Compatibility: std::array conforms to the SequenceContainer


concept, making it compatible with many STL algorithms and functions.

• Initialization: std::array provides various ways to initialize its elements, includ-


ing aggregate initialization (similar to plain arrays), fill constructors, and copy
constructors.
Here’s a practical example of using std::array to store and manipulate a list of integers:
# include <iostream >
# include < array >

i n t main ( ) {
/ / D e c l a r e and i n i t i a l i z e an s t d : : a r r a y o f i n t e g e r s
s t d : : a r r a y < i n t , 5> numbers = { 1 , 2 , 3 , 4 , 5 } ;

/ / A c c e s s e l e m e n t s by i n d e x
s t d : : c o u t << " Element a t i n d e x 2 : " << numbers [ 2 ] << s t d : : e n d l ;
106 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

/ / Modify an e l e m e n t
numbers [ 3 ] = 1 0 ;

/ / C a l c u l a t e t h e sum o f e l e m e n t s
i n t sum = 0 ;
f o r ( const i n t & num : numbers ) {
sum += num ;
}
s t d : : c o u t << "Sum o f e l e m e n t s : " << sum << s t d : : e n d l ;

/ / S i z e and i t e r a t i o n
s t d : : c o u t << " S i z e o f t h e a r r a y : " << numbers . s i z e ( ) << s t d : : e n d l ;
s t d : : c o u t << " Elements i n t h e a r r a y : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
s t d : : c o u t << s t d : : e n d l ;

return 0 ;
}

In this example:

• We include the necessary headers for std::array.

• We declare and initialize an std::array called numbers with a fixed size of 5.

• We access and modify elements using the subscript operator ([]).

• We calculate the sum of elements using a loop.

• We use size() to get the size of the array.

• We iterate through the array using a range-based for loop.

std::array combines the safety of a fixed-size array with the convenience and com-
patibility of STL containers, making it a valuable tool for managing arrays in C++.

List
std::list is a doubly linked list container class provided by the C++ Standard Library as
part of the Standard Template Library (STL). Unlike std::vector, which uses dynamic
arrays, std::list uses linked nodes to store its elements. Here are some key characteristics
and features of std::list:

• Doubly Linked List: std::list is implemented as a doubly linked list, which means
that each element in the list has pointers to both the next and the previous ele-
ments. This allows for efficient insertions and deletions at both the beginning and
the end of the list, as well as at arbitrary positions.
• Efficient Insertions and Deletions: Inserting or removing an element at the be-
ginning or end of a std::list is a constant-time operation, O(1). Insertions and
deletions at arbitrary positions also have good time complexity, O(N), where N is
the distance from the beginning or end of the list.

• Iterators: std::list provides bidirectional iterators, which allow you to traverse the
list in both forward and backward directions. This makes it suitable for scenarios
where you need to access elements in reverse order.

• Dynamic Sizing: Unlike std::array, which has a fixed size, or std::vector, which
dynamically resizes itself, std::list can dynamically grow or shrink as elements
are inserted or removed. This makes it useful for scenarios where the number of
elements is not known in advance.

• Memory Overhead: Due to its doubly linked nature, std::list has higher mem-
ory overhead compared to arrays and vectors. Each element in the list requires
additional memory for two pointers (next and previous).

• No Contiguous Memory: Elements in a std::list are not stored in contiguous mem-


ory locations, which can impact cache locality and random access performance.
It’s optimized for insertions and deletions, not for quick indexing.

Here’s a basic example of using std::list to store and manipulate a list of integers:
# include <iostream >
# include < l i s t >

i n t main ( ) {
/ / D e c l a r e and i n i t i a l i z e an s t d : : l i s t o f i n t e g e r s
s t d : : l i s t < i n t > numbers = { 1 , 2 , 3 , 4 , 5 } ;

/ / I n s e r t an e l e m e n t a t t h e b e g i n n i n g o f t h e l i s t
numbers . p u s h _ f r o n t ( 0 ) ;

/ / I n s e r t an e l e m e n t a t t h e end o f t h e l i s t
numbers . push_back ( 6 ) ;

/ / I t e r a t e t h r o u g h t h e l i s t u s i n g an i t e r a t o r
s t d : : c o u t << " Elements i n t h e l i s t : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
s t d : : c o u t << s t d : : e n d l ;

/ / Remove t h e f i r s t and l a s t e l e m e n t s
numbers . p o p _ f r o n t ( ) ;
numbers . pop_back ( ) ;

/ / D i s p l a y t h e updated l i s t
s t d : : c o u t << " Elements a f t e r removal : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
108 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

s t d : : c o u t << s t d : : e n d l ;

return 0 ;
}

In this example:

• We include the necessary headers for std::list.

• We declare and initialize an std::list called numbers.

• We use push_front() and push_back() to insert elements at the beginning and end
of the list.

• We use an iterator to iterate through the list and display its contents.

• We use pop_front() and pop_back() to remove the first and last elements.

• Finally, we display the updated list.

std::list is a useful container when you need efficient insertions and deletions, espe-
cially at both ends, and when you don’t require constant-time random access.

Set
std::set is a container class provided by the C++ Standard Library as part of the Standard
Template Library (STL). It represents a collection of unique, sorted elements. Here are
some key characteristics and features of std::set:

• Unique Elements: In a std::set, each element is unique. Duplicate values are


automatically eliminated, ensuring that the set contains only distinct values.

• . Sorted Order: Elements in a std::set are automatically sorted in ascending order.


This sorting is performed using a comparison function, which can be customized.

• Efficient Search: Searching for an element in a std::set is an efficient operation,


typically performed in logarithmic time (O(log N)), where N is the number of
elements in the set. This makes it suitable for tasks that require quick lookups.

• nsertions and Deletions: Inserting and deleting elements in a std::set are also effi-
cient operations, performed in logarithmic time. The set automatically maintains
its sorted order after these operations.

• Iterators: std::set provides bidirectional iterators, allowing you to traverse the set in
both forward and backward directions. This is useful for tasks that involve iterating
through elements.

• . Custom Comparisons: You can customize the comparison function used for
sorting elements in the set. By default, elements are sorted using the less-than (<)
operator, but you can provide your own comparison function for more complex
data types.
• Memory Overhead: std::set has a memory overhead due to the need to maintain
unique and sorted elements. Each element is typically associated with additional
memory for internal data structures.
Here’s a basic example of using std::set to store and manipulate a set of integers:
# include <iostream >
# include < set >

i n t main ( ) {
/ / D e c l a r e and i n i t i a l i z e an s t d : : s e t o f i n t e g e r s
s t d : : s e t < i n t > numbers = { 5 , 2 , 9 , 1 , 5 , 6 } ;

/ / I n s e r t an e l e m e n t i n t o t h e s e t
numbers . i n s e r t ( 8 ) ;

/ / Check i f an e l e m e n t i s p r e s e n t in the s e t
i f ( numbers . count ( 5 ) > 0) {
s t d : : c o u t << " 5 i s i n t h e s e t . " << s t d : : e n d l ;
} else {
s t d : : c o u t << " 5 i s not i n t h e s e t . " << s t d : : e n d l ;
}

/ / I t e r a t e t h r o u g h t h e s e t u s i n g an i t e r a t o r
s t d : : c o u t << " Elements i n t h e s e t : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
s t d : : c o u t << s t d : : e n d l ;

/ / Remove an e l e m e n t from t h e s e t
numbers . e r a s e ( 1 ) ;

/ / D i s p l a y t h e updated s e t
s t d : : c o u t << " Elements a f t e r removal : " ;
f o r ( const i n t & num : numbers ) {
s t d : : c o u t << num << " " ;
}
s t d : : c o u t << s t d : : e n d l ;

return 0 ;
}
In this example:

• We include the necessary headers for std::set.


• We declare and initialize an std::set called numbers.
• We use insert() to add an element to the set.
• . We use count() to check if an element exists in the set.
• We use an iterator to iterate through the set and display its contents.
• We use erase() to remove an element from the set.
110 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

• Finally, we display the updated set.

std::set is a valuable container when you need a collection of unique, sorted elements
and efficient search operations.

Maps
std::map is a container class provided by the C++ Standard Library as part of the Standard
Template Library (STL). It represents an associative container that stores a collection of
key-value pairs, where each key is unique, and the keys are sorted in ascending order.
Here are some key characteristics and features of std::map:

• Associative: std::map is an associative container, meaning it associates keys with


values. You can look up values by their corresponding keys.

• Unique Keys: In a std::map, each key is unique. This ensures that there are no
duplicate keys in the map.

• Sorted Keys: Keys in a std::map are automatically sorted in ascending order. This
allows for efficient key-based searches and traversals.

• Efficient Lookup: Searching for a key in a std::map is an efficient operation, typi-


cally performed in logarithmic time (O(log N)), where N is the number of key-value
pairs in the map.

• Insertions and Deletions: Inserting, updating, and deleting key-value pairs in a


std::map are also efficient operations, performed in logarithmic time. The map
maintains its sorted order after these operations.

• Custom Comparisons: You can customize the comparison function used for sorting
keys in the map. By default, keys are sorted using the less-than (<) operator, but
you can provide your own comparison function for more complex data types.

• Iterators: std::map provides bidirectional iterators, allowing you to traverse the


map in both forward and backward directions. This is useful for tasks that involve
iterating through key-value pairs.

• Memory Overhead: std::map has a memory overhead due to the need to maintain
unique and sorted keys. Each key-value pair is typically associated with additional
memory for internal data structures.

Here’s a basic example of using std::map to store and manipulate a map of names
and ages:
# include <iostream >
# i n c l u d e <map>

i n t main ( ) {
/ / D e c l a r e and i n i t i a l i z e an s t d : : map o f names ( k e y s ) and a g e s ( v a l u e s )
s t d : : map< s t d : : s t r i n g , i n t > ageMap ;
/ / I n s e r t key−v a l u e p a i r s i n t o t h e map
ageMap [ " A l i c e " ] = 2 5 ;
ageMap [ " Bob " ] = 3 0 ;
ageMap [ " C h a r l i e " ] = 2 2 ;

/ / Look up t h e age o f a p e r s o n by name


s t d : : s t r i n g nameToLookup = " Bob " ;
i f ( ageMap . f i n d ( nameToLookup ) ! = ageMap . end ( ) ) {
s t d : : c o u t << nameToLookup << " ’ s age i s " << ageMap [ nameToLookup ] << " y e a r s . "
} else {
s t d : : c o u t << nameToLookup << " not found i n t h e map . " << s t d : : e n d l ;
}

/ / I t e r a t e t h r o u g h t h e map u s i n g an i t e r a t o r
s t d : : c o u t << " C o n t e n t s o f t h e map : " << s t d : : e n d l ;
f o r ( const auto& e n t r y : ageMap ) {
s t d : : c o u t << e n t r y . f i r s t << " : " << e n t r y . second << " y e a r s " << s t d : : e n d l ;
}

return 0 ;
}
In this example:
• We include the necessary headers for std::map.
• We declare and initialize an std::map called ageMap to store names as keys and
ages as values.
• We use the subscript operator ([]) to insert key-value pairs into the map.
• We use find() to check if a key exists in the map before accessing its value.
• We use an iterator to iterate through the map and display its contents.
std::map is a versatile container when you need to maintain a collection of key-value
pairs with unique keys and sorted keys for efficient lookups.

Pairs
std::pair is a simple but versatile utility class provided by the C++ Standard Library. It
allows you to create a pair of two objects, which can be of different types. This class is
frequently used for various purposes, especially in scenarios where you need to group
two values together. Here are some key characteristics and features of std::pair:
• Two-Element Container: std::pair can hold two values, referred to as "first" and
"second" elements. These elements can be of different types.
• Value Semantics: Like most C++ objects, std::pair exhibits value semantics, mean-
ing that copies of pairs are created with distinct copies of the stored elements.
• Flexible Types: You can use std::pair to store values of any data type, including
user-defined types. For example, you can use it to store a pair of integers, a pair of
strings, or even a pair of custom objects.
112 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

• Comparison Operations: std::pair supports comparison operations, so you can


compare pairs using operators like <, >, <=, and >= based on their first elements
and then their second elements.

• Convenient for Functions: Pairs are often used to return multiple values from
functions. Instead of defining a custom struct or class for this purpose, you can
use std::pair to bundle the values together.
Here’s a basic example of using std::pair to store and manipulate a pair of integers:
# include <iostream >
# include < u t i l i t y > / / Required f o r s t d : : pair

i n t main ( ) {
/ / D e c l a r e and i n i t i a l i z e a p a i r o f i n t e g e r s
s t d : : p a i r < i n t , i n t > c o o r d i n a t e s = s t d : : make_pair ( 3 , 4 ) ;

/ / A c c e s s and modify t h e e l e m e n t s o f t h e p a i r
s t d : : c o u t << " C o o r d i n a t e s : ( " << c o o r d i n a t e s . f i r s t << " , " << c o o r d i n a t e s . second <<

/ / Compare p a i r s b a s e d on t h e i r f i r s t e l e m e n t s
s t d : : p a i r < i n t , i n t > o t h e r C o o r d i n a t e s = s t d : : make_pair ( 2 , 5 ) ;
i f ( coordinates < otherCoordinates ) {
s t d : : c o u t << " These c o o r d i n a t e s a r e c l o s e r t o t h e o r i g i n . " << s t d : : e n d l ;
} else {
s t d : : c o u t << " These c o o r d i n a t e s a r e f a r t h e r from t h e o r i g i n . " << s t d : : e n d l ;
}

return 0 ;
}

In this example:

• We include the necessary header for std::pair.

• We declare and initialize an std::pair called coordinates with two integers.

• We access and modify the elements of the pair using .first and .second.

• We compare pairs based on their first elements to determine which set of coordi-
nates is closer to the origin.

std::pair is a convenient and widely used tool in C++ for working with pairs of values.
It’s often employed in a wide range of scenarios, from simple data grouping to returning
multiple values from functions.

Tuples
std::tuple is a versatile container provided by the C++ Standard Library that allows you to
store a fixed-size collection of elements. Each element in a tuple can be of a different type,
making it useful for scenarios where you need to bundle together values of different data
types. Tuples are indexed, ordered, and allow you to access their elements by position.
Key Features of std::tuple
• Heterogeneous Elements: One of the primary advantages of std::tuple is that it
can hold elements of different types. This is in contrast to arrays or vectors, where
all elements must have the same data type.

• Fixed Size: The size of a tuple is determined at compile time. Once you define the
number and types of elements in a tuple, it cannot be changed without altering
the code.

• Ordered: Elements in a tuple are ordered and can be accessed by their position
within the tuple. This order is established at compile time and remains consistent.

• Value Semantics: Tuples have value semantics, meaning they create copies of their
elements when copied. Modifications to one tuple do not affect another tuple.

• Structured Binding (C++17 and later): C++17 introduced structured binding, which
allows you to easily decompose a tuple into individual variables. This makes
working with tuples more convenient.

• Compile-Time Type Safety: The types of elements in a tuple are known at compile
time, providing type safety. This means you can’t accidentally access elements
with the wrong data type.

• Comparison Operators: Tuples support comparison operators (e.g., ==, <, >), mak-
ing it possible to compare tuples for equality or order.

Here’s how you can create and access a std::tuple:


# include <iostream >
# include <tuple >

i n t main ( ) {
/ / C r e a t i n g a t u p l e with e l e m e n t s o f d i f f e r e n t t y p e s
s t d : : t u p l e < i n t , double , s t d : : s t r i n g > myTuple ( 4 2 , 3 . 1 4 , " H e l l o " ) ;

/ / A c c e s s i n g e l e m e n t s by p o s i t i o n
i n t i n t V a l u e = s t d : : g e t <0 >( myTuple ) ;
double d o u b l e V a l u e = s t d : : g e t < 1 > ( myTuple ) ;
s t d : : s t r i n g s t r i n g V a l u e = s t d : : g e t <2 >( myTuple ) ;

s t d : : c o u t << " Tuple e l e m e n t s : " << i n t V a l u e << " , " << d o u b l e V a l u e << " , " << s t r i n g

return 0 ;
}

In this example:

• We include the necessary header for std::tuple.

• We create a tuple myTuple containing an integer, a double, and a string.

• We access elements using std::get<index>(tuple), where index is the position of


the element in the tuple.
114 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

Queues
std::queue is a container adapter provided by the C++ Standard Library that represents a
queue data structure. A queue follows the First-In-First-Out (FIFO) principle, meaning
that the element added first is the one that will be removed first. std::queue is typically
implemented using other standard containers, such as std::deque (double-ended queue)
or std::list. Here are some key characteristics and features of std::queue:

• FIFO Behavior: Elements are inserted at the back (enqueue) and removed from
the front (dequeue) of the queue. This ensures that the element that has been in
the queue the longest is the first to be removed.
• Container Adapter: std::queue is a container adapter, which means that it is built
on top of an underlying container (usually std::deque by default). The choice of
the underlying container can be customized if needed.
• Limited Access: Access to elements in a queue is limited. You can only access the
front element (the one to be dequeued next) and the back element (the one most
recently enqueued).
• No Random Access: Unlike arrays or vectors, you cannot directly access elements
at arbitrary positions within a queue. It’s optimized for efficient enqueuing and
dequeuing.
• Constant-Time Operations: Enqueuing and dequeuing operations are typically per-
formed in constant time, O(1), making queues efficient for managing a collection
of elements where order matters.
• . Empty Queue: It provides methods like empty() to check if the queue is empty,
which is useful for handling boundary cases.

Here’s a basic example of using std::queue to manage a queue of integers:


# include <iostream >
# i n c l u d e <queue >

i n t main ( ) {
/ / D e c l a r e a queue o f i n t e g e r s
s t d : : queue < i n t > myQueue ;

/ / Enqueue e l e m e n t s
myQueue . push ( 1 0 ) ;
myQueue . push ( 2 0 ) ;
myQueue . push ( 3 0 ) ;

/ / Dequeue and p r i n t e l e m e n t s
while ( ! myQueue . empty ( ) ) {
s t d : : c o u t << " Dequeued : " << myQueue . f r o n t ( ) << s t d : : e n d l ;
myQueue . pop ( ) ; / / Remove t h e f r o n t e l e m e n t
}

return 0 ;
}
std::queue is commonly used in various applications where elements are processed in
a specific order, such as task scheduling, breadth-first search algorithms, and managing
a print job queue in a printer.

Optional
std::optional is a relatively new addition to the C++ Standard Library, introduced in C++17.
It provides a way to represent values that may or may not be present, and it’s particu-
larly useful in situations where you need to handle optional or nullable values without
resorting to null pointers or special sentinel values. Here are some key characteristics
and features of std::optional:

• Optional Values: std::optional allows you to represent values that may or may not
exist. It encapsulates the idea of optionality in a type-safe manner.

• Value Semantics: Instances of std::optional have value semantics, meaning that


they own their values. Copying an optional creates a new instance with a copy of
the value.

• Type Safety: The type of the value contained in the optional is known at com-
pile time, providing strong type safety. This eliminates common runtime errors
associated with nullable pointers.

• Efficient Storage: std::optional is designed to be efficient in terms of storage. It


only holds a value when it’s present, which means no extra memory overhead is
incurred when the value is absent.

• Accessing Values: You can access the value within an optional using the * derefer-
ence operator or the -> arrow operator, similar to working with pointers.

• Checking for Presence: You can check if an optional has a value using the has_value()
member function or the bool conversion operator.

• Value Initialization: You can initialize an optional without a value, making it empty.
This can be useful when you don’t have a valid value to provide initially.

• Use Cases: std::optional is useful for function return values that may fail, configu-
ration settings that may or may not be present, and handling nullable values in
general.

Here’s a basic example of using std::optional to represent an optional integer value:


# include <iostream >
# include < optional >

i n t main ( ) {
/ / D e c l a r e an o p t i o n a l i n t e g e r
std : : optional < int > optionalValue ;

/ / Check i f t h e o p t i o n a l has a v a l u e
i f ( optionalValue . has_value ( ) ) {
116 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

s t d : : c o u t << " V a l u e i s p r e s e n t : " << * o p t i o n a l V a l u e << s t d : : e n d l ;


} else {
s t d : : c o u t << " V a l u e i s not p r e s e n t . " << s t d : : e n d l ;
}

/ / Assign a value to the optional


optionalValue = 42;

/ / Access the value within the optional


i f ( optionalValue . has_value ( ) ) {
s t d : : c o u t << " V a l u e i s p r e s e n t : " << * o p t i o n a l V a l u e << s t d : : e n d l ;
} else {
s t d : : c o u t << " V a l u e i s not p r e s e n t . " << s t d : : e n d l ;
}

return 0 ;
}
In this example:
• We include the necessary header for std::optional.
• . We declare an std::optional called optionalValue to hold an integer.
• We check if the optional has a value using has_value().
• We assign a value to the optional using the assignment operator.
• We access the value within the optional using *optionalValue.
std::optional is a powerful tool for handling optional values in a type-safe and ex-
pressive manner, reducing the risk of null pointer-related errors in your code.

Example of STL Algorithms


Find
std::find is a C++ Standard Library algorithm that’s part of the <algorithm> header. It’s
used to search for a specific value within a range, such as an array, a container, or
any sequence that provides iterators. std::find is a simple yet versatile tool for finding
elements in a collection. Here are its key features:
• . Search for Value: std::find is used to locate a specific value within a given range.
It searches for the first occurrence of the value.
• Range-Based: It operates on any sequence-like data structure that provides itera-
tors. This includes arrays, vectors, lists, sets, maps, and more.
• Type-Agnostic: std::find is not tied to a specific data type. You can search for values
of any type that supports comparison using ==.
• Returns an Iterator: When the value is found, std::find returns an iterator pointing
to the first occurrence of the value. If the value is not found, it returns the end
iterator of the range.
• Linear Search: The algorithm performs a linear search, comparing each element
in the range with the target value until it finds a match or reaches the end.

Here’s a basic example of using std::find to search for an integer within a vector:
# include <iostream >
# include < vector >
# include <algorithm >

i n t main ( ) {
s t d : : v e c t o r < i n t > numbers = { 1 , 2 , 3 , 4 , 5 } ;

int target = 3;

/ / Use s t d : : f i n d t o s e a r c h f o r t h e t a r g e t v a l u e
auto r e s u l t = s t d : : f i n d ( numbers . b e g i n ( ) , numbers . end ( ) , t a r g e t ) ;

i f ( r e s u l t ! = numbers . end ( ) ) {
s t d : : c o u t << " V a l u e " << t a r g e t << " found a t p o s i t i o n " << s t d : : d i s t a n c e ( numbe
} else {
s t d : : c o u t << " V a l u e " << t a r g e t << " not found . " << s t d : : e n d l ;
}

return 0 ;
}

In this example:

• We include the necessary headers for vectors, algorithms, and I/O.

• We create a vector of integers called numbers and initialize it with some values.

• We specify the target value we want to find (in this case, 3).

• We use std::find to search for the target value within the range defined by num-
bers.begin() and numbers.end().

• If the value is found (result is not equal to numbers.end()), we print its position
using std::distance.

std::find is a fundamental algorithm in C++ that’s commonly used for searching


within collections, and it’s a building block for more complex operations.

Find_if
std::find_if is another C++ Standard Library algorithm provided by the <algorithm>
header. It’s an extension of std::find and is used for searching a range based on a specified
predicate or condition. std::find_if allows you to search for an element that satisfies a
specific condition rather than just looking for an exact value. Here are its key features:

• Search with a Predicate: std::find_if searches for the first element in a range that
satisfies a given predicate or condition.
118 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

• Range-Based: Similar to std::find, it operates on sequences that provide iterators,


including arrays, vectors, lists, sets, maps, and custom containers.

• Type-Agnostic: You can use std::find_if to search for elements of any data type as
long as you define a suitable predicate.

• Returns an Iterator: When the predicate is satisfied, std::find_if returns an iterator


pointing to the first element that meets the condition. If the predicate is never
satisfied, it returns the end iterator of the range.

• Linear Search: Like std::find, this algorithm performs a linear search, checking
each element in the range against the predicate until a match is found or the end
of the range is reached.

Here’s a basic example of using std::find_if to search for an element greater than a
specific threshold in a vector of integers:
# include <iostream >
# include < vector >
# include <algorithm >

bool i s G r e a t e r T h a n T h r e s h o l d ( i n t v a l u e , i n t t h r e s h o l d ) {
return v a l u e > t h r e s h o l d ;
}

i n t main ( ) {
s t d : : v e c t o r < i n t > numbers = { 1 , 2 , 3 , 4 , 5 } ;

int threshold = 3;

/ / Use s t d : : f i n d _ i f with a custom p r e d i c a t e


auto r e s u l t = s t d : : f i n d _ i f ( numbers . b e g i n ( ) , numbers . end ( ) , [ t h r e s h o l d ] ( i n t v a l u e ) {
return v a l u e > t h r e s h o l d ;
});

i f ( r e s u l t ! = numbers . end ( ) ) {
s t d : : c o u t << " F i r s t v a l u e g r e a t e r than " << t h r e s h o l d << " found : " << * r e s u l t
} else {
s t d : : c o u t << "No v a l u e g r e a t e r than " << t h r e s h o l d << " found . " << s t d : : e n d l ;
}

return 0 ;
}

In this example:

• We include the necessary headers for vectors, algorithms, and I/O.

• We define a custom predicate function isGreaterThanThreshold that checks if a


value is greater than a threshold.

• . We create a vector of integers called numbers and initialize it with some values.

• We specify the threshold value we want to use for comparison.


• We use std::find_if to search for the first element greater than the threshold, and
we provide the custom predicate as a lambda function.

std::find_if is a powerful algorithm for searching based on custom conditions, making


it versatile for a wide range of applications where you need to find elements that meet
specific criteria.

for_each
std::for_each is a C++ Standard Library algorithm provided by the <algorithm> header.
It is used for performing a specified operation on each element within a given range,
such as an array, a container, or any sequence that provides iterators. std::for_each is
often used when you need to apply a function or perform an action on every element in
a collection. Here are its key features:

• Apply a Function: std::for_each applies a given function or callable object to each


element in the specified range.

• Range-Based: It operates on sequences that provide iterators, including arrays,


vectors, lists, sets, maps, and custom containers.

• Type-Agnostic: You can use std::for_each to process elements of any data type as
long as you define a suitable function or callable object.

• Returns Nothing: std::for_each does not return any value. It is typically used for its
side effects, such as modifying elements in place or performing some action on
each element.

• Sequential Processing: The function is applied sequentially to each element in the


order defined by the iterator range.

Here’s a basic example of using std::for_each to print each element of a vector of


integers:
# include <iostream >
# include < vector >
# include <algorithm >

void p r i n t V a l u e ( i n t v a l u e ) {
s t d : : c o u t << v a l u e << " " ;
}

i n t main ( ) {
s t d : : v e c t o r < i n t > numbers = { 1 , 2 , 3 , 4 , 5 } ;

/ / Use s t d : : f o r _ e a c h t o a p p l y t h e p r i n t V a l u e f u n c t i o n t o each e l e m e n t
s t d : : f o r _ e a c h ( numbers . b e g i n ( ) , numbers . end ( ) , p r i n t V a l u e ) ;

s t d : : c o u t << s t d : : e n d l ;

return 0 ;
}
120 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

In this example:

• We include the necessary headers for vectors, algorithms, and I/O.

• We define a custom function printValue that takes an integer and prints it.

• We define a custom function printValue that takes an integer and prints it.

• We create a vector of integers called numbers and initialize it with some values.

• We use std::for_each to apply the printValue function to each element within the
range defined by numbers.begin() and numbers.end().

The result of running this code will be the values in the vector printed to the console.
std::for_each is a convenient algorithm when you want to perform a consistent
operation on each element in a collection without manually writing loops. It promotes
a more declarative and functional style of programming.

Count
std::count is a C++ Standard Library algorithm provided by the <algorithm> header. It
is used for counting the occurrences of a specific value within a given range, such as
an array, a container, or any sequence that provides iterators. std::count is a simple yet
powerful tool for finding out how many times a particular value appears in a collection.
Here are its key features:

• Count Occurrences: std::count counts the number of times a specific value appears
within a given range.

• Range-Based: It operates on sequences that provide iterators, including arrays,


vectors, lists, sets, maps, and custom containers.

• Type-Agnostic: You can use std::count to count occurrences of elements of any


data type that supports comparison using ==.

• Returns an Integer: std::count returns an integer representing the count of occur-


rences of the specified value within the range.

• Linear Search: The algorithm performs a linear search, comparing each element
in the range with the target value and incrementing the count when a match is
found.

Here’s a basic example of using std::count to count the number of occurrences of a


specific integer value in a vector:
# include <iostream >
# include < vector >
# include <algorithm >

i n t main ( ) {
s t d : : v e c t o r < i n t > numbers = { 1 , 2 , 2 , 3 , 2 , 4 , 2 , 5 } ;
int target = 2;

/ / Use s t d : : c o u n t t o c o u n t t h e o c c u r r e n c e s o f t h e t a r g e t v a l u e
i n t count = s t d : : count ( numbers . b e g i n ( ) , numbers . end ( ) , t a r g e t ) ;

s t d : : c o u t << " V a l u e " << t a r g e t << " a p p e a r s " << count << " t i m e s . " << s t d : : e n d l ;

return 0 ;
}

In this example:

• We include the necessary headers for vectors, algorithms, and I/O.

• We create a vector of integers called numbers and initialize it with some values.

• We specify the target value we want to count (in this case, 2).

• We use std::count to count the occurrences of the target value within the range
defined by numbers.begin() and numbers.end().

The result of running this code will be the number of times the target value appears
in the vector.
std::count is a convenient algorithm when you need to quickly determine how many
times a specific value occurs in a collection, and it’s commonly used in tasks like data
analysis and statistics.

Lab 9: Exploring C++ Standard Library Containers


1. Objective To gain hands-on experience with C++ Standard Library containers and
understand their basic operations.

2. Task 1: Vector Operations

• Create a C++ program that includes the necessary headers (nclude <iostream>
and <vector>).
• Declare a std::vector<int> called numbers and initialize it with the integers 1
to 5.
• Use a for loop to print the contents of the numbers vector.
• Add two more integers to the numbers vector using the push_back method.
• Print the modified numbers vector.

3. Task 2: List Operations

• Include the <list> header in your program.


• Declare a std::list<char> called characters and initialize it with the characters
’A’ to ’E’.
122 L E C T URE 9: STANDARD TEMPL ATE LIBRARY (STL)

• Use an iterator to print the contents of the characters list in reverse order
(from ’E’ to ’A’).
• Insert a new character ’F’ at the beginning of the characters list.
• Print the modified characters list.

4. Task 3: Map Operations

• Include the <map> header.


• Declare a std::map<std::string, int> called grades.
• Add at least five key-value pairs representing student names and their corre-
sponding grades.
• Use an iterator to print the student names and grades from the grades map.
• Remove one entry from the grades map.
• Print the modified grades map.

5. Task 4: Set Operations

• Include the <set> header.


• Declare a std::set<double> called scores and initialize it with some random
scores.
• Print the scores in ascending order using a for loop.
• Remove a specific score from the scores set.
• Print the modified scores set.

6. Task 5: Container Algorithms

• Include the <algorithm> header.


• Use the std::find algorithm to search for a specific value within one of your
containers.
• Use the std::count algorithm to count the occurrences of an element in an-
other container.

Practice Questions
1. Explain the primary difference between std::vector and std::list in terms of memory
allocation and element access.

2. What is the purpose of std::unordered_set, and what is the key advantage of using
it over std::set?
Lecture 10: Files and Streams in C++

In computer programming, dealing with data is a fundamental task. Data can be read
from various sources, processed, and then written back to storage. C++ provides a
versatile and powerful mechanism for working with external files through the concept
of files and streams. Streams provide a unified interface for reading and writing data,
whether it’s coming from a file, the console, or any other source.
This chapter explores the basics of files and streams in C++, including file input and
output operations, stream classes, and error handling. By the end of this chapter, you
will have a solid understanding of how to work with files and streams in C++.

Understanding Streams
What Are Streams?
In C++, a stream is an abstraction that represents a sequence of characters or bytes.
Streams are a flexible way to handle input and output operations. They can be associated
with various devices, such as the standard input (keyboard), standard output (screen),
and external files.
Streams can be categorized into two main types:

• Input Streams: These are used for reading data from a source, such as a file or the
keyboard. The standard input stream is represented by cin.
• Output Streams: These are used for writing data to a destination, such as a file or
the screen. The standard output stream is represented by cout.

C++ provides a set of predefined stream objects for common input and output oper-
ations. These include cin (standard input), cout (standard output), cerr (standard error
output), and clog (standard logging output).

Stream Classes
Streams in C++ are implemented as classes within the Standard Library. The primary
stream classes are:

• istream: This class is used for input operations and includes functions for reading
data.
• ostream: This class is used for output operations and includes functions for writing
data.
• ifstream: This class is derived from istream and is used specifically for file input
operations.
124 L E C T URE 10: FILES AND STREAMS IN C++

• ofstream: This class is derived from ostream and is used specifically for file output
operations.
• fstream: This class is derived from both ifstream and ofstream and is used for both
input and output operations on files.
These stream classes provide member functions that allow you to perform various
operations like reading and writing data, positioning the file pointer, and handling errors.

Stream States and Flags


Streams can be in various states depending on their current condition. Common stream
states include:
• . good(): Indicates that the stream is in a good state and ready for further opera-
tions.
• eof(): Indicates that the end of the stream has been reached.
• fail(): Indicates that a failure occurred during the last operation.
• bad(): Indicates that the stream is in an unrecoverable bad state.
Stream states are often checked using stream flags. The clear() member function is
used to clear error flags, and the setstate() member function is used to set specific flags.

File Input and Output


Opening and Closing Files
Before you can read from or write to a file, you must open it. To open a file for reading
or writing, you use an ifstream (for input) or ofstream (for output) object, respectively.
# include <fstream >
using namespace s t d ;

i n t main ( ) {
/ / Open a f i l e f o r r e a d i n g
ifstream i n p u t F i l e ( " input . t x t " ) ;

/ / Open a f i l e f o r w r i t i n g ( c r e a t e s a new f i l e i f i t doesn ’ t e x i s t )


ofstream o u t p u t F i l e ( " output . t x t " ) ;

/ / Perform o p e r a t i o n s on t h e f i l e s . . .

/ / Close the f i l e s
inputFile . close ( ) ;
outputFile . close ( ) ;

return 0 ;
}
Always remember to close files when you’re done with them to release system re-
sources.
Reading from Files
To read data from a file, use the » operator with an ifstream object.
# include <iostream >
# include <fstream >
using namespace s t d ;

i n t main ( ) {
ifstream i n p u t F i l e ( " input . t x t " ) ;
i n t number ;

i f ( i n p u t F i l e . is_open ( ) ) {
while ( i n p u t F i l e >> number ) {
c o u t << " Read : " << number << e n d l ;
}
inputFile . close ( ) ;
} else {
c e r r << " F a i l e d t o open t h e f i l e . " << e n d l ;
}

return 0 ;
}

Writing to Files
To write data to a file, use the « operator with an ofstream object.
# include <iostream >
# include <fstream >
using namespace s t d ;

i n t main ( ) {
ofstream o u t p u t F i l e ( " output . t x t " ) ;

i f ( outputFile . is_open ( ) ) {
o u t p u t F i l e << " H e l l o , F i l e ! " << e n d l ;
outputFile . close ( ) ;
} else {
c e r r << " F a i l e d t o open t h e f i l e . " << e n d l ;
}

return 0 ;
}

Error Handling
Handling errors when working with files is crucial to ensure the reliability of your pro-
grams. C++ provides mechanisms to check and handle errors during file operations.

Checking for Errors


You can use the fail() member function to check if a stream operation has failed.
126 L E C T URE 10: FILES AND STREAMS IN C++

ifstream i n p u t F i l e ( " input . t x t " ) ;


if ( inputFile . f a i l ( ) ) {
c e r r << " F a i l e d t o r ea d from t h e f i l e . " << e n d l ;
}

Exception Handling
C++ also supports exception handling for more robust error management. You can use
try, catch, and throw statements to handle exceptions that occur during file operations.
try {
ifstream i n p u t F i l e ( " input . t x t " ) ;
if ( ! inputFile ) {
throw r u n t i m e _ e r r o r ( " F a i l e d t o r e ad from t h e f i l e . " ) ;
}
/ / Read from t h e f i l e . . .
} catch ( const r u n t i m e _ e r r o r& e ) {
c e r r << " E r r o r : " << e . what ( ) << e n d l ;
}

Working with Binary files


Binary files are files that contain non-text data, such as images, audio, or program
executables. These files are not human-readable and must be handled differently than
text files. In C++, you can work with binary files using file streams, specifically ifstream
and ofstream, just like you do with text files. However, you need to be aware of the
differences in handling binary data.
Here’s a guide on how to work with binary files using C++ streams:

Writing Binary Data to a File


To write binary data to a file, you’ll typically use an ofstream object in binary mode.
Binary mode is essential because it prevents any character encoding translations that
might occur in text mode.
# include <iostream >
# include <fstream >
using namespace s t d ;

i n t main ( ) {
/ / Open a b i n a r y f i l e f o r w r i t i n g
o f s t r e a m o u t p u t F i l e ( " b i n a r y _ d a t a . d a t " , i o s : : out | i o s : : b i n a r y ) ;

i f ( outputFile . is_open ( ) ) {
/ / Data t o be w r i t t e n
int integerData = 42;
double doubleData = 3 . 1 4 1 5 9 ;

/ / W r i t e b i n a r y data
o u t p u t F i l e . w r i t e ( r e i n t e r p r e t _ c a s t < char * >(& i n t e g e r D a t a ) , s i z e o f ( i n t ) ) ;
o u t p u t F i l e . w r i t e ( r e i n t e r p r e t _ c a s t < char * >(& doubleData ) , s i z e o f ( double ) ) ;
/ / Close the f i l e
outputFile . close ( ) ;
c o u t << " Data w r i t t e n t o b i n a r y f i l e . " << e n d l ;
} else {
c e r r << " F a i l e d t o open t h e f i l e . " << e n d l ;
}

return 0 ;
}

In this example:

1. We open a file named "binary_data.dat" in binary mode using ios::out | ios::binary


as the mode flags.

2. We prepare some binary data (an integer and a double) that we want to write to
the file.

3. We use the write() method to write the binary data. Note the use of reinterpret_cast
to convert the data pointers to char* pointers, allowing us to write raw bytes to
the file.

4. Finally, we close the file.

Reading Binary Data from a File


Reading binary data from a file involves using an ifstream object in binary mode, similar
to writing.
# include <iostream >
# include <fstream >
using namespace s t d ;

i n t main ( ) {
/ / Open t h e b i n a r y f i l e f o r r e a d i n g
i f s t r e a m i n p u t F i l e ( " binary_data . dat " , i o s : : in | i o s : : binary ) ;

i f ( i n p u t F i l e . is_open ( ) ) {
/ / Data t o s t o r e t h e r e a d v a l u e s
int readInteger ;
double readDouble ;

/ / Read b i n a r y data
i n p u t F i l e . r ea d ( r e i n t e r p r e t _ c a s t < char * >(& r e a d I n t e g e r ) , s i z e o f ( i n t ) ) ;
i n p u t F i l e . r ea d ( r e i n t e r p r e t _ c a s t < char * >(& readDouble ) , s i z e o f ( double ) ) ;

if ( ! inputFile . f a i l ( ) ) {
c o u t << " Read I n t e g e r : " << r e a d I n t e g e r << e n d l ;
c o u t << " Read Double : " << readDouble << e n d l ;
} else {
c e r r << " F a i l e d t o r ea d d a t a from t h e f i l e . " << e n d l ;
}

/ / Close the f i l e
128 L E C T URE 10: FILES AND STREAMS IN C++

inputFile . close ( ) ;
} else {
c e r r << " F a i l e d t o open t h e f i l e . " << e n d l ;
}

return 0 ;
}

In this example:

1. We open the same file "binary_data.dat" for reading, using ios::in | ios::binary as
the mode flags.

2. We declare variables to store the data we will read from the file.

3. We use the read() method to read the binary data, again using reinterpret_cast to
convert the data pointers to char* pointers.

4. We check for errors using the fail() method. If the read was successful, we print
the read values.

5. Finally, we close the file.

Binary File Considerations


• When working with binary files, it’s crucial to specify the correct data types and
sizes when reading and writing. The use of sizeof() ensures that the correct number
of bytes is processed.

• Endianness (byte order) can be an issue when reading/writing binary data across
different systems. Be cautious when sharing binary files between systems with
different endianness.

• Binary files may not be portable between different compilers or platforms due to
differences in data representation and padding.

• Binary files are not human-readable, so ensure proper error handling and version
control when working with them.

Working with binary files in C++ allows you to handle non-text data efficiently. How-
ever, it requires careful management of data types, sizes, and endianness to ensure data
integrity when reading and writing binary files.

Lab 10: C++ Streams: Text and Binary File Operations


• Lab Objectives:

1. Understand the basics of C++ streams.


2. Learn how to work with text files.
3. Learn how to work with binary files.
4. Practice error handling when working with files.
• Text File Operations Create a program that opens a text file named "text_file.txt"
for writing and writes the following lines to the file:
H e l l o , World !
This i s a t e x t f i l e .
C++ s t r e a m s a r e awesome !

Ensure proper error handling for file opening. Create another program that opens
"text_file.txt" for reading and displays its contents on the console.
• Binary File Operations
1. Create a program that opens a binary file named "binary_data.dat" for writing.
Write the following data to the file:n integer with the value 42. A double with
the value 3.14159.
2. Create another program that opens "binary_data.dat" for reading. Read the
data and display it on the console
• Error Handling
• Modify the programs in Task 1 and Task 2 to include proper error handling for file
opening, reading, and writing. Ensure that error messages are displayed if any
errors occur.
• User Interaction
1. Create a program that allows the user to enter text interactively and write it
to a text file named "user_text.txt." Allow the user to enter multiple lines of
text, and write them to the file. Use a loop to continue taking input until the
user decides to stop.
2. Create another program that reads the contents of "user_text.txt" and displays
them on the console.
• Binary File Extension

1. Extend the binary file program from Task 2 to write and read additional data
types such as float, char, and bool.
2. Ensure proper error handling for all file operations, and display the read data
on the console.

• Lab Completion:

1. Test each program thoroughly to ensure proper functionality and error han-
dling.
2. Verify that text and binary files are created, written to, and read from correctly.
3. Ensure that all error messages are informative and helpful.
4. Demonstrate understanding of both text and binary file operations and error
handling.
130 L E C T URE 10: FILES AND STREAMS IN C++

Practice Questions
1. What is the purpose of C++ streams, and why are they essential in programming?
2. What is the difference between text mode and binary mode when working with
C++ streams?
3. How do you open a text file for reading in C++ using streams? Provide an example.
4. How can you check if a file stream (ifstream or ofstream) was opened successfully?
What happens if the stream fails to open?
5. Describe the process of writing text to a file in C++ using an ofstream object.
Provide an example.
6. How do you read text from a file in C++ using an ifstream object? Provide an
example.
7. Explain the concept of end-of-file (EOF) when reading from a file. How do you
detect EOF in C++?
8. What are the common stream states that can be checked using member functions
like good(), eof(), fail(), and bad()? How are they used?
9. How do you open a binary file for writing in C++ using streams? Provide an exam-
ple.
10. What is the purpose of write() and read() methods when working with binary files
in C++? Provide examples of using these methods.
11. Explain the importance of proper error handling when working with file streams
in C++. What mechanisms can be used for error handling?
12. Describe how you can use C++ streams to read user input from the console and
write it to a text file. Provide an example.
13. What precautions should be taken when sharing binary files between systems with
different endianness (byte order)?
14. How do you close a file stream in C++? Why is it important to close files properly?
15. What are the advantages of using C++ streams over traditional C-style file handling
functions (e.g., fopen, fread, fwrite)?

TODO:
• add a chapter about UML and how this is related to OOP
• add part of the project after each chapter, should be progressed while chapters are
moving
• add a chapter about writing GUI applications using C++

You might also like