Skip to main content

OOPS in C++

·3612 words·17 mins
Pratham Dhyani
Author
Pratham Dhyani
Just someone who loves to code and be creative
Table of Contents

Declaring classes ->
#

class Teacher {
public:

-> (properties/attributes)

string name;
string dept;
string subject;
double salary;

-> (methods/member functions)

 void changeDept{string newDept}{
		dept=newDept;
  }

};

Declaring objects ->
#

int main(){
	Teacher t1; 
	Teacher t2; 
	//assigning values to an object using class
	t1.name = "Soham"; 
	t1.subject = "CSE"; 
	t1.dept = "Btech"; 
	t1.salary = "100000000"; 
	//Printing name of the object 
	cout << t1.name << endl; 
	}

Access Modifiers ->
#

  • Public - To ensure the functions and variables inside the class can be used outside just like global variables.

  • Private- Default setting which restricts the usage of functions and variables inside the class to not be used outside of it just like local variables.

  • Protected- Data and methods accessible inside class and to its derived class.

Setter and Getter ->
#

We set some functions to make changes in the private access modifiers because we might need to change the values in some cases.

e.g. ->

#include<iostream>
#incldue<string>
using namespace std;

class Example{
private:
double salary:

public:
string name;
string age;
//setter
void setSalary(double s) {
	salary=s;
}
//getter
double getSalary(){
	return salary;
}
};

int main(){

	Example e1;
	e1.name="Sahil";
	e1.age="18";
	e1.setSalary(250000);
	cout << e1.getSalary() << endl;


}

(setter and getter are specific functions that are used to alter the values and print the values of private variables.)

Encapsulation:
#

Encapsulation is wrapping up of data and member functions in a single unit called class.

  • We basically did that in the first example by including Variables and Functions together in one class.

  • Data Hiding -> It helps in Protecting the sensitive information by using the Private Access Modifier.

Constructor:
#

Method invoked automatically at the time of object creation.==

When we make an object in the main function like-
Teacher t1;
the constructor gets invoked automatically.

Properties:
#

  • Has the same name as the class.

  • Doesn’t have a return type like void or int.

  • Only called once (automatically), at object creation.

  • Memory allocation happens when constructor is called. -> When we make a variable inside class, memory isn’t allocated but when we invoke the constructor (by making an object in the main function) then memory is allocated to that particular object.

  • The constructor is made by default by the compiler if we don’t make it ourselves.

  • Always make it public as it is always used when we make an object in the main function.

Initialization:
#

Initializing - Pre defining attributes for the object in a class which will be applied to all the objects of that class.

#include<iostream>
#incldue<string>
using namespace std;

class Game{
 public: 
 
 Game() {           //constructor
 type = "Free";
 }

 string name;
 string genre;

};

int main() {

Game g1;
g1.name = "Red dead redemption";
g1.genre = "Open world";

cout << g1.type << endl;
return 0;
}

Output :

Free

In the above example we see that we didn’t declare the value of type in the main function but we declared it in the constructor so when we printed it at the end, we still got the output.

Types of Constructor:
#

  1. Non parameterized
  2. Parameterized
  3. Copy
1. Non parametrized constructor:
#
  • Constructors which do not contain any parameters.

  • The example that we used in Initializing is an example of Non parameterized constructor because there were no parameters included.

2. Parametrized constructor:
#

Constructors that require parameters.

Reduces repetition and makes code easy to read and short.

e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Game{
 public:
 Game(string n, string g, string t, double p) {             //constructor
	 name = n;
	 genre = g;
	 type = t;
	 price = p;
 }
 };

int main() {

Game g1("Marvel Rivals", "Hero Shooter", "Free", "NULL");

cout << "Name of the game:" g1.name << endl;
cout << "Genre of the game:" g1.genre << endl;
cout << "Price of the game:" price << endl;

return 0;
};

Output :
Name of the game: Marvel Rivals Genre of the game: Hero Shooter Price of the game: NULL

There can be multiple constructor (all constructors have the same name as the name of the class) in one class provided that the parameters of the constructors are different. Either the number or the type of the parameters must be different.

Construction Overloading- When we use multiple constructors with different types of parameters. This is a type of Polymorphism.

