Oops Basics
Oops Basics
In this lesson, we'll learn about the historical background of OOP and also key features of object-oriented
programming.
• Historical Background
• Object: A Fundamental Entity
• Example
• Explanation
Historical Background #
It was in the 60’s and early 70’s when the idea of object-oriented
programming started occupying the minds of programmers. Keeping in view
the benefits acquired through object orientation in SIMULA, the scientists
encouraged languages using the same approach for programming. Object-
oriented programming was a complete paradigm shift from the popular
structural programming.
Example #
A fork is an object with properties including a number of prongs, its size, and
material (made of plastic or metal), etc. Behavior and functions of a fork
include shredding, squashing, making design or may be simply eating.
Steel
Prongs
Size
Fork
Explanation #
Programmers realized that when we represent entities in the program as
objects, having their behaviors and properties, it becomes easy to deal with
the increasing code complexity as well as the code becomes more reusable. So,
a fork object can be part of a dinner set, and a similar object may also be sold
separately. Once, we know its properties and behavior, we only need to reuse
the same piece of code whenever a fork is needed.
This lesson deals with the nature of the arguments being passed to a function.
• Pass-by-Value
• Pass-by-Reference
Pass-by-Value #
The compiler never actually sends the variables into the function. Instead, a
copy of each argument is made and used in the scope of the function. This
concept is known as passing-by-value. Only the values of the arguments are
passed into the functions, not the arguments themselves.
When the function ends, all the argument copies and the variables created
inside the function are destroyed forever. Basically, changing the value of an
argument inside a function won’t change it outside the function.
Have a look at the function below, which multiplies its argument by 10:
#include <iostream>
using namespace std;
int main(){
int x = 10;
Just as we discussed, the value of x did not change outside the function scope.
The illustration below will help up understanding this better.
multiplyBy10(x)
10
x
multiplyBy10()
The function
is called
1 of 5
10
10
x
multiplyBy10()
The value of x
is sent to the
function
2 of 5
10 10
x
multiplyBy10()
A copy of x is
used in the
function scope
3 of 5
10 100
x
multiplyBy10()
The copy is
multiplied by 10
4 of 5
5 of 5
This is normal behavior in C++ but in some cases, we might want a function to
manipulate variables outside its scope. Well, there is a solution for that.
Pass-by-Reference #
This is the second approach of passing elements to functions. As we know,
every variable is stored at an address in the memory.
To access the address of a variable, we need to add the & operator before it.
Let’s refactor the multiplyBy10 so that we pass x by reference.
#include <iostream>
using namespace std;
int main(){
int x = 10;
// Multiplying by 10
multiplyBy10(x);
Voila! With a simple & , we can allow a function to edit things outside its
scope. Let’s see what happens when we pass arguments by reference:
multiplyBy10(x)
10
x
multiplyBy10()
The function
is called
1 of 4
10
x
multiplyBy10()
The value of x
is sent to the
function
2 of 4
100
x
multiplyBy10()
x is multiplied
by 10
3 of 4
100
x
The function
ends, and the
value of x has
changed
4 of 4
In this lesson, we'll see the same function perform different operations based on its arguments.
• What is Overloading?
• Advantages of Function Overloading
#include <iostream>
using namespace std;
int main() {
cout << product(10, 20) << endl; // Works fine
cout << product(10) << endl; // Error!
}
What is Overloading? #
Let’s see this in action by overloading the product function which we just
wrote:
#include <iostream>
using namespace std;
int main() {
double x = 10;
double y = 20;
double z = 5;
float a = 12.5;
float b = 4.654;
cout << product(x, y) << endl;
cout << product(x, y, z) << endl;
cout << product(a, b) << endl;
// cout << product(x) << endl;
}
In the code above, we see the same function behaving differently when
encountering different types of inputs. We still have to define which cases it
can handle. Line 27 would crash since product doesn’t know how to handle a
single argument.
Note: Functions which have no arguments and differ only in the return
types cannot be overloaded since the compiler won’t be able to
differentiate between their calls.
An obvious benefit is that the code becomes simple and clean. We don’t have
to keep track of different functions.
Increases
execution
speed
Makes
Allows the
code
implementation
cleaner Overloading
of
and
polymorphism
readable
Same
function
name
saves
memory
We know enough about function in order to delve deeper into the principles
of OOP. In the next chapter, we will explore pointers.
Before that, be sure to check out the quiz and exercises ahead to test your
concepts of functions. Good luck!
Pointers and Dynamic Memory
So far, we've only discussed the behavior of pointers with the stack. Let's see how pointers can be used in
dynamic memory.
We can specify the amount of space we want, and a random portion of the
heap will be reserved for us. While the heap does allow us to use as much
space as we want, the look-up operation is slower compared to a stack.
However, a variable in dynamic memory (heap) can have a “global” scope
instead of just being a local variable for a function.
#include <iostream>
using namespace std;
int main() {
int *p = new int; // dynamic memory reserved for an integer
*p = 10; // the object is assigned the value of 10
cout << "The value of the object p points to: " << *p << endl;
double *arr = new double[500]; // an array of size 500 has been created in the heap
arr[0] = 50;
cout << "arr[0]: " << arr[0] << endl;
In the code above, the new objects are created during runtime (instead of
compile time). This is an advantage since we don’t need to specify the amount
of memory we need at compile time.
We’ve learned the basics about the nature of pointers. In the next lesson, we’ll
take a look at the interaction between pointers and functions.
What is a Class?
This section will familiarize us with the basic building blocks of object-oriented programming: Classes.
• Custom Objects
• Data Members
• Member Functions
• Bene ts of Using Classes
Custom Objects #
In C++, we have several different data types like int , string , bool etc. An
object can be created out of any of those types. An object is an instance of a
class. Well, object-oriented programming wouldn’t make sense if we couldn’t
make our own custom objects. This is where classes come into play.
Classes are used to create user-defined data types. The predefined data types in
C++ are classes themselves. We can use these basic data types to create our
own class. The cool part is that our class can contain multiple variables,
pointers, and functions which would be available to us whenever a class
object is created.
Let’s start off with an example of a car class. Below, we can see the attributes
that a car object would contain:
Car Class
Data Member
Members Functions
TopSpeed Refuel()
NumberOfSeats Drive()
FuelCapacity Park()
Manufacturer Indicator()
Car Class
We can see two types of attributes in the Car class above. In general, these
two categories are present in all classes.
Data Members #
These are also known as the member variables of a class. This is because they
contain the information relevant to the object of the class. A car object would
have a top speed, the number of seats it has, and so many other pieces of data
that we could store in variables.
Member Functions #
This category of attributes enables the class object to perform operations
using the member variables. In the case of the car class, the Refuel() function
would fill up the FuelTank property of the object.
In the next lesson, we will start our journey into creating a class.
Access Modifiers
In this lesson, you will learn about the private, public and protected members.
• Private
• Public
• Protected
There are three types of access modifiers. Let’s take a look at them one by one.
Private #
A private member cannot be accessed directly from outside the class. The aim
is to keep it hidden from the users and other classes. It is a popular practice to
keep the data members private since we do not want anyone manipulating
our data directly. By default, all declared members are private in C++.
However, we can also make members private using the private: heading.
class Class1 {
int num; // This is, by default, a private data member
...
};
class Class2 {
private: // We have explicitly defined that the variable is private
int num;
...
};
Public #
This tag indicates that the members can be directly accessed by anything
which is in the same scope as the class object.
Member functions are usually public as they provide the interface through
which the application can communicate with our private members.
class myClass {
int num; // Private variable
Class
Private Public
variables functions
Other
components
of the
application
Protected #
The protected category is unique. The access level to the protected members
lies somewhere between private and public. The primary use of the protected
tag is to implement inheritance, which is the process of creating classes out of
classes. Since this is a whole other topic for the future, we’ll refrain from
going into details right now.
We’ve seen a hint of how data members can be created in a class. In the next
lesson, we will go into further details on the topic.
Constructors
In this lesson, we explore the world of constructors and learn why they are necessary for a class.
• What is a Constructor?
• Default Constructor
• Parameterized Constructor
• this Pointer
What is a Constructor? #
As the name suggests, the constructor is used to construct the object of a class.
It is a special member function that outlines the steps that are performed
when an instance of a class is created in the program.
A constructor’s name must be exactly the same as the name of its class.
The constructor is a special function because it does not have a return type.
We do not even need to write void as the return type. It is a good practice to
declare/define it as the first member function.
So, let’s study the different types of constructors and use them to create class
objects.
Default Constructor #
The default constructor is the most basic form of a constructor. Think of it this
way:
This will make sense when we look at the code below. Here, we have a Date
class, with its default constructor, and we’ll create an object out of it in
main() :
#include <iostream>
#include <string>
using namespace std;
class Date {
int day;
int month;
int year;
public:
// Default constructor
Date(){
// We must define the default values for day, month, and year
day = 0;
month = 0;
year = 0;
}
int main(){
// Call the Date constructor to create its object;
Notice that when we created a Date object in line 28, we don’t treat the
constructor as a function and write this:
d.Date()
We create the object just like we create an integer or string object. It’s that
easy!
The default constructor does not need to be explicitly defined. Even if we
don’t create it, the C++ compiler will call a default constructor and set data
members to null or 0 .
Parameterized Constructor #
The default constructor isn’t all that impressive. Sure, we could use set
functions to set the values for day , month and year ourselves, but this step
can be avoided using a parameterized constructor.
#include <iostream>
#include <string>
using namespace std;
class Date {
int day;
int month;
int year;
public:
// Default constructor
Date(){
// We must define the default values for day, month, and year
day = 0;
month = 0;
year = 0;
}
// Parameterized constructor
Date(int d, int m, int y){
// The arguments are used as values
day = d;
month = m;
year = y;
}
this Pointer #
The this pointer exists for every class. It points to the class object itself. We
use the pointer when we have an argument which has the same name as a
data member. this->memberName specifies that we are accessing the
memberName variable of the particular class.
Note: Since this is a pointer, we use the -> operator to access members
instead of . .
#include <iostream>
#include <string>
using namespace std;
class Date {
int day;
int month;
int year;
public:
// Default constructor
Date(){
// We must define the default values for day, month, and year
day = 0;
month = 0;
year = 0;
}
// Parameterized constructor
Date(int day, int month, int year){
// Using this pointer
this->day = day;
this->month = month;
this->year = year;
}
// A simple print function
void printDate(){
cout << "Date: " << day << "/" << month << "/" << year << endl;
}
};
int main(){
// Call the Date constructor to create its object;
This marks the end of our discussion on constructors. In the next lesson, we
will discuss the opposite of constructors, destructors.
Destructors
• What is a Destructor?
• Explicit Garbage Collection
• Example
• Destructors and Pointers
• Destroying Objects is Important
What is a Destructor? #
A destructor is the opposite of a constructor. It is called when the object of a
class is no longer in use. The object is destroyed and the memory it occupied
is now free for future use.
C++ does not have transparent garbage collection like Java . Hence, in order
to efficiently free memory, we must specify our own destructor for a class.
class Date {
int day;
int month;
int year;
public:
// Default constructor
Date(){
// We must define the default values for day, month, and year
day = 0;
month = 0;
year = 0;
}
// Parameterized constructor
Date(int d, int m, int y){
// The arguments are used as values
day = d;
month = m;
year = y;
}
~Date(){
cout << "Deleting date object" << endl;
}
};
int main(){
Date d(1, 8, 2018);
d.printDate();
}
As we can see, the destructor is automatically called and the memory is freed
up. What’s interesting is that the cout statement we specified is also executed
on the destructor call.
Application
The destructor
destroys the
object when it is
no longer in use
#include <iostream>
#include <string>
using namespace std;
class Date {
int day;
int month;
int year;
public:
// Default constructor
Date(){
// We must define the default values for day, month, and year
day = 0;
month = 0;
year = 0;
}
// Parameterized constructor
Date(int d, int m, int y){
// The arguments are used as values
day = d;
month = m;
year = y;
}
~Date(){
cout << "Deleting date object" << endl;
}
};
int main(){
Date* d = new Date(1, 8, 2018); // Object created in dynamic memory
d->printDate();
delete d;
cout << "End of program" << endl;
}
In the next lesson, we will learn about friend functions and their uses.
Friend Functions
So far we have observed that the private data members of a class are only
accessible through the functions present in that class. Nothing from outside
can manipulate the class object without using its functions.
To create a friend function for a class, it must be declared in the class along
with the friend keyword.
#include <iostream>
#include <string>
class Ball{
double radius;
string color;
public:
Ball(){
radius = 0;
color = "";
}
void printVolume();
void printRadius();
// The friend keyword specifies that this is a friend function
friend void setRadius(Ball &b, double r);
};
void Ball::printRadius(){
cout << radius << endl;
}
int main(){
Ball b(30, "green");
cout << "Radius: ";
b.printRadius();
setRadius(b, 60);
cout << "New radius: ";
b.printRadius();
cout << "Volume: ";
b.printVolume();
}
In line 25, we can see that the Ball object is being passed by reference to the
friend function. This is a crucial step in the functionality of the friend. If the
object is not passed by reference, the changes made in the friend function will
not work outside its scope. Basically, our object will not be altered.
This concludes our discussion on the basics of the classes in C++. The next
section deals with the concept of data hiding, which plays a pivotal role in
implementing efficient classes.
Creating a Function
In this lesson, we will learn how to create functions in C++ and use them in our program.
• Declaration
• De nition
Declaration #
The declaration of a function means defining its name, type, and
argument(s). This may sound confusing right now but we’ll get the hang of it
really soon. Here’s the template for function declaration:
type functionName(argument(s));
type refers to the type of value the function produces. In formal terms,
we say that a function returns something when we use it. The type of
object it returns must be specified in the declaration. So, if a function
returns a number, its type would be int or double etc. Any data type
available in C++ can be used as the function’s type. If the function does
not return anything, its type will be void .
functionName is simply the label we’ll use for the function, just like we do
for variables.
function could also have no arguments at all. In that case, the arguments
section remains empty.
Now that we’ve been through all the components of function declaration, let’s
see a few examples:
string blah(); // Returns a string and does not contain any arguments
De nition #
The definition (also known as implementation) of a function refers to the set
of instructions which the function performs. Without the definition, a
function will not know what to do. Hence, we need to make sure our
implementation is flawless and doesn’t produce any bugs. Here’s the template
for the definition of a function:
type functionName(arguments) {
// Definition
}
The curly braces, {} , contain the definition of a function. This is known as the
scope of the function (more on this later). Here, we can write a normal C++
code which will execute once every time the function is called.
You may have noticed by now that the int main(){} in a C++ program is also a
function! It is the function that the compiler runs to execute our code.
To return something from a function, we use the return command. This ends
the function.
Below, we’ve defined a few functions and called them in the main program.
The first approach only uses the definition to create a function. The second
approach first declares the functions and then defines them below the main() .
#include <iostream>
#include <string>
using namespace std;
string blah(){
string s = "Hello World";
return s;
}
int main()
{
int x = 10;
int y = 20;
int total = sum(x, y); // We store the returned value into a variable
// of the corresponding type
cout << total <<endl;
At this point, we’re equipped to write basic functions. The basic structure of
the functions is clear. Remember, a function can call other functions as well.
After all, the main function does that as well.
In the next lesson, we will learn some interesting details about the arguments
of a function.
Encapsulation
This lesson shows us how to implement the rst component of data hiding: encapsulation.
class Movie{
string title;
int year;
string genre;
public:
Movie(){
title = "";
year = -1;
genre = "";
}
There must be a way to interact with the title , year and genre variables.
They hold all the information about a movie, but how do we access or modify
them?
We could create a getTitle() method which would return the title to us.
Similarly, the other two members could also have corresponding get
functions.
By observing the emerging pattern, we can make a definitive conclusion.
These functions should be part of the class of itself! Let’s try it out.
#include <iostream>
#include <string>
using namespace std;
class Movie{
string title;
int year;
string genre;
public:
Movie(){
title = "";
year = -1;
genre = "";
}
string getTitle(){
return title;
}
void setTitle(string t){
title = t;
}
int getYear(){
return year;
}
void setYear(int y){
year = y;
}
string getGenre(){
return genre;
}
void setGenre(string g){
genre = g;
}
void printDetails(){
cout << "Title: " << title << endl;
cout << "Year: " << year << endl;
cout << "Genre: " << genre << endl;
}
};
int main() {
Movie m("The Lion King", 1994, "Adventure");
m.printDetails();
cout << "---" << endl;
m.setTitle("Forrest Gump");
Advantages of Encapsulation #
Classes are easier to change and maintain.
We can specify which data member we want to keep hidden or
accessible.
We decide which variables have read/write privileges (increases
flexibility).
In the next lesson, we’ll discuss the second component of data hiding:
abstraction.
Abstraction in Classes
This lesson will de ne what abstraction is and how it relates to data hiding.
• What is Abstraction?
• Class Abstraction
What is Abstraction? #
Users will perform actions and expect the application to respond accordingly.
They will not be concerned with how the response in generated. Only the final
outcome is relevant.
Class Abstraction #
So, let’s put all this theory into practice. In the code below, we have a basic
class for a circle:
class Circle{
double radius;
double pi;
};
It has two variables, radius and pi . Now let’s add the constructor and
functions for the area and perimeter:
#include <iostream>
using namespace std;
class Circle{
double radius;
double pi;
public:
Circle (){
radius = 0;
pi = 3.142;
}
Circle(double r){
radius = r;
pi = 3.142;
}
double area(){
return pi * radius * radius;
}
double perimeter(){
return 2 * pi * radius;
}
};
int main() {
Circle c(5);
cout << "Area: " << c.area() << endl;
cout << "Perimeter: " << c.perimeter() << endl;
}
As you can see, we only need to define the radius of the circle in the
constructor. After that, the area() and perimeter() functions are available to
us. This interface is part of encapsulation. However, the interesting part is
what happens next.
All we have to do is call the functions and voila, we get the area and perimeter
as we desire. Users would not know what computations were performed
inside the function. Even the pi constant will be hidden to them. Only the
input and the output matter. This is the process of abstraction using classes.
The second strategy for implementing abstraction is creating header les. Find out more below!
In the last lesson, we created the Circle class which had two functions,
area() and perimeter() . At that point, all the code was in a single file. Since
our goal is to hide the unnecessary details from the users, we can divide the
code into different files. This is where header files come into play.
#include <iostream>
using namespace std;
class Circle{
double radius;
double pi;
public:
Circle (){
radius = 0;
pi = 3.142;
}
Circle(double r){
radius = r;
pi = 3.142;
}
double area(){
return pi * radius * radius;
}
double perimeter(){
return 2 * pi * radius;
}
};
int main() {
Circle c(5);
cout << "Area: " << c.area() << endl;
To hide our class, we will follow a few steps. The first step is to create a header
file. This file will only contain the declaration of the class and its members. A
header file always has the .h extension:
#include <iostream>
main.cpp
using namespace std;
public:
Circle (){
radius = 0;
pi = 3.142;
}
Circle(double r){
radius = r;
pi = 3.142;
}
double area(){
return pi * radius * radius;
}
double perimeter(){
return 2 * pi * radius;
}
};
int main() {
Circle c(5);
cout << "Area: " << c.area() << endl;
cout << "Perimeter: " << c.perimeter() << endl;
}
As you can see, the header file isn’t very useful if the complete
implementation is still visible in our main file. Therefore, the second step is to
move all the implementation to a separate file. Let’s call this Circle.cpp .
In this file, we must include the header file. The include command should
already be familiar to you. We use it all the time to include libraries like
iostream or vector . We can include header files in the same way!
Since we’re implementing all the methods of our Circle class in Circle.cpp ,
we must mention the name of the class along with the scope resolution
operator ( :: ). Let’s do this now:
#include <iostream>
main.cpp
using namespace std;
public:
Circle (){
radius = 0;
pi = 3.142;
}
Circle(double r){
radius = r;
pi = 3.142;
}
double area(){
return pi * radius * radius;
}
double perimeter(){
return 2 * pi * radius;
}
};
int main() {
Circle c(5);
cout << "Area: " << c.area() << endl;
cout << "Perimeter: " << c.perimeter() << endl;
}
At this point, everything is in place. We can remove the Circle class from our
main.cpp . All we have to do is include the header file and the compiler will
handle the rest:
#include <iostream>
main.cpp
#include "./Circle.h"
Circle.cpp
int main() {
Circle c(5);
cout << "Area: " << c.area() << endl;
cout << "Perimeter: " << c.perimeter() << endl;
}
#ifndef CIRCLE_H
#define CIRCLE_H
These commands tell the compiler that this header file can be used in multiple
places. The #ifndef command ends with #endif .
In this lesson, we'll be learning about the core concept of the object-oriented paradigm, i.e., Inheritance and why
there is a need for it?
Vehicle Class #
In a Vehicle class, we have many data members like Make, Color, Year and
Model. A Vehicle HAS-A Model, Year, Color and Make.
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model = "";
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
cout << "Model: " << Model << endl;
}
};
int main(){
Vehicle v("Ford Australia", "Yellow", 2008, "Falcon");
v.print_details();
}
Year
Vehicle(string mk, string col,
Color
Make
Print_details()
These attributes are also attributes of all Cars, Ships and Airplanes but every
type of vehicle has some attributes that are different from other types of
vehicles, as we will see in detail.
Cars Class #
The implementation of a Cars class needs the same data members and
member functions of Vehicle class but we have to include them in the Cars
class. Cars do have a trunk and every trunk has a capacity to store things upto
some limit.
class Cars{
string Make;
string Color;
int Year;
string Model;
string trunk_size;
public:
Cars(){
Make = "";
Color = "";
Year = 0;
Model = "";
trunk_size = "";
}
Cars(string mk, string col, int yr, string mdl, string ts){
Make = mk;
Color = col;
Year = yr;
Model = mdl;
trunk_size = ts;
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
cout << "Model: " << Model << endl;
cout << "Trunk size: " << trunk_size << endl;
}
};
int main(){
Cars car("Chevrolet", "Black", 2010, "Camaro", "9.1 cubic feet");
car.print_details();
}
Year Cars()
Model
Cars Color Cars(string mk, string col,
Make
int yr, string mdl, string ts)
trunk_size
Print_details()
Ships Class #
The implementation of a Ships class needs the same data members and
member functions of Vehicle class but we have to include them in the Ships
class. Ships do have anchors and they vary in numbers.
class Ships{
string Make;
string Color;
int Year;
string Model;
int Number_of_Anchors;
public:
Ships(){
Make = "";
Color = "";
Year = 0;
Model = "";
Number_of_Anchors = 0;
}
Ships(string mk, string col, int yr, string mdl, int na){
Make = mk;
Color = col;
Year = yr;
Model = mdl;
Number_of_Anchors = na;
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
cout << "Model: " << Model << endl;
cout << "Number of Anchors: " << Number_of_Anchors << endl;
}
};
int main(){
Ships ship("Harland and Wolff, Belfast", "Black and whilte",
1912, "RMS Titanic", 3);
ship.print_details();
}
Make
No_of_Anchors
Print_details()
In the declared classes for different types of vehicles ( Cars and Ships ), we
have many repetitive attributes which should be in one base class and should
be inherited in the derived classes.
In the next lesson, we’ll be learning about base class and derived class.
Base Class and Derived Class
In this lesson, we'll be learning about how a base class attributes are available to the derived classes and how to
de ne base and a derived class.
In the last lesson, we have seen that Vehicle class attributes are shared by the
other two classes( Cars and Ships ).
Derived Classes #
Cars and Ships are considered as derived classes as they’re inheriting
properties from vehicle class.
Modes of Inheritance #
There are three modes of class inheritance: public , private and protected .
The basic syntax for inheritance is given below:
Public Inheritance #
We are updating our Cars and Ships class so that Make, Color, Year, Model
and the function void print_details() can be inherited from base class
Vehicle . So, we have removed these variables and function from the derived
classes. The following example shows the classes Cars and Ships that inherits
publicly from the base class Vehicle .
class Vehicle{
protected:
string Make;
string Color;
int Year;
string Model;
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model = "";
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
cout << "Model: " << Model << endl;
}
};
public:
Cars(){
trunk_size = "";
}
Cars(string mk, string col, int yr, string mdl, string ts)
:Vehicle(mk, col, yr, mdl){
trunk_size = ts;
}
void car_details(){
print_details();
cout << "Trunk size: " << trunk_size << endl;
}
};
public:
Ships(){
Number_of_Anchors = 0;
}
Ships(string mk, string col, int yr, string mdl, int na)
:Vehicle(mk, col, yr, mdl){
Number_of_Anchors = na;
}
void Ship_details(){
print_details();
cout << "Number of Anchors: " << Number_of_Anchors << endl;
}
};
int main(){
Cars car("Chevrolet", "Black", 2010, "Camaro", "9.1 cubic feet");
car.car_details();
Explanation #
Now the Ships and Cars classes have access to public member functions of
the base class Vehicle as shown in the above illustration. Protected and
public data members are accessible to derived classes.
Now that we have learned about the base and derived classes. So, let’s move to
the next lesson in which we’ll learn about the base class constructors and
destructors.
Base Class Constructor and Destructor
IIn this lesson, we'll learn how constructors and destructors are called in derived and base classes during
inheritance.
#include <iostream>
using namespace std;
// Base class
class Base {
public:
Base(){
cout << "Base class default constructor!" << endl;
}
// Base class's parameterised constructor
Base(float i) {
cout << "Base class parameterized constructor" << endl;
}
};
// Derived class
class Derived : public Base {
public:
Derived(){
cout << "Derived class default constructor!" << endl;
}
// main function
int main() {
// creating object of Derived Class
Derived obj;
cout << endl;
Derived obj1(10.2);
}
#include <iostream>
using namespace std;
// Base class
class Base {
public:
~Base(){
cout << endl << "Base class Destructor!" ;
}
};
// Derived class
class Derived : public Base {
public:
~Derived(){
cout << endl << "Derived class Destructor!" ;
}
};
// main function
int main() {
// creating object of Derived Class
Derived obj;
}
In the next lesson, we’ll be learning about the public , protected and private
inheritance.
Modes of Inheritance
In this lesson, we'll learn about how Public, Private and Protected inheritance is done in C++.
You are already familiar with Access Modifiers from the Classes chapter. By
using these specifiers, we limit the access of the data members and member
functions to the other classes and main.
class Vehicle{
string Make;
string Color;
int Year;
protected:
string Model;
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model ;
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
}
};
public:
Cars(){
trunk_size = "";
}
Cars(string mk, string col, int yr, string mdl, string ts)
:Vehicle(mk, col, yr, mdl){
trunk_size = ts;
}
void car_details(){
print_details();
cout << "Trunk size: " << trunk_size << endl;
cout << "Model: " << Model << endl; // Model is protected and
// is accessible in derived class
}
};
int main(){
Cars car("Chevrolet", "Black", 2010, "Camaro", "9.1 cubic feet");
// car.Year = 2000; // this will give error as Year is private
// car.Model = "Accord"; // this will give error as Model is protected
car.car_details();
//car.print_details(); // public functions of base class are inaccessible in main
}
string Make;
string Color;
int Year;
protected:
string Model;
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model = "";
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
}
};
public:
Cars(){
trunk_size = "";
}
Cars(string mk, string col, int yr, string mdl, string ts)
:Vehicle(mk, col, yr, mdl){
trunk_size = ts;
}
void car_details(){
print_details();
cout << "Trunk size: " << trunk_size << endl;
cout << "Model: " << Model << endl; // Model is protected and
// is accessible in derived class
}
};
int main(){
Cars car("Chevrolet", "Black", 2010, "Camaro", "9.1 cubic feet");
// car.Year = 2000; // this will give error as Year is private
// car.Model = "Accord"; // this will give error as Model is protected
car.car_details();
//car.print_details(); // public functions of base class are inaccessible in main
}
class Vehicle{
string Make;
string Color;
int Year;
protected:
string Model;
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model = "";
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
}
};
public:
Cars(){
trunk_size = "";
}
Cars(string mk, string col, int yr, string mdl, string ts)
:Vehicle(mk, col, yr, mdl){
trunk_size = ts;
}
void car_details(){
cout << "Trunk size: " << trunk_size << endl;
cout << "Model: " << Model << endl; // Model is protected and
// is accessible in derived class
}
};
int main(){
Cars car("Chevrolet", "Black", 2010, "Camaro", "9.1 cubic feet");
// car.Year = 2000; // this will give error as Year is private
//car.Model = "Accord"; // this will give error as Model is protected
car.car_details();
car.print_details(); // public functions of base class are accessible in main
}
Types of Inheritance
Base class
member
access Public Protected Private
specifier
In this lesson, we'll learn about the types of inheritance which includes multiple inheritance and multilevel
inheritance.
• Multiple Inheritance
• Example
• Implementation
• Multilevel Inheritance
• Example
• Implementation
Multiple Inheritance #
We can inherit the base class attributes to the derived class if we want derived
class to have access data members and member functions of the base class.
But to inherit multiple classes data members and member functions to the
derived, the concept of multiple inheritance comes in. We can inherit multiple
classes as base classes separated by ,
Example #
Let’s take the example of Vehicle and Cars classes which acts as the base
classes of the Honda class:
class Honda : public Vehicle, public Cars
Cars Vehicle
Honda
Multiple Inheritance
Implementation #
Implementation of the Honda class is given below:
class Vehicle{
protected:
string Make;
string Color;
int Year;
string Model;
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model = "";
}
void print_details(){
cout << Manufacturer: << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
cout << "Model: " << Model << endl;
}
};
class Cars{
string trunk_size;
public:
Cars(){
trunk_size = "";
}
Cars(string ts){
trunk_size = ts;
}
void car_details(){
cout << "Trunk size: " << trunk_size << endl;
}
};
public:
Honda(){
top_speed = 0;
}
Honda(string mk, string col, int yr, string mdl, string na, int ts)
:Vehicle(mk, col, yr, mdl), Cars(na){
top_speed = ts;
}
void Honda_details(){
print_details();
car_details();
cout << "Top speed of the car: " << top_speed << endl;
}
};
int main(){
Honda car("Honda", "Black", 2006, "Accord", "14.7 cubic feet", 260);
car.Honda_details();
}
Now, the Honda class object has access to all member functions of Cars and
Vehicle classes as they’re now base classes of Honda class. The highlighted
lines in the code indicate how multiple inheritance is achieved.
Multilevel Inheritance #
If we want to inherit data members and member functions of the base class
which is already inherited from another class, the concept of multilevel
inheritance comes in. This contains a more hierarchical approach.
class parent
Example #
Let’s take the example of Vehicle class which acts as a parent to Cars class.
Now Cars class acts as a parent to Honda class.
Vehicle Parent
Implementation #
Implementation of the Honda class is given below:
class Vehicle {
protected:
string Make;
string Color;
int Year;
string Model;
public:
Vehicle(){
Make = "";
Color = "";
Year = 0;
Model = "";
}
void print_details(){
cout << "Manufacturer: " << Make << endl;
cout << "Color: " << Color << endl;
cout << "Year: " << Year << endl;
cout << "Model: " << Model << endl;
}
};
public:
Cars(){
trunk_size = "";
}
Cars(string mk, string col, int yr, string mdl, string ts)
:Vehicle(mk, col, yr, mdl){
trunk_size = ts;
}
void car_details(){
cout << "Trunk size: " << trunk_size << endl;
}
};
public:
Honda(){
top_speed = 0;
}
Honda(string mk, string col, int yr, string mdl, string na, int ts)
:Cars(mk, col, yr, mdl, na){
top_speed = ts;
}
void Honda_details(){
print_details();
car_details();
cout << "Top speed of the car: " << top_speed << endl;
}
};
int main(){
Honda car("Honda", "Black", 2006, "Accord", "14.7 cubic feet", 260);
car.Honda_details();
}
Now, Honda class object has access to all member functions of Cars class and
the Cars class has access to all members functions of the Vehicle class as
they’re now base classes of Honda class. The highlighted lines in the code
indicate how multilevel inheritance is achieved.
Ctrl+C Ctrl+V
Extensibility #
Using inheritance , one can extend the base class logic as per the business
logic of the derived class. This is an easy way to upgrade or enhance specific
parts of a product without changing the core attributes. An existing class can
act as a base class to derive a new class having upgraded features.
Data Hiding #
The base class can decide to keep some data private so that it cannot be
altered by the derived class. This concept i.e. Encapsulation has already been
discussed in the previous chapter.
In this lesson, we will be learning about the basics of polymorphism with the implementation details.
• De nition
• Shape Class
• Implementation
• Rectangle Class
• Implementation
• Circle Class
• Implementation
• Explanation of Code
De nition #
When we say polymorphism in programming that means something which
exhibits many forms or behaviors. So far, we have learned that we can add
new data and functions to a class through inheritance. But what about if we
want our derived class to inherit a method from the base class and have a
different implementation for it? That is when polymorphism comes in, a
fundamental concept in OOP paradigm.
Shape Class #
We are considering here the example of Shape class, which is base class for
many shapes like Rectangle and Circle. This class contains a function
getArea() which calculates the area for the derived classes.
Implementation #
Let’s look at the implementation of Shape class:
Shape class
float getArea()
Shape class
// A simple Shape interface which provides a method to get the Shape's area
class Shape {
public:
float getArea(){}
};
Rectangle Class #
Consider the Rectangle class which is derived from Shape class. It has two
data members, i.e., width and height and it returns the Area of the rectangle
by using getArea() function.
Shape class
float getArea()
Rectange class
float getArea()
width * height
Rectangle class
Implementation #
Let’s look at the implementation of the Rectangle class:
public:
Rectangle(float wid, float heigh) {
width = wid;
height = heigh;
}
float getArea(){
return width * height;
}
};
Circle Class #
Consider the Circle class which is derived from Shape class. It has one data
member, i.e., radius and it returns the Area of the circle by using getArea()
function.
Shape class
float getArea()
Circle class
float getArea()
3.14159 * rad * rad
Circle class
Implementation #
Let’s look at the implementation of the Circle class:
public:
Circle(float rad){
radius = rad;
}
float getArea(){
return 3.14159f * radius * radius;
}
};
Now, if we merge all the classes then by calling the getArea() function, let’s
see what happened:
#include <iostream>
using namespace std;
// A simple Shape interface which provides a method to get the Shape's area
class Shape {
public:
float getArea(){}
};
public:
Rectangle(float wid, float heigh) {
width = wid;
height = heigh;
}
float getArea(){
return width * height;
}
};
public:
Circle(float rad){
radius = rad;
}
float getArea(){
return 3.14159f * radius * radius;
}
};
int main() {
Rectangle r(2, 6); // Creating Rectangle object
cout << "Calling Rectangle getArea function: " << r.getArea() << endl; // Calls Rectan
cout << "Calling Rectangle from shape pointer: " << shape->getArea() << endl <<endl; // Ca
cout << "Calling Circle getArea function: " << c.getArea() << endl;
cout << "Calling Circle from shape pointer: " <<shape->getArea() << endl << endl;
}
Explanation of Code #
Polymorphism only works with a pointer and reference types, so we have
created a Shape pointer, and pointed to the derived class objects. But due to
multiple existences of the same functions in classes, it will get confused
between which class getArea() function it’s calling. The derived classes
function has a different implementation but the same name and that’s why we
are not getting the expected output.
float getArea()
Circle
Rectangle
Implementation #
Highlighted portion shows where overriding is done. Let’s Have a look!
#include <iostream>
using namespace std;
// A simple Shape interface which provides a method to get the Shape's area
class Shape {
public:
float getArea(){}
};
public:
Rectangle(float wid, float heigh) {
width = wid;
height heigh;
}
float getArea(){
return width * height;
}
};
public:
Circle(float rad){
radius = rad;
}
float getArea(){
return 3.14159f * radius * radius;
}
};
int main() {
Rectangle r(2, 6); // Creating Rectangle object
cout << "Calling Rectangle getArea function: " << r.getArea() << endl; // Calls Rectan
cout << "Calling Rectangle from shape pointer: " << shape->getArea() << endl <<endl; // Ca
cout << "Calling Circle getArea function: " << c.getArea() << endl;
cout << "Calling Circle from shape pointer: " <<shape->getArea() << endl << endl;
}
The derived classes can give its own specific implementation to inherited
methods without modifying the parent class methods.
If a child class needs to use the parent class method, it can use it, and the
other classes that want to have different implementation can use the
overriding feature to make changes.
Derived class/es must have the same declaration, i.e., name, same
parameters and same return type of the function as of the base class.
The method in the base class must need to be overridden in the derived
class.
In the next lesson, we’ll learn about how to make a function virtual.
Virtual Member Functions
In this lesson, we'll be learning about a very important concept of polymorphism, i.e., Virtual member.
• De nition
• Why Do We Need a Virtual Function?
• Explanation
De nition #
A virtual function is a member function which is declared within the base
class and is overridden by the derived class. When you refer to a derived class
object using a pointer or a reference to the base class, you can call a virtual
function for that object and execute the derived class’s version of the function.
Virtual functions ensure that the correct function is called for an object,
regardless of the type of reference (or pointer) used for the function call. They
are mainly used to achieve Runtime polymorphism. Functions are declared
with a virtual keyword in a base class. The function resolution call is done at
run-time.
// A simple Shape interface which provides a method to get the Shape's area
class Shape {
public:
virtual float getArea(){}
};
public:
Rectangle(float wid, float heigh) {
width = wid;
height = heigh;
}
float getArea(){
return width * height;
}
};
public:
Circle(float rad){
radius = rad;
}
float getArea(){
return 3.14159f * radius * radius;
}
};
int main() {
Rectangle r(2, 6); // Creating Rectangle object
Shape* shape = &r; // Referencing Shape class to Rectangle object
cout << "Calling Rectangle from shape pointer: " << shape->getArea() << endl; // Calls sha
cout << "Calling Circle from shape pointer: " <<shape->getArea() << endl;
Explanation #
we have seen earlier when we’re trying to print the child class function
getArea() by referencing a parent class pointer, it gave us an error. Just by
writing the keyword virtual we can reference a parent class pointer to child
class object.
In the next lesson, we’ll be learning about the Pure Virtual functions.
Pure Virtual Member Functions
In this lesson, we'll be learning about a very important concept of polymorphism, i.e., Pure Virtual Member
Functions.
• Abstract Class
• How to Write a Pure Virtual Function?
• =0 Sign
• Overriding Virtual Function
• Explanation
Abstract Class #
As we’ve seen in the Inheritance chapter, we can only make derived class’s
objects to access their functions, and we will never want to instantiate objects
of a base class, we call it an abstract class. Such a class exists only to act as a
parent of derived classes that will be used to instantiate objects.
=0 Sign #
The equal sign = here has nothing to do with the assignment, the value 0 is
not assigned to anything. The =0 syntax is simply how we tell the compiler
that a virtual function will be pure.
abstract class itself, and you can’t instantiate objects from it (although you
might from classes derived from it). For consistency, you may want to make all
the virtual functions in the base class pure.
#include <iostream>
using namespace std;
// A simple Shape interface which provides a method to get the Shape's area
class Shape {
public:
virtual float getArea() = 0;
};
// A Rectangle is a Shape with a specific width and height
class Rectangle : public Shape { // derived form Shape class
private:
float width;
float height;
public:
Rectangle(float wid, float heigh) {
width = wid;
height = heigh;
}
float getArea(){
return width * height;
}
};
public:
Circle(float rad){
radius = rad;
}
float getArea(){
return 3.14159f * radius * radius;
}
};
public:
Square(float len){
length = len;
}
float getArea(){
return length * length;
}
};
int main() {
Shape * shape[3]; // Referencing Shape class to Rectangle object
Explanation #
Now in main() you attempt to create objects of a Shape class, the compiler
will complain that you’re trying to instantiate an object of an abstract class. It
will also tell you the name of the pure virtual function that makes it an
abstract class. Notice that, although this is only a declaration, you never need
to write a definition of the Shape class getArea(). Initialize the Shape class
pointer and point it to objects of derived classes to access the getArea()
function of respective classes.