Oops Notes
Oops Notes
Oops Notes
**Object-oriented programming** (OOPs) is a programming paradigm that is based on the concept of objects rather than just functions and procedures. It is the most popular
methodology among developers.
There are many reasons why OOPs is mostly preferred, but the most important among them are:
OOPs helps users to understand the software easily, although they don’t know the actual implementation.
With OOPs, the readability, understandability, and maintainability of the code increase multifold.
Even very big software can be easily written and managed easily using OOPs.
What is an interface?
An interface refers to a special type of class, which contains methods, but not their definition. Only the declaration of methods is allowed inside an interface. To use an interface, you
cannot create objects. Instead, you need to implement that interface and define the methods for their implementation. (JAVA)
Access Specifier
In C++, there are three access specifiers:
Constructor
A constructor in C++ is a special method that is automatically called when an object of a class is created. To create a constructor, define a method with the same name as the
class.
class Car { // The class
public: // Access specifier
string brand; // Attribute
string model; // Attribute
int year; // Attribute
int main() {
// Create Car objects and call the constructor with different values
Car carObj1("BMW", "X5", 1999);
Car carObj2("Ford", "Mustang", 1969);
// Print values
cout << carObj1.brand << " " << carObj1.model << " " << carObj1.year << "\n";
cout << carObj2.brand << " " << carObj2.model << " " << carObj2.year << "\n";
return 0;
}
Default Constructor
The default constructor is the constructor which doesn’t take any argument. It has no parameters.
In this case, as soon as the object is created the constructor is called which initializes its data members.
A default constructor is so important for the initialization of object members, that even if we do not define a constructor explicitly, the compiler will provide a default constructor
implicitly
Parameterized Constructor
We can also have more than one constructor in a class and that concept is called constructor overloading.
Copy Constructor is a type of constructor which is used to create a copy of an already existing object of a class type.
It is usually of the form X (X&), where X is the class name.
The compiler provides a default Copy Constructor to all the classes.
#include<iostream>
using namespace std;
class PrepInsta
{
private:
int x, y;
public:
PrepInsta()
{ // empty default constuctor
}
int getX()
{
return x;
}
int getY()
{
return y;
}
};
int main()
{
// Trying to call parameterized constructor here
PrepInsta p1(10, 15);
cout << "\nFor p4 no copy constructor called only assignment operation happens\n" << endl;
cout << "\np3.x = " << p3.getX() << ", p3.y = " << p3.getY();
cout << "\np4.x = " << p4.getX() << ", p4.y = " << p4.getY();
return 0;
}
class Example
{
public:
static int var;
};
int main()
{
Example obj1;
Example obj2;
obj1.var = 2;
obj2.var = 3;
obj1.var++;
Static functions in a class also does not depend on object of class. We are allowed to invoke a static member function using the object and the . operator but it is recommended to
invoke the static members using the class name and the scope resolution :: operator.
Static member functions are allowed to access only the static data members or other static member function, they can not access the non-static data members or member
functions of the class.
Destructor
Destructor is an instance member function which is invoked automatically whenever an object is going to be destroyed. Meaning, a destructor is the last function that is going to be
called before an object is destroyed.
To create a destructor, define a method with the same name as the class preceded by ~. If not declared, the compilers, auto generate a destructor by default.
Inheritance
Inheritance is a process in which one class acquires all the properties and behaviors of its parent class. In such a way, you can reuse, extend or modify the attributes and behaviors
which are defined in other classes.
Base is a non-derived class because it does not inherit from any other classes. C++ allocates memory for Base, then calls Base’s default constructor to do the initialization.
int main()
{
Base base;
return 0;
}
int main()
{
Derived derived;
return 0;
}
If you were to try this yourself, you wouldn’t notice any difference from the previous example where we instantiate non-derived class Base. But behind the scenes, things happen
slightly differently. As mentioned above, Derived is really two parts: a Base part, and a Derived part. When C++ constructs derived objects, it does so in phases. First, the most-base
class (at the top of the inheritance tree) is constructed first. Then each child class is constructed in order, until the most-child class (at the bottom of the inheritance tree) is
constructed last.
So when we instantiate an instance of Derived, first the Base portion of Derived is constructed (using the Base default constructor). Once the Base portion is finished, the Derived
portion is constructed (using the Derived default constructor). At this point, there are no more derived classes, so we are done.
#include <iostream>
class Base
{
public:
int m_id {};
Base(int id=0)
: m_id { id }
{
std::cout << "Base\n";
}
Derived(double cost=0.0)
: m_cost { cost }
{
std::cout << "Derived\n";
}
int main()
{
std::cout << "Instantiating Base\n";
Base base;
return 0;
}
Instantiating Base
Base
Instantiating Derived
Base
Derived
Single Inheritance
In single inheritance, a class is allowed to inherit from only one class. i.e. one sub class is inherited by one base class only.
Multiple Inheritance
Multiple Inheritance: Multiple Inheritance is a feature of C++ where a class can inherit from more than one classes. i.e one sub class is inherited from more than one base classes.
As it turns out, most of the problems that can be solved using multiple inheritance can be solved using single inheritance as well. Many object-oriented languages (eg. Smalltalk,
PHP) do not even support multiple inheritance. Many relatively modern languages such as Java and C# restrict classes to single inheritance of normal classes, but allow multiple
inheritance of interface classes (which we will talk about later). The driving idea behind disallowing multiple inheritance in these languages is that it simply makes the language too
complex, and ultimately causes more problems than it fixes.
Ambiguity can be occurred in using the multiple inheritance when a function with the same name occurs in more than one base class.
Multi-level Inheritance
In this type of inheritance, a derived class is created from another derived class.
#include <iostream>
using namespace std;
class A
{
public:
void display()
{
std::cout << "Class A" << std::endl;
}
};
class B
{
public:
void display()
{
std::cout << "Class B" << std::endl;
}
};
class C : public A, public B
{
void view()
{
display();
}
};
int main()
{
C c;
c.display();
return 0;
}
The above issue can be resolved by using the class resolution operator with the function. In the above example, the derived class code can be rewritten as:
class C : public A, public B
{
void view()
{
A :: display(); // Calling the display() function of class A.
B :: display(); // Calling the display() function of class B.
}
};
class A
{
public:
void display()
{
cout<<?Class A?;
}
} ;
class B
{
public:
void display()
{
cout<<?Class B?;
}
} ;
In the above case, the function of the derived class overrides the method of the base class. Therefore, call to the display() function will simply call the function defined in the derived
class. If we want to invoke the base class function, we can use the class resolution operator.
int main()
{
B b;
b.display(); // Calling the display() function of B class.
b.B :: display(); // Calling the display() function defined in B class.
}
Hierarchical Inheritance
Hierarchical inheritance is defined as the process of deriving more than one class from a base class.
Encapsulation
One can visualize Encapsulation as the method of putting everything that is required to do the job, inside a capsule and presenting that capsule to the user. What it means is that by
Encapsulation, all the necessary data and methods are bind together and all the unnecessary details are hidden to the normal user. So Encapsulation is the process of binding data
members and methods of a program together to do a specific job, without revealing unnecessary details.
1. Data hiding: Encapsulation is the process of hiding unwanted information, such as restricting access to any member of an object.
2. Data binding: Encapsulation is the process of binding the data members and the methods together as a whole, as a class.
The public setXXX() and getXXX() methods are the access points of the instance variables of the EncapTest class. Normally, these methods are referred as getters and setters.
Therefore, any class that wants to access the variables should access them through these getters and setters.
Benefits of Encapsulation
The fields of a class can be made read-only or write-only.
A class can have total control over what is stored in its fields.
Polymorphism
In OOPs, Polymorphism refers to the process by which some code, data, method, or object behaves differently under different circumstances or contexts. Compile-time polymorphism
and Run time polymorphism are the two types of polymorphisms in OOPs languages.
This type of polymorphism is resolved at compilation stage. It is also called static and early binding. Examples are-
Method Overloading: When there are multiple methods with same name but different parameters then these functions are said to be overloaded. Functions can be
overloaded by change in number of arguments or/and change in type of arguments.
class Geeks
{
public:
int main() {
Geeks obj1;
In java, method overloading is not possible by changing the return type of the method only because of ambiguity. Let's see how ambiguity may occur:
class Adder{
static int add(int a,int b){return a+b;}
static double add(int a,int b){return a+b;}
}
class TestOverloading3{
public static void main(String[] args){
System.out.println(Adder.add(11,11));//ambiguity
}}
Operator Overloading : In C++, we can make operators work for user-defined classes. This means C++ has the ability to provide the operators with a special meaning for a
data type, this ability is known as operator overloading. For example, we can overload an operator ‘+’ in a class like String so that we can concatenate two strings by just using
+. Other example classes where arithmetic operators may be overloaded are Complex Numbers, Fractional Numbers, Big Integer, etc.
Operator functions are the same as normal functions. The only differences are, that the name of an operator function is always the operator keyword followed by the symbol of the
operator and operator functions are called when the corresponding operator is used. Following is an example of a global operator function.
#include <iostream>
using namespace std;
class ComplexNumber{
private:
int real;
int imaginary;
public:
ComplexNumber(int real, int imaginary){
this->real = real;
this->imaginary = imaginary;
}
void print(){
cout<<real<<" + i"<<imaginary;
}
ComplexNumber operator+ (ComplexNumber c2){
ComplexNumber c3(0,0);
c3.real = this->real+c2.real;
c3.imaginary = this->imaginary + c2.imaginary;
return c3;
}
};
int main() {
ComplexNumber c1(3,5);
ComplexNumber c2(2,4);
ComplexNumber c3 = c1 + c2;
c3.print();
return 0;
}
Important points about operator overloading 1) For operator overloading to work, at least one of the operands must be a user-defined class object. 2) Assignment Operator:
Compiler automatically creates a default assignment operator with every class. The default assignment operator does assign all members of the right side to the left side and works
fine in most cases (this behaviour is the same as the copy constructor). 3) Conversion Operator: We can also write conversion operators that can be used to convert one type to
another type.
4) Any constructor that can be called with a single argument works as a conversion constructor, which means it can also be used for implicit conversion to the class being
constructed.
Runtime Polymorphism
This kind of polymorphism is resolved at runtime. Its also called Dynamic binding or Late binding. ++ supports Runtime polymorphism with the help of features like virtual functions.
Virtual functions take the shape of the functions based on the type of object in reference and are resolved at runtime.
Method Overriding
In this process, an overridden method is called through the reference variable of a superclass. The determination of the method to be called is based on the object being
referred to by the reference variable.
Upcasting
If the reference variable of Parent class refers to the object of Child class, it is known as upcasting. For example:
Virtual Function
A virtual function is a member function which is declared within a base class and is re-defined (overridden) by a 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.
The idea is that virtual functions (https://www.geeksforgeeks.org/virtual-function-cpp/) are called according to the type of the object instance pointed to or referenced, not
according to the type of the pointer or reference. In other words, virtual functions are resolved late, at runtime.
If we don't make virtual function , and make a base class pointer that stores the address of derived class , then derived class can not be overridden because this happend due
to static linkage because the function is getting set only once by the compiler which is in the base class.
e.g.
#include <iostream>
using namespace std;
// Base class
class Shape {
public:
// parameterized constructor
Shape(int l, int w)
{
length = l;
width = w;
}
int get_Area()
{
cout << "This is call to parent class area\n";
// Returning 1 in user-defined function means true
return 1;
}
protected:
int length, width;
};
// Derived class
class Square : public Shape {
public:
Square(int l = 0, int w = 0)
: Shape(l, w)
{
} // declaring and initializing derived class
// constructor
int get_Area()
{
cout << "Square area: " << length * width << '\n';
return (length * width);
}
};
// Derived class
class Rectangle : public Shape {
public:
Rectangle(int l = 0, int w = 0)
: Shape(l, w)
{
} // declaring and initializing derived class
// constructor
int get_Area()
{
cout << "Rectangle area: " << length * width
<< '\n';
return (length * width);
}
};
int main()
{
Shape* s;
Output
This is call to parent class area
This is call to parent class area
class base
{
public:
virtual void print ()
{ cout<< "print base class" <<endl; }
void show ()
{ cout<< "show base class" <<endl; }
};
void show ()
{ cout<< "show derived class" <<endl; }
};
//main function
int main()
{
base *bptr;
derived d;
bptr = &d;
return 0;
}
What is the use? Virtual functions allow us to create a list of base class pointers and call methods of any of the derived classes without even knowing the kind of derived
class object.
class Employee {
public:
virtual void raiseSalary()
{
// common raise salary code
}
(https://media.geeksforgeeks.org/wp-content/uploads/VirtualFunction.png)
The compiler adds additional code at two places to maintain and use vptr.
1. Code in every constructor. This code sets the vptr of the object being created. This code sets vptr to point to the vtable of the class.
2. Code with polymorphic function call (e.g. bp->show() in above code). Wherever a polymorphic call is made, the compiler inserts code to first look for vptr using a base class
pointer or reference (In the above example, since the pointed or referred object is of a derived type, vptr of a derived class is accessed). Once vptr is fetched, vtable of derived
class can be accessed. Using vtable, the address of the derived class function show() is accessed and called.
Note: Compile Time Error is better than Run Time Error. So, java compiler renders compiler time error if you declare the same method having same parameters.
The keyword virtual tells the compiler it should not perform early binding. Instead, it should automatically install all the mechanisms necessary to perform late binding. This means
that if you call play( ) for a Brass object through an address for the base-class Instrument, you’ll get the proper function.
To accomplish this, the compiler creates a single table (called the VTABLE) for each class that contains virtual functions. The compiler places the addresses of the virtual functions
for that particular class in the VTABLE. In each class with virtual functions, it secretly places a pointer, called the vpointer (abbreviated as VPTR), which points to the VTABLE for that
object. When you make a virtual function call through a base-class pointer (that is, when you make a polymorphic call), the compiler quietly inserts code to fetch the VPTR and look
up the function address in the VTABLE, thus calling the right function and causing late binding to take place.
All of this – setting up the VTABLE for each class, initializing the VPTR, inserting the code for the virtual function call – happens automatically, so you don’t have to worry about it.
With virtual functions, the proper function gets called for an object, even if the compiler cannot know the specific type of the object.
With no virtual functions, the size of the object is exactly what you’d expect: the size of a single int. With a single virtual function in OneVirtual, the size of the object is the size of
NoVirtual plus the size of a void pointer. It turns out that the compiler inserts a single pointer (the VPTR) into the structure if you have one or more virtual functions. There is no size
difference between OneVirtual and TwoVirtuals. That’s because the VPTR points to a table of function addresses. You need only one because all the virtual function addresses are
contained in that single table.
This example required at least one data member. If there had been no data members, the C++ compiler would have forced the objects to be a nonzero size because each object must
have a distinct address. If you imagine indexing into an array of zero-sized objects, you’ll understand. A “dummy” member is inserted into objects that would otherwise be zero-sized.
When the type information is inserted because of the virtual keyword, this takes the place of the “dummy” member. Try commenting out the int a in all the classes in the above
example to see this.
Data Abstraction
So far, all of the virtual functions we have written have a body (a definition). However, C++ allows you to create a special kind of virtual function called a pure virtual function (or
abstract function) that has no body at all! A pure virtual function simply acts as a placeholder that is meant to be redefined by derived classes.
To create a pure virtual function, rather than define a body for the function, we simply assign the function the value 0.
class Base
{
public:
const char* sayHi() const { return "Hi"; } // a normal non-virtual function
virtual const char* getName() const { return "Base"; } // a normal virtual function
When we add a pure virtual function to our class, we are effectively saying, “it is up to the derived classes to implement this function”.
Using a pure virtual function has two main consequences: First, any class with one or more pure virtual functions becomes an abstract base class, which means that it can not be
instantiated!
Some Interesting Facts: 1) A class is abstract if it has at least one pure virtual function.
3) If we do not override the pure virtual function in derived class, then derived class also becomes abstract class.
A pure virtual destructor can be declared in C++. After a destructor (https://www.geeksforgeeks.org/destructors-c/)has been created as a pure virtual object(instance of a class),
where the destructor body is provided. This is due to the fact that destructors will not be overridden in derived classes, but will instead be called in reverse order. As a result, for a
pure virtual destructor, you must specify a destructor body.
When destroying instances of a derived class using a base class pointer object, a virtual destructor is used to free up memory space allocated by the derived class object or instance.
Note: Only Destructors can be Virtual. Constructors cannot be declared as virtual, this is because if you try to override a constructor by declaring it in a base/super class and call it in
the derived/sub class with same functionalities it will always give an error as overriding means a feature that lets us to use a method from the parent class in the child class which is
not possible.
class Node {
private:
int key;
Node* next;
/* Other members of Node Class */
Friend Function
Like friend class, a friend function can be given a special grant to access private and protected members. A friend function can be:
b. A global function
Following are some important points about friend functions and classes:
1) Friends should be used only for limited purpose. Too many functions or external classes are declared as friends of a class with protected or private data, it lessens the value of
encapsulation of separate classes in object-oriented programming.
2) Friendship is not mutual. If class A is a friend of B, then B doesn’t become a friend of A automatically.
public:
A() { a = 0; }
friend class B; // Friend Class
};
class B {
private:
int b;
public:
void showA(A& x)
{
// Since B is friend of A, it can access
// private members of A
std::cout << "A::a=" << x.a;
}
};
int main()
{
A a;
B b;
b.showA(a);
return 0;
}
output
A::a=0
CLASH OF CLANS
The game was a mimicry of the mobile game clash of clans . The game scene consists of different huts , one town halls , cannons , and wizard towers. We can release a limited
number of barbarians , balloons and archers in the ground as per level. Different keys were used for different purposes. Using A S D W we can move king or queen . When every
building is destroyed the user wins and passes for the next level. The main focus of the game is to implement the concept of OOPs. The game is implemented in Python.
Why python :
I used the python language because of its easy implementation compared to CPP. My main focus was to learn the concept of OOPs from this project, and to make the project shorter
and easily understandable I used python.The errors in python are visible in the terminal itself along with the line which makes it more appropriate to do big projects using OOPs.
Using inheritance we could reduce repeating coding . Using abstraction , I hid the functioning of attacking of different components of the game , using polymorphism (compile time )
different attacks by different people required set of variables that makes it compile time polymorphism.