This Pointer (->)
#

this is a special pointer in C++ that points to the current object.

this ->prop is the same as *(this).prop (basically works as a pointer but its an easier way to write it.)

e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Game{
 public:
 Game(string name, string game, string type, double price) {             //constructor
	 this->name = name;
	 this->genre = game;
	 this->type = type;
	 this->price = price;
 }
 };

int main() {

Game g1("Marvel Rivals", "Hero Shooter", "Free", "NULL");

cout << "Name of the game:" g1.name << endl;
cout << "Genre of the game:" g1.genre << endl;
cout << "Price of the game:" g1.price << endl;

return 0;
};

Output :
Name of the game: Marvel Rivals Genre of the game: Hero Shooter Price of the game: NULL

The above example shows the usage of this->.

We use this-> to help the compiler identify the attributes of the objects. The name of parameters and attributes of the objects is the same but still the code will work without error because this-> points out the object.

3. Copy constructor:
#

Special Constructor used to copy properties of one object into another.

It can be custom made or also used by default.

  1. Default copy constructor
    e.g.
#include<iostream>
#incldue<string>
using namespace std;

class Game{
 public:
 Game(string n, string g, string t, double p) {             //constructor
	 name = n;
	 genre = g;
	 type = t;
	 price = p;
 }
 };

int main() {

Game g1("Marvel Rivals", "Hero Shooter", "Free", "NULL");

Game g2(g1);  //default copy constructor

cout << "Name of the game:" g2.name << endl;
cout << "Genre of the game:" g2.genre << endl;
cout << "Price of the game:" g2.price << endl;

return 0;
};

Output :
Name of the game: Marvel Rivals
Genre of the game: Hero Shooter
Price of the game: NULL

Point to note:

Although we didn’t specify or make any custom constructor which takes the parameters in order to copy the values of one object but if we type
Game g2(g1);
the attributes of g1 is stored in a new object g2 and the code runs without any error.

  1. Custom made copy constructor
    e.g.
#include<iostream>
#incldue<string>
using namespace std;

class Game{
 public:
 Game(string n, string g, string t, double p) {             //constructor
	 name = n;
	 genre = g;
	 type = t;
	 price = p;


//custom made copy constructor
Game(Game &obj){
	cout << "Using custom copy    
	constructor /n";
	this->name = obj.name;
	this->genre = obj.genre;
	this->type = obj.type;
	this->price = obj.price;
}
 }
 };

int main() {

Game g1("Marvel Rivals", "Hero Shooter", "Free", "NULL");

Game g2(g1);  

cout << "Name of the game:" g2.name << endl;
cout << "Genre of the game:" g2.genre << endl;
cout << "Price of the game:" g2.price << endl;

return 0;
};

Output :
Using custom copy constructor Name of the game: Marvel Rivals
Genre of the game: Hero Shooter
Price of the game: NULL

Explanation:

  • The custom copy constructor has a parameter which is an object.

  • If we see, the ‘&’ sign signifies that we are using the address of the actual object and not its copy. (AKA Call by reference.)

  • That means that the changes made inside the constructor will be reflected in the actual value of the object.

Types of copies:
#

There are two ways of copying data of objects-

  1. Shallow Copy :
    • Copies all the member values from one object to another.

    • This is the default way by which the copy is made in both of the above examples we have used.

    • This creates a problem when we are using dynamically allocated variables. It works (as we saw above) with static variables. Example of dynamically allocation of variables - using pointers.

e.g. of the error

#include<iostream>
#incldue<string>
using namespace std;

class Show{
 public:

string name;
double* rating;

Show(string name, double rating) {      
	 name = n;
	 ratingptr = new double;
	 *ratingptr = rating;   //storing value of rating in pointer

void getinfo(){
	cout << "Name: " <<endl;
	cout << "Rating: " <<endl;
}
 }
 };

int main() {

Show s1("Squid Games","8");
Show s2(s1);  

s1.getInfo();
*(s2.ratingptr) = 9;
s1.getInfo();


return 0;
};

Output :
Name: Squid Games
Rating: 8
Name: Squid Games
Rating: 9

In the above example we copied the information of s1 to s2 and then changed the rating of s2 to 9 but when we print s1, the value gets changed in it as well. That is because we used pointers to allocate the value which changes the value in that address which, in turn, ends up creating mistakes when we use shallow copies.

  1. Deep copy :

The method that we use when dealing with dynamically allocated values.

Deep copies not only copies the member values but also makes copies of any dynamically allocated memory that the member points to.

We have to make our own constructor for making use of deep copies.

e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Show{
 public:

string name;
double* rating;

Show(string name, double rating) {      
	 name = n;
	 ratingptr = new double;
	 *ratingptr = rating;   

Show(Show &obj){              //constructor to make deep copies
this->name = obj.name;
ratingptr = new double;
*ratingptr = *obj.ratingptr;
}

void getinfo(){
	cout << "Name: " <<endl;
	cout << "Rating: " <<endl;
}
 }
 };

int main() {

Show s1("Squid Games","8");
Show s2(s1);  

s1.getInfo();
*(s2.ratingptr) = 9;
s1.getInfo();


return 0;
};

Output :
Name: Squid Games
Rating: 8
Name: Squid Games
Rating: 8

Destructor:
#

Unlike constructor, destructor helps in deallocating the memories of objects.

  • We do have an in-built destructor in the compiler but we have to make a custom destructor if we have used dynamically allocated memories in the code as the in-built destructor can not do it.

  • Just like we use the keyword new to dynamically allocate memories, we have to use delete in order to remove the memory of the pointer. ==It is important to remember that when we use the remove keyword we are deleting the memory and not the pointer itself.
    e.g.
    delete ptr
    will delete the memory that the pointer was pointing at and the pointer will still stay as it is.

  • Destructor has the same name as the class.

  • Gets called automatically by the compiler when the main function has to de-allocate the memory of an object.

  • Use the ‘~’ symbol to signify the destructor.

e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Show{
 public:

string name;
double* rating;

Show(string name, double rating) {      
	 name = n;
	 ratingptr = new double;
	 *ratingptr = rating;   

Show(Show &obj){              
this->name = obj.name;
ratingptr = new double;
*ratingptr = *obj.ratingptr;
}

~Show(){                //destructor
delete ratingptr;
}

void getinfo(){
	cout << "Name: " <<endl;
	cout << "Rating: " <<endl;
}
 }
 };

int main() {

Show s1("Squid Games","8");
Show s2(s1);  

s1.getInfo();
*(s2.ratingptr) = 9;
s1.getInfo();


return 0;
};

The change actually happens in the memory and not on the output. All the code editors delete the memories once we are done with running a particular code but destructor is essential when we are working on projects in companies and organizations in order to prevent wastage of memory storage.

Inheritance:
#

When properties and member functions of base class are passed on to the derived class.

  • Helps in code resusability.

  • Base class constructor is called first and then the child constructor is called.

  • In destructor, child destructor gets called first and then the base destructor.

  • To call a parameterised inheritance constructor made by us, the syntax is as follows-
    Student() : Person(string name, int age){
    this->rollno = rollno;
    }
    In the example below we have used an un parameterized inheritance constructor-

#include<iostream>
#incldue<string>
using namespace std;

class Person{           //base constructor
public:
	string name;
	int age;
};

class Student : public Person {  //child constructor
public:
	int rollno;
	void getInfo(){
	cout << "Name " << name << endl;
	cout << "Age " << age << endl;
	cout << "Roll no " << rollno << endl;
	};


int main(){
	Student s1;
	s1.name = "Pratham Dhyani";
	s1.age = 18;
	s1.rollno = 331;
    s1.getInfo();
    return 0;
	
};

Output :
Name Pratham Dhyani
Age 18
Roll no. 331

In the above example we used the access modifier public before inheriting the class Person. This means that the attributes we will copy will still be public i.e. usable throughout the code. But if we used private then it would mean that once the attributes get copied they are only going to be used in the child constructor (student for the above example) but they will still remain public in the parent constructor.

Protected is another method of using private data. We only use it when we want to keep data private but we want to allow it to be inherited to other classes. - Basically data that is only used for inheritance but is to be kept private otherwise. - Only the class and the inherited class is able to access the protected data.

Types of inheritances:
#

  1. Single inheritance:

The example that we saw above was single level inheritance. The child simply inherits the attributes from the parent class.

  1. Multi - level Inheritance:

e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Person{           
public:
	string name;
	int age;
};

class Student : public Person {  
public:
	int rollno;
};

class GradStudent : public Student{
Public:
	string Researchpaper;
};


int main(){
	Gradstudent s1;
	s1.name = "Pratham Dhyani";
	s1.Researchpaper = "Psychology";
	cout << s1.name << endl;
	cout<< s1.Researchpaper << endl;
    return 0;
	
};

Output :
Pratham Dhyani
Psychology

Although we never mentioned the attribute name in the class GradStudent, but when we printed the name using the Gradstudent class, we still got the the correct output because it inherited the name from the student class which had inherited it from the person class.

  1. Multiple Inheritance:

This is when one child class gets attributes from 2 parents class simultaneously.

e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Student{           
public:
	string name;
	int rollno;
};

class Teacher {  
public:
	string subject
	double salary;
};

class TA : public Student, public Teacher {

};

int main(){
	TA t1;
	t1.name << "Sanyam";
	t1.subject << "Machine Learning";
	
	cout << t1.name << endl;
	cout<< t1.subject << endl;
    return 0;
	
};

Output :
Sanyam
Machine Learning

Although we never mentioned the attributes name and subject in the class TA, but when we printed the name and subject using the TA class, we still got the the correct output because it inherited the name from the student class and the subject from the teacher class.

  1. Hierarchical Inheritance:

    Inheritance wherein two child classes get the attributes from the same parent class.

    e.g.

#include<iostream>
#incldue<string>
using namespace std;

class Game{           
public:
	string genre;
	int rating;
};

class MarvelRivals : public Game {  
public:
	double activePlayers;
};

class RDR: public Game {
public:
	int questsAvailable;
};

int main(){
	MarvelRivals m1;
	m1.genre << "Action";
	RDR r1;
	r1.rating << "4.7/5.0"
	
	cout << m1.genre << endl;
	cout<< r1.rating << endl;
    return 0;
	
};

Output :
Action
4.5/5.0

Although we never mentioned the attributes genre and rating in the classes MarvelRivals and RDR, but when we printed the genre and rating using the respective classes, we still got the the correct output because it inherited the attributes from the Game parent class.

  1. Hybrid Inheritance:

    A mixture of all the inheritances we have studied so far. Basically multiple inheritances are used together.

Polymorphism:
#

Ability of objects to take on different forms or behave in different ways depending on the context in which they are used.

Types of Polymorphism:
#

1. Compile Time Polymorphism:
#

Output decided during compiling. It is static in nature.

e.g. 1. Constructor Overloading:
2 or more constructors with the same name but different parameters inside the same class. (We can make parameters different in 2 ways. Either change the number of parameters or change the data-type of the parameters.)

#include<iostream>
#incldue<string>
using namespace std;

class Student{           
public:
	string name;

	Student(){
	cout << "Non-parameterised constructor" << endl;
}
	Student(string name){
	this->name=name;
	cout << "Parameterised constructor" << endl;
}
};

int main(){
	Student s1();
	Student s2("Simran");
    return 0;
	
};

Output :
Non-parameterized constructor
Parameterized constructor

According to the context/the input given, the different constructors were called in different situations despite having the same name.

e.g. 2. Function Overloading: 2 or more functions inside the same class having the same name but different parameters.

#include<iostream>
#incldue<string>
using namespace std;

class Print{           
public:
	void show(int x){
	cout << "int: << x << endl;
}
	void show(char ch){
	cout << "char: << ch << endl;
}
};

int main(){
	Print p1;
	p1.show(3);
	p1.show("*")
    return 0;
	
};

Output :
3
*

According to the context/the input given, the different functions were called in different situations despite having the same name.

e.g. 3.
Operator Overloading: Using the same operator for different purposes according to the context.

2. Run Time Polymorphism:
#
  • AKA Dynamic Polymorphism.

  • Function overriding takes place.

e.g. 1.
Function overriding: Parent class and child class both contain the same function with different implementation and the object will decide which function will run. The child class function overrides the parent class function.

#include<iostream>
#incldue<string>
using namespace std;

class Parent{           
public:
	void show(){
	cout << "parent class" << endl;
	}
};
class Child : public Parent {
public:
	void show(){
	cout << "child class" << endl;
	}		
};

int main(){
	Child c1;
	c1.show();
	Parent p1;
	p1.show();
	return 0;
};

Output :
child class
parent class

e.g. 2.
Virtual Functions: Pair of functions that are used in the base class and the child class. The child class always overrides the base class.

  • They are dynamic in nature.

  • Defined by the keyword virtual inside a base class.

  • Called upon during runtime.

  • Note: They always have to be used in a pair (i.e. one in base class and one in child class.)

  • Copy paste the same function from parent class to child class, just remove the virtual keyword and change the data inside the function as per your requirement.

#include<iostream>
#incldue<string>
using namespace std;

class Parent{           
public:
	virtual void hello(){
		cout << "Parent class says hello!\n";
	}
};

class Child : public Parent {
public:
		void hello(){
		cout << "Hello from child class!\n"
		}	
};

int main(){
	Child c1;
	c1.hello();
	return 0;
};

Output :
Hello from child class!

Abstraction:
#

Hiding all unnecessary details and showing only the important parts.

  • Using access modifiers (public, private and protected) is an example of implementing abstraction. This is because we only make certain data accessible for further usage hence hiding unwanted information and showing only the required data.

  • Using Abstract classes is another example of implementing abstraction.

    • Abstract classes are used to provide a base class from which other classes can be derived.

    • Basically classes are used to make blueprints for objects/instances (objects are also called instances) but abstract class is used to make blueprints for other classes.

    • They can not be instantiated (can not make objects/instances) and are meant to be inherited.

    • Typically used to define an interface for derived classes.

    • Every class with a pure virtual function becomes an abstract class.

#include<iostream>
#incldue<string>
using namespace std;

class Shape{        //abstract class   
public:
	virtual void draw() = 0; //pure virtual fucntion
};

class Circle : public Shape {
	public:
		void draw(){
		cout << "Drawing a circle\n"
		}	
};

int main(){
	Circle c1;
	c1.draw();
	return 0;
};

Output :
Drawing a circle

Static keyword:
#

Can be used to make:

1. Static Variables:
#

Variables declared as static in a function are created and inherited once for life the lifetime of the program.

#include<iostream>
#incldue<string>
using namespace std;

void numb(){
	int x = 0;
	cout<< "Value of x is " << x << endl;
	x++
}

int main(){
	numb();
	numb();
	numb();
};

Output :
Value of x is 0
Value of x is 0
Value of x is 0

We get this output because the value of x is reset each time the function is run and so the value that gets appended doesn’t get counted.

#include<iostream>
#incldue<string>
using namespace std;

void numb(){
	static int x = 0;
	cout<< "Value of x is " << x << endl;
	x++
}

int main(){
	numb();
	numb();
	numb();
};

Output :
Value of x is 0
Value of x is 1
Value of x is 2

We get this output because we used the static keyword which makes the variable in a separate space and not in the function so the appended value gets changed and doesn’t reset. i.e. the initialization statement runs only once and the rest of the function repeats

Static Variables in a class are created and initialized once. They are shared by all the objects of the class.

Different objects from the same class take different values even if the name of the variable is the same but when we add the static keyword, we observe that the same variable is used for different objects.

2. Static objects:
#

Using static objects turn the runtime of the object to lifetime and it persists that object until the code is finished running.

not using static:

#include<iostream>
#incldue<string>
using namespace std;

class ABC(){
	public:
	ABC(){
		cout << "constructor\n";
}

	~ABC(){
		cout << "destructor\n";
}
};

int main(){
	if(true){
		ABC obj;
	}

	cout << "End of main\n";
	reuturn 0;
};

Output :
constructor
destructor
End of main

using static keyword:

#include<iostream>
#incldue<string>
using namespace std;

class ABC(){
	public:
	ABC(){
		cout << "constructor\n";
}

	~ABC(){
		cout << "destructor\n";
}
};

int main(){
	if(true){
		static ABC obj;
	}

	cout << "End of main\n";
	reuturn 0;
};

Output :
constructor
End of main
destructor

Thanks for reading! 💗

Reach out - pratham938@gmail.com