Sie sind auf Seite 1von 67

EC6301 OBJECT ORIENTED PROGRAMMING AND DATA STRUCTURES

SYLLABUS-REGULATION 2013

OBJECTIVES:
To comprehend the fundamentals of object oriented programming, particularly in C++.
To use object oriented programming to implement data structures.
To introduce linear, non-linear data structures and their applications.
UNIT I DATA ABSTRACTION & OVERLOADING
Overview of C++ Structures Class Scope and Accessing Class Members Reference
Variables Initialization Constructors Destructors Member Functions and Classes
Friend Function Dynamic Memory Allocation Static Class Members Container Classes
and Integrators Proxy Classes Overloading: Function overloading and Operator
Overloading.
UNIT II INHERITANCE & POLYMORPHISM
Base Classes and Derived Classes Protected Members Casting Class pointers and
Member Functions Overriding Public, Protected and Private Inheritance Constructors
and Destructors in derived Classes Implicit Derived Class Object To Base Class Object
Conversion Composition Vs. Inheritance Virtual functions This Pointer Abstract Base
Classes and Concrete Classes Virtual Destructors Dynamic Binding.
UNIT III LINEAR DATA STRUCTURES
Abstract Data Types (ADTs) List ADT array-based implementation linked list
implementation singly linked lists Polynomial Manipulation - Stack ADT Queue ADT Evaluating arithmetic expressions
UNIT IV NON-LINEAR DATA STRUCTURES
Trees Binary Trees Binary tree representation and traversals Application of trees: Set
representation and Union-Find operations Graph and its representations Graph Traversals
Representation of Graphs Breadth-first search Depth-first search - Connected
components.

UNIT V SORTING and SEARCHING


Sorting algorithms: Insertion sort - Quick sort - Merge sort - Searching: Linear search
Binary Search
TOTAL: 45 PERIODS
OUTCOMES:
Upon completion of the course, students will be able to:
Explain the concepts of Object oriented programming.
Write simple applications using C++.
Discuss the different methods of organizing large amount of data.
TEXT BOOKS:
1. Deitel and Deitel, C++, How To Program, Fifth Edition, Pearson Education, 2005.
2. Mark Allen Weiss, Data Structures and Algorithm Analysis in C++,Third Edition,
Addison-Wesley, 2007.
REFERENCES:
1. Bhushan Trivedi, Programming with ANSI C++, A Step-By-Step approach, Oxford
University Press, 2010.

2. Goodrich, Michael T., Roberto Tamassia, David Mount, Data Structures and Algorithms
in C++, 7th Edition, Wiley. 2004.
3. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest and Clifford Stein,
"Introduction to Algorithms", Second Edition, Mc Graw Hill, 2002.
4. Bjarne Stroustrup, The C++ Programming Language, 3rd Edition, Pearson Education,
2007.
5. Ellis Horowitz, Sartaj Sahni and Dinesh Mehta, Fundamentals of Data Structures in C+
+,
Galgotia Publications, 2007.

EC6301 OBJECT ORIENTED PROGRAMMING AND DATA STRUCTURES


UNIT-1
1.1.

DATA ABSTRACTION AND OVERLOADING

OVERVIEW OF C++

Introduction of C++ / Basic concepts of OOPS / OVERVIEW OF C++


Before starting to learn C++ it is essential that one must have a basic knowledge of the
concepts of Object oriented programming. Some of the important object oriented features are
namely:
Objects
Classes
Inheritance
Data Abstraction
Data Encapsulation
Polymorphism
Overloading
Reusability
In order to understand the basic concepts in C++, the programmer must have a command of
the basic terminology in object-oriented programming. Below is a brief outline of the
concepts of Object-oriented programming languages:
Objects:
Object is the basic unit of object-oriented programming. Objects are identified by its unique
name. An object represents a particular instance of a class. There can be more than one
instance of an object. Each instance of an object can hold its own relevant data. An Object is
a collection of data members and associated member functions also known as methods.
Classes:
Classes are data types based on which objects are created. Objects with similar properties and
methods are grouped together to form a Class. Thus a Class represent a set of individual
objects. Characteristics of an object are represented in a class as Properties(Attributes) . The
actions that can be performed by objects becomes functions of the class and is referred to as
Methods (Functions).
Inheritance:
Inheritance is the process of forming a new class from an existing class or base class. The
base class is also known as parent class or super class, The new class that is formed is called
derived class. Derived class is also known as a child class or sub class. Inheritance helps in
reducing the overall code size of the program, which is an important concept in objectoriented programming.

Data Abstraction:
Data Abstraction increases the power of programming language by creating user defined data
types. Data Abstraction also represents the needed information in the program without
presenting the details.
Data Encapsulation:
Data Encapsulation combines data and functions into a single unit called Class. When using
Data Encapsulation, data is not accessed directly; it is only accessible through the functions
present inside the class. Data Encapsulation enables the important concept of data hiding
possible.
Polymorphism:
Polymorphism allows routines to use variables of different types at different times. An
operator or function can be given different meanings or functions. Polymorphism refers to a
single function or multi-functioning operator performing in different ways.
Overloading:
Overloading is one type of Polymorphism. It allows an object to have different meanings,
depending on its context. When an exiting operator or function begins to operate on new data
type, or class, it is understood to be overloaded.
Reusability:
This term refers to the ability for multiple programmers to use the same written and debugged
existing class of data. This is a time saving device and adds code efficiency to the language.
Additionally, the programmer can incorporate new features to the existing class, further
developing the application and allowing users to achieve increased performance. This time
saving feature optimizes code, helps in gaining secured applications and facilitates easier
maintenance on the application.
1.2.

STRUCTURES

Structure of C++ Program : Layout of C++ Program


C++ Programming language is most popular language after C Programming language. C++ is
first Object oriented programming language.We have summarize structure of C++ Program in
the following Picture

Structure of C++ Program


Section 1 : Header File Declaration Section
1.
Header files used in the program are listed here.
2.
Header File provides Prototype declaration for different library functions.
3.
We can also include user define header file.
4.
Basically all preprocessor directives are written in this section.
Section 2 : Global Declaration Section
1.
Global Variables are declared here.
2.
Global Declaration may include
o
Declaring Structure
o
Declaring Class
o
Declaring Variable
Section 3 : Class Declaration Section
1.
Actually this section can be considered as sub section for the global declaration
section.
2.
Class declaration and all methods of that class are defined here.
Section 4 : Main Function
1.
Each and every C++ program always starts with main function.
2.
This is entry point for all the function. Each and every method is called indirectly
through main.
3.
We can create class objects in the main.
4.
Operating system call this function automatically.
Section 5 : Method Definition Section
1.
This is optional section . Generally this method was used in C Programming.
1.3.
CLASS SCOPE AND ACCESSING CLASS MEMBERS
Access Specifiers / Control in Classes
Now before studying how to define class and its objects, lets first quickly learn what are
access specifiers.
Access specifiers in C++ class defines the access control rules. C++ has 3 new keywords
introduced, namely,
1.
2.
3.

public
private
protected

These access specifiers are used to set boundaries for availability of members of class be it
data members or member functions
Access specifiers in the program, are followed by a colon. You can use either one, two or all 3
specifiers in the same class to set different boundaries for different class members. They
change the boundary for all the declarations that follow them.

Public
Public, means all the class members declared under public will be available to everyone. The
data members and member functions declared public can be accessed by other classes too.
Hence there are chances that they might change them. So the key members must not be
declared public.
class PublicAccess
{
public: // public access specifier
int x;
// Data Member Declaration
void display(); // Member Function decaration
}
Private
Private keyword, means that no one can access the class members declared private outside
that class. If someone tries to access the private member, they will get a compile time error.
By default class variables and member functions are private.
class PrivateAccess
{
private: // private access specifier
int x;
// Data Member Declaration
void display(); // Member Function decaration
}
Protected
Protected, is the last access specifier, and it is similar to private, it makes class member
inaccessible outside the class. But they can be accessed by any subclass of that class. (If class
A is inherited by class B, then class B is subclass of class A. We will learn this later.)
class ProtectedAccess
{
protected: // protected access specifier
int x;
// Data Member Declaration
void display(); // Member Function decaration
}
1.4.

REFERENCE VARIABLES INITIALIZATION

Think of a variable name as a label attached to the variable's location in memory. You can
then think of a reference as a second label attached to that memory location. Therefore, you
can access the contents of the variable through either the original variable name or the
reference. For example, suppose we have the following example:

int

i = 17;

We can declare reference variables for i as follows.


int& r = i;
Read the & in these declarations as reference. Thus, read the first declaration as "r is an
integer reference initialized to i" and read the second declaration as "s is a double reference
initialized to d.". Following example makes use of references on int and double:
#include <iostream>
using namespace std;
int main ()
{
// declare simple variables
int i;
double d;
// declare reference variables
int& r = i;
double& s = d;
i = 5;
cout << "Value of i : " << i << endl;
cout << "Value of i reference : " << r << endl;
d = 11.7;
cout << "Value of d : " << d << endl;
cout << "Value of d reference : " << s << endl;
return 0;
}
When the above code is compiled together and executed, it produces the following result:
Value of i : 5
Value of i reference : 5
Value of d : 11.7
Value of d reference : 11.7
References are usually used for function argument lists and function return values. So
following are two important subjects related to C++ references which should be clear to a
C++ programmer:

1.5.
CONSTRUCTORS DESTRUCTORS
Constructors
Constructors are special class functions which performs initialization of every object. The
Compiler calls the Constructor whenever an object is created. Constructors iitialize values to
object members after storage is allocated to the object.
class A
{
int x;
public:
A(); //Constructor
};
While defining a contructor you must remeber that the name of constructor will be same as
the name of the class, and contructors never have return type.
Constructors can be defined either inside the class definition or outside class definition using
class name and scope resolution :: operator.
class A
{
int i;
public:
A(); //Constructor declared
};
A::A() // Constructor definition
{
i=1;
}
Types of Constructors
Constructors are of three types :
1.
2.
3.

Default Constructor
Parametrized Constructor
Copy COnstructor

Default Constructor
Default constructor is the constructor which doesn't take any argument. It has no parameter.
Syntax :
class_name ()

{ Constructor Definition }
Example :
class Cube
{
int side;
public:
Cube()
{
side=10;
}
};
int main()
{
Cube c;
cout << c.side;
}
Output : 10
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 initialization of object members, that even if we do
not define a constructor explicitly, the compiler will provide a default constructor implicitly.
class Cube
{
int side;
};
int main()
{
Cube c;
cout << c.side;
}
Output : 0
In this case, default constructor provided by the compiler will be called which will initialize
the object data members to default value, that will be 0 in this case.

Parameterized Constructor
These are the constructors with parameter. Using this Constructor you can provide different
values to data members of different objects, by passing the appropriate values as argument.

Example :
class Cube
{
int side;
public:
Cube(int x)
{
side=x;
}
};
int main()
{
Cube c1(10);
Cube c2(20);
Cube c3(30);
cout << c1.side;
cout << c2.side;
cout << c3.side;
}
OUTPUT : 10 20 30
By using parameterized construcor in above case, we have initialized 3 objects with user
defined values. We can have any number of parameters in a constructor.

Copy Constructor
These are special type of Constructors which takes an object as argument, and is used to copy
values of data members of one object into other object. We will study copy constructors in
detail later.

Constructor Overloading
Just like other member functions, constructors can also be overloaded. Infact when you have
both default and parameterized constructors defined in your class you are having Overloaded
Constructors, one with no parameter and other with parameter.
You can have any number of Constructors in a class that differ in parameter list.
class Student
{
int rollno;
string name;
public:
Student(int x)

{
rollno=x;
name="None";
}
Student(int x, string str)
{
rollno=x ;
name=str ;
}
};
int main()
{
Student A(10);
Student B(11,"Ram");
}
In above case we have defined two constructors with different parameters, hence overloading
the constructors.
One more important thing, if you define any constructor explicitly, then the compiler will not
provide default constructor and you will have to define it yourself.
In the above case if we write Student S; in main(), it will lead to a compile time error,
because we haven't defined default constructor, and compiler will not provide its default
constructor because we have defined other parameterized constructors.

Destructors
Destructor is a special class function which destroys the object as soon as the scope of object
ends. The destructor is called automatically by the compiler when the object goes out of
scope.
The syntax for destructor is same as that for the constructor, the class name is used for the
name of destructor, with a tilde ~ sign as prefix to it.
class A
{
public:
~A();
};
Destructors will never have any arguments.

Example to see how Constructor and Destructor is called


class A
{
A()
{
cout << "Constructor called";
}
~A()
{
cout << "Destructor called";
}
};
int main()
{
A obj1; // Constructor Called
int x=1
if(x)
{
A obj2; // Constructor Called
} // Destructor Called for obj2
} // Destructor called for obj1
Single Definition for both Default and Parameterized Constructor
In this example we will use default argument to have a single definition for both defualt and
parameterized constructor.
class Dual
{
int a;
public:
Dual(int x=0)
{
a=x;
}
};
int main()
{
Dual obj1;
Dual obj2(10);
}
Here, in this program, a single Constructor definition will take care for both these object
initializations. We don't need separate default and parameterized constructors

1.6.

MEMBER FUNCTIONS AND CLASSES

A member function of a class is a function that has its definition or its prototype within the
class definition like any other variable. It operates on any object of the class of which it is a
member, and has access to all the members of a class for that object.
Let us take previously defined class to access the members of the class using a member
function instead of directly accessing them:
class Box
{
public:
double length;
// Length of a box
double breadth;
// Breadth of a box
double height;
// Height of a box
double getVolume(void);// Returns box volume
};
Member functions can be defined within the class definition or separately using scope
resolution operator, ::. Defining a member function within the class definition declares the
function inline, even if you do not use the inline specifier. So either you can
defineVolume() function as below:
class Box
{
public:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
double getVolume(void)
{
return length * breadth * height;
}
};
If you like you can define same function outside the class using scope resolution
operator, :: as follows:
double Box::getVolume(void)
{
return length * breadth * height;
}

Here, only important point is that you would have to use class name just before :: operator. A
member function will be called using a dot operator (.) on a object where it will manipulate
data related to that object only as follows:
Box myBox;

// Create an object

myBox.getVolume(); // Call member function for the object


1.7.

FRIEND FUNCTION

A friend function of a class is defined outside that class' scope but it has the right to access
all private and protected members of the class. Even though the prototypes for friend
functions appear in the class definition, friends are not member functions.
A friend can be a function, function template, or member function, or a class or class
template, in which case the entire class and all of its members are friends.
To declare a function as a friend of a class, precede the function prototype in the class
definition with keyword friend as follows:
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
To declare all member functions of class ClassTwo as friends of class ClassOne, place a
following declaration in the definition of class ClassOne:
friend class ClassTwo;
Consider the following program:
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );

void setWidth( double wid );


};
// Member function definition
void Box::setWidth( double wid )
{
width = wid;
}
// Note: printWidth() is not a member function of any class.
void printWidth( Box box )
{
/* Because printWidth() is a friend of Box, it can
directly access any member of this class */
cout << "Width of box : " << box.width <<endl;
}
// Main function for the program
int main( )
{
Box box;
// set box width without member function
box.setWidth(10.0);
// Use friend function to print the wdith.
printWidth( box );
return 0;
}
When the above code is compiled and executed, it produces the following result:
Width of box : 10
1.8.

DYNAMIC MEMORY ALLOCATION

A good understanding of how dynamic memory really works in C++ is essential to


becoming a good C++ programmer. Memory in your C++ program is divided into two parts:

The stack: All variables declared inside the function will take up memory from the
stack.

The heap: This is unused memory of the program and can be used to allocate the
memory dynamically when program runs.

Many times, you are not aware in advance how much memory you will need to store
particular information in a defined variable and the size of required memory can be
determined at run time.
You can allocate memory at run time within the heap for the variable of a given type using a
special operator in C++ which returns the address of the space allocated. This operator is
called new operator.
If you are not in need of dynamically allocated memory anymore, you can
use deleteoperator, which de-allocates memory previously allocated by new operator.
The new and delete operators:
There is following generic syntax to use new operator to allocate memory dynamically for
any data-type.
new data-type;
Here, data-type could be any built-in data type including an array or any user defined data
types include class or structure. Let us start with built-in data types. For example we can
define a pointer to type double and then request that the memory be allocated at execution
time. We can do this using the new operator with the following statements:
double* pvalue = NULL; // Pointer initialized with null
pvalue = new double; // Request memory for the variable
The memory may not have been allocated successfully, if the free store had been used up. So
it is good practice to check if new operator is returning NULL pointer and take appropriate
action as below:
double* pvalue = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
The malloc() function from C, still exists in C++, but it is recommended to avoid using
malloc() function. The main advantage of new over malloc() is that new doesn't just allocate
memory, it constructs objects which is prime purpose of C++.

At any point, when you feel a variable that has been dynamically allocated is not anymore
required, you can free up the memory that it occupies in the free store with the delete
operator as follows:
delete pvalue;

// Release memory pointed to by pvalue

Let us put above concepts and form the following example to show how new and delete
work:
#include <iostream>
using namespace std;
int main ()
{
double* pvalue = NULL; // Pointer initialized with null
pvalue = new double; // Request memory for the variable
*pvalue = 29494.99; // Store value at allocated address
cout << "Value of pvalue : " << *pvalue << endl;
delete pvalue;

// free up the memory.

return 0;
}
If we compile and run above code, this would produce the following result:
Value of pvalue : 29495
1.9.

STATIC CLASS MEMBERS

We can define class members static using static keyword. When we declare a member of a
class as static it means no matter how many objects of the class are created, there is only one
copy of the static member.
A static member is shared by all objects of the class. All static data is initialized to zero
when the first object is created, if no other initialization is present. We can't put it in the class
definition but it can be initialized outside the class as done in the following example by
redeclaring the static variable, using the scope resolution operator :: to identify which class
it belongs to.
Let us try the following example to understand the concept of static data members:
#include <iostream>

using namespace std;


class Box
{
public:
static int objectCount;
// Constructor definition
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// Increase every time object is created
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
// Initialize static member of class Box
int Box::objectCount = 0;
int main(void)
{
Box Box1(3.3, 1.2, 1.5);
Box Box2(8.5, 6.0, 2.0);

// Declare box1
// Declare box2

// Print total number of objects.


cout << "Total objects: " << Box::objectCount << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result:
Constructor called.
Constructor called.
Total objects: 2
Static Function Members:

By declaring a function member as static, you make it independent of any particular object
of the class. A static member function can be called even if no objects of the class exist and
the static functions are accessed using only the class name and the scope resolution
operator ::.
A static member function can only access static data member, other static member functions
and any other functions from outside the class.
Static member functions have a class scope and they do not have access to the this pointer of
the class. You could use a static member function to determine whether some objects of the
class have been created or not.
Let us try the following example to understand the concept of static function members:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// Constructor definition
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// Increase every time object is created
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
// Initialize static member of class Box
int Box::objectCount = 0;

int main(void)
{
// Print total number of objects before creating object.
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5);
Box Box2(8.5, 6.0, 2.0);

// Declare box1
// Declare box2

// Print total number of objects after creating object.


cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result:
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2
1.10.

CONTAINER CLASSES AND INTEGRATORS

Containers
Standard Containers
A container is a holder object that stores a collection of other objects (its elements). They are
implemented as class templates, which allows a great flexibility in the types supported as
elements.
The container manages the storage space for its elements and provides member functions to
access them, either directly or through iterators (reference objects with similar properties to
pointers).
Containers replicate structures very commonly used in programming: dynamic arrays
(vector), queues (queue), stacks (stack), heaps (priority_queue), linked lists (list), trees (set),
associative arrays (map)...
Many containers have several member functions in common, and share functionalities. The
decision of which type of container to use for a specific need does not generally depend only
on the functionality offered by the container, but also on the efficiency of some of its
members (complexity). This is especially true for sequence containers, which offer different
trade-offs in complexity between inserting/removing elements and accessing them.
stack, queue and priority_queue are implemented as container adaptors. Container adaptors
are not full container classes, but classes that provide a specific interface relying on an object
of one of the container classes (such as deque or list) to handle the elements. The underlying

container is encapsulated in such a way that its elements are accessed by the members of the
container adaptor independently of the underlying container class used.
Container class templates
Sequence containers:
array -Array class (class template )
vector-Vector (class template )
deque-Double ended queue (class template )
forward_list -Forward list (class template )
list-List (class template )
Container adaptors:
Stack-LIFO stack (class template )
Queue-FIFO queue (class template )
priority_queue-Priority queue (class template )
Associative containers:
Set-Set (class template )
Multiset-Multiple-key set (class template )
Map-Map (class template )
Multimap-Multiple-key map (class template )
Unordered associative containers:
unordered_set -Unordered Set (class template )
unordered_multiset-Unordered Multiset (class template )
unordered_map -Unordered Map (class template )
unordered_multimap-Unordered Multimap (class template )
1.11.

PROXY CLASSES

A proxy is a class that provides a modified interface to another class. Here is an example suppose we have an array class that we we only want to be able to contain the binary digist 1
or 0. Here is a first try:
struct array1 {
int mArray[10];
int & operator[]( int i) {
/// what to put here
}
}; `
We want operator[] to complain if we say something like a[1] = 42, but that isn't possible
because the operator only sees the index of into the array, not the value being stored.
We can solve this using a proxy:
#include <iostream>

using namespace std;;


struct aproxy {
aproxy( int & r ) : mPtr( & r ) {}
void operator = ( int n ) {
if ( n > 1 ) {
throw "not binary digit";
}
*mPtr = n;
}
int * mPtr;
};
struct array {
int mArray[10];
aproxy operator[]( int i) {
return aproxy( mArray[i] );
}
};
int main() {
try {
array a;
a[0] = 1; // ok
a[0] = 42;
// throws exception
}
catch( const char * e ) {
cout << e << endl;
}
}
The proxy class now does our checking for a binary digit and we make the array's operator[]
return an instance of the proxy which has limited access to the array's internals.

1.12.

OVERLOADING:
OVERLOADING.

FUNCTION

OVERLOADING

AND

OPERATOR

Function Overloading
If any class have multiple functions with same names but different parameters then they are
said to be overloaded. Function overloading allows you to use the same name for different
functions, to perform, either same or different functions in the same class.
Function overloading is usually used to enhance the readability of the program. If you have to
perform one single operation but with different number or types of arguments, then you can
simply overload the function.

Ways to overload a function


1.
By changing number of Arguments.
2.
By having different types of argument.
Number of Arguments different
In this type of function overloading we define two functions with same names but different
number of parameters of the same type. For example, in the below mentioned program we
have made two sum() functions to return sum of two and three integers.
int sum (int x, int y)
{
cout << x+y;
}
int sum(int x, int y, int z)
{
cout << x+y+z;
}
Here sum() function is overloaded, to have two and three arguments. Which sum() function
will be called, depends on the number of arguments.
int main()
{
sum (10,20); // sum() with 2 parameter will be called
sum(10,20,30); //sum() with 3 parameter will be called
}
Different Datatype of Arguments
In this type of overloading we define two or more functions with same name and same
number of parameters, but the type of parameter is different. For example in this program, we
have two sum() function, first one gets two integer arguments and second one gets two
double arguments.
int sum(int x,int y)
{
cout<< x+y;
}
double sum(double x,double y)
{
cout << x+y;
}
int main()
{

sum (10,20);
sum(10.5,20.5);
}
Operator Overloading in C++
In C++ the overloading principle applies not only to functions, but to operators too. That is,
of operators can be extended to work not just with built-in types but also classes. A
programmer can provide his or her own operator to a class by overloading the built-in
operator to perform some specific computation when the operator is used on objects of that
class. Is operator overloading really useful in real world implementations? It certainly can be,
making it very easy to write code that feels natural (we'll see some examples soon). On the
other hand, operator overloading, like any advanced C++ feature, makes the language more
complicated. In addition, operators tend to have very specific meaning, and most
programmers don't expect operators to do a lot of work, so overloading operators can be
abused to make code unreadable. But we won't do that.
Complex a(1.2,1.3);
Complex b(2.1,3);
part
Complex c = a+b;

//this class is used to represent complex numbers


//notice the construction taking 2 parameters for the real and imaginary
//for this to work the addition operator must be overloaded

The addition without having overloaded operator + could look like this:
Complex c = a.Add(b);
This piece of code is not as readable as the first example though--we're dealing with numbers,
so doing addition should be natural. (In contrast to cases when programmers abuse this
technique, when the concept represented by the class is not related to the operator--ike using
+ and - to add and remove elements from a data structure. In this cases operator overloading
is
a
bad
idea,
creating
confusion.)
In order to allow operations like Complex c = a+b, in above code we overload the "+"
operator. The overloading syntax is quite simple, similar to function overloading, the
keyword operator must be followed by the operator we want to overload:
class Complex
{
public:
Complex(double re,double im)
:real(re),imag(im)
{};
Complex operator+(const Complex& other);
Complex operator=(const Complex& other);
private:
double real;
double imag;
};
Complex Complex::operator+(const Complex& other)
{

double result_real = real + other.real;


double result_imaginary = imag + other.imag;
return Complex( result_real, result_imaginary );
}
The assignment operator can be overloaded similarly. Notice that we did not have to call any
accessor functions in order to get the real and imaginary parts from the parameter other since
the overloaded operator is a member of the class and has full access to all private data.
Alternatively, we could have defined the addition operator globally and called a member to
do the actual work. In that case, we'd also have to make the method a friend of the class, or
use an accessor method to get at the private data:
friend Complex operator+(Complex);
Complex operator+(const Complex &num1, const Complex &num2)
{
double result_real = num1.real + num2.real;
double result_imaginary = num1.imag + num2.imag;
return Complex( result_real, result_imaginary );
}
Why would you do this? when the operator is a class member, the first object in the
expression must be of that particular type. It's as if you were writing:
Complex a( 1, 2 );
Complex a( 2, 2 );
Complex c = a.operator=( b );
when it's a global function, the implicit or user-defined conversion can allow the operator to
act even if the first operand is not exactly of the same type:
Complex c = 2+b;
expression is valid

//if the integer 2 can be converted by the Complex class, this

By the way, the number of operands to a function is fixed; that is, a binary operator takes two
operands, a unary only one, and you can't change it. The same is true for the precedence of
operators too; for example the multiplication operator is called before addition. There are
some operators that need the first operand to be assignable, such as : operator=, operator(),
operator[] and operator->, so their use is restricted just as member functions(non-static), they
can't be overloaded globally. The operator=, operator& and operator, (sequencing) have
already defined meanings by default for all objects, but their meanings can be changed by
overloading or erased by making them private.

UNIT-2

INHERITANCE AND POLYMORPHISM

2.1 Base Classes and Derived Classes


Inheritance is the process by which new classes called derived classes are created from
existing classes called base classes. The derived classes have all the features of the base class
and the programmer can choose to add new features specific to the newly created derived
class. For example, a programmer can create a base class named fruit and define derived
classes as mango, orange, banana, etc. Each of these derived classes, (mango, orange, banana,
etc.) has all the features of the base class (fruit) with additional attributes or features specific
to these newly created derived classes. Mango would have its own defined features, orange
would have its own defined features, banana would have its own defined features, etc. This
concept of Inheritance leads to the concept of polymorphism.

(1) Simple / Single


(2) Multilevel
(3) Hierarchical
(4) Multiple
(5) Hybrid
Accessibility modes and Inheritance
We can use the following chart for seeing the accessibility of the members in the Base class
(first class) and derived class (second class).

Here X indicates that the members are not inherited, i.e. they are not accessible in the derived
class.
Multiple inheritance
We can derive a class from any number of base classes. Deriving a class from more than one
direct base class is called multiple inheritance. In the following example, classes A, B, and C
are direct base classes for the derived class X: class A { /* ... */ }; class B { /* ... */ }; class C
{ /* ... */ }; class X : public A, private B, public C { /* ... */ }; The following inheritance
graph describes the inheritance relationships of the above example. An arrow points to the
direct base class of the class at the tail of the arrow:

The order of derivation is relevant only to determine the order of default initialization by
constructors and cleanup by destructors. A direct base class cannot appear in the base list of a
derived class more than once: class B1 { /* ... */ }; // direct base class class D : public B1,
private B1 { /* ... */ }; // error
However, a derived class can inherit an indirect base class more than once, as shown in the
following example:
class L { /* ... */ }; // indirect base class
class B2 : public L { /* ... */ }; class B3 : public L { /* ... */ }; class D : public B2, public B3
{ /* ... */ }; // valid In the above example, class D inherits the indirect base class L once
through class B2 and once through class B3. However, this may lead to ambiguities because
two subobjects of class L exist, and both are accessible through class D. You can avoid this
ambiguity by referring to class L using a qualified class name. For example: B2::L or B3::L.
we can also avoid this ambiguity by using the base specifier virtual to declare a base class.
2.2 Protected Members
rotected members can be accessed from derived classes. Private ones can't.
class Base {

private:
int MyPrivateInt;
protected:
int MyProtectedInt;
public:
int MyPublicInt;
}
class Derived : Base
{
public:
int foo1() { return MyPrivateInt;} // Won't compile!
int foo2() { return MyProtectedInt;} // OK
int foo3() { return MyPublicInt;} // OK
};
class Unrelated
{
private:
Base B;
public:
int foo1() { return B.MyPrivateInt;} // Won't compile!
int foo2() { return B.MyProtectedInt;} // Won't compile
int foo3() { return B.MyPublicInt;} // OK
};
In terms of "best practice", it depends. If there's even a faint possibility that someone might
want to derive a new class from your existing one and need access to internal members, make
them Protected, not Private. If they're private, your class may become difficult to inherit from
easily.

2.3 Casting Class pointers and Member Functions


Casting isn't usually necessary in student-level C++ code, but understanding why it's needed
and the restrictions involved can help widen one's understanding of C++. This document uses
some basic knowledge about inheritance, pointers and casting to explore casting without
going too deeply into the mechanics of C++ (see theNotes section for further information). If
you think you already know the basics of inheritance and casting (C++ casting, not old Cstyle casting), skip to the next section.
Basic inheritance and casting
Inheritance
C++ lets you invent new types of variables. Here's a simple example

class base {
public:
int i;
};
This created a new type of variable called base. You can create a new variable of that type
much as you can create a variable of a standard type base b;
will create a new variable b. Inside this variable there's an integer called b.i.
You can also built new variable types by extending existing ones. The following code creates
a variable called d that contains an integer i (inherited from base) and a float called f
class derived: public base {
public:
float f;
};
derived d;
You can build a family tree of related types if you want.
Casting
C++ lets you convert between types. This is called casting. Sometimes this happens
automatically. For example,
int i=7;
float f=i;
works without fuss because implicit casting happens. It also lets you do
float f=7.7;
int i=f;
even though an integer can't store the value 7.7.

Sometimes C++ will only let you cast if you explicitly request it. C++ has 4 casting operators
- const_cast,static_cast, dynamic_cast, and reinterpret_cast. Some of these will be mentioned
later.
Sometimes none of the 4 operators will work, because C++ can't determine how the casting
should be done. In such cases, you need to write a routine. That will also be covered later.
Casting between user-defined types
Implicit casting can happen between standard types (like float, double, etc). It can also
happen between related user-defined types. The following code (that uses the new types
invented earlier) compiles without protest.
class base {
public:
int i;
};
class derived: public base {
public:
float f;
};
int main () {
base b;
derived d;
b=d;
}
d has 2 fields, b only has one, but C++ ignores the extra field, rather in the way that it ignores
the decimal places in the earlier example when an integer was made from a float.
Suppose in this example we had d=b; instead of b=d;. What would happen? You might think
that d.i=b.i happens, and that d.f is unchanged, or set to 0, but in fact the compiler doesn't
allow the cast.
It's worth noticing that C++ only allowed b=d; in the first place because the 2 types involved
were related. In the following example the 2nd new type isn't derived from the 1st, though it
has exactly the same components as the derived type in the previous example. It doesn't
compile.
class base {
public:

int i;
};
class base2 {
public:
int i;
float f;
};
int main () {
base b;
base2 d;
b=d;
}
Let's try something else. Suppose we create 2 types derived from the same base type that
have exactly the same components.
class base {
public:
int i;
};
class child1: public base {
public:
float f;
};
class child2: public base {
public:
float f;
};
int main () {
child1 c1;
child2 c2;
c1=c2;
}
Can these siblings work together? No, the code doesn't compile. So as you can see, C++ is
cautious about casting.
The casting operators

C++'s casting operators can be used to force the issue when C++ is uncertain about the user's
intentions, but the command can only work if C++ already knows what should be done. For
now, let's focus on just 2 of the operators

static_cast - Such conversions rely on static (compile-time) type information. More


generally, a static_cast may be used to perform the explicit inverse of the implicit
standard conversions.
reinterpret_cast - to perform type conversions on unrelated types.You should use this
type of cast only when absolutely necessary.

Pointers and casting


Pointers can be cast too. Here's an example
class base {
public:
int i;
};
class derived: public base {
public:
float f;
};
int main () {
base b;
derived d;
base *pointer_to_base;
derived *pointer_to_derived;
pointer_to_base=&d; // OK. Note that you can't access d.f via
// pointer_to_base though.
// pointer_to_derived=&b; // Not ok. Were it allowed, then
// pointer_to_derived->f=7; would cause trouble, because b
// doesn't have an f field. But you can force the issue.
pointer_to_derived=static_cast<derived*>(&b); // Allowed
}
If you wanted to store the pointer's value in an integer, the following wouldn't work
class base {
public:
int i;
};

int main () {
base b;
int i;
i=&b;
}
You can't use i=static_cast<int>(&b); either, because the types aren't similar enough. You
need to usei=reinterpret_cast<int>(&b);, but the reinterpret_cast should be used with care
because it can lead to dangerous situations, as this code fragment illustrates
...
int main () {
base *pointer_to_base;
int i=-1;
pointer_to_base=reinterpret_cast<base*>(i);
}
The compiler lets this happen, but pointer_to_base won't point to a valid location for an
object on today's machines.
Writing member functions
Classes can not only contain variables, they can contain functions too. Conversion routines
are ideal candidates for inclusion within a class. Here the derived class is being extended so
that it can deal with the problems mentioned above. An extra constructor (a copy constructor)
is added so that a derived object can be created as a copy of a base object, the derived
object's f field being initialised to 0.
class base {
public:
int i;
};
class derived: public base {
public:
float f;
derived(const base& b) {
i=b.i;
f=0;
};
};

int main () {
base b;
derived d=b;
}

2.4 Overriding
If we inherit a class into the derived class and provide a definition for one of the base class's
function again inside the derived class, then that function is said to be overridden, and this
mechanism is called Function Overriding
class Base
{
public:
void show()
{
cout << "Base class";
}
};
class Derived:public Base
{
public:
void show()
{
cout << "Derived Class";
}
}
In this example, function show() is overridden in the derived class. Now let us study how
these overridden functions are called in main() function.

2.5 Public, Protected and Private Inheritance


class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B

};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A
{
// x is private
// y is private
// z is not accessible from D
};
IMPORTANT NOTE: Classes B, C and D all contain the variables x, y and z.

One of the most important concepts in object-oriented programming is that of inheritance.


Inheritance allows us to define a class in terms of another class, which makes it easier to
create and maintain an application. This also provides an opportunity to reuse the code
functionality and fast implementation time.
When creating a class, instead of writing completely new data members and member
functions, the programmer can designate that the new class should inherit the members of an
existing class. This existing class is called the base class, and the new class is referred to as
the derived class.
The idea of inheritance implements the is a relationship. For example, mammal IS-A animal,
dog IS-A mammal hence dog IS-A animal as well and so on.
Base & Derived Classes:
A class can be derived from more than one classes, which means it can inherit data and
functions from multiple base classes. To define a derived class, we use a class derivation list
to specify the base class(es). A class derivation list names one or more base classes and has
the form:
class derived-class: access-specifier base-class
Where access-specifier is one of public, protected, or private, and base-class is the name of
a previously defined class. If the access-specifier is not used, then it is private by default.
Consider a base class Shape and its derived class Rectangle as follows:
#include <iostream>
using namespace std;
// Base class
class Shape
{
public:
void setWidth(int w)
{
width = w;

}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// Derived class
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
int main(void)
{
Rectangle Rect;
Rect.setWidth(5);
Rect.setHeight(7);
// Print the area of the object.
cout << "Total area: " << Rect.getArea() << endl;
return 0;
}
When the above code is compiled and executed, it produces the following result:
Total area: 35
Access Control and Inheritance:
A derived class can access all the non-private members of its base class. Thus base-class
members that should not be accessible to the member functions of derived classes should be
declared private in the base class.
We can summarize the different access types according to who can access them in the
following way:
Access

public

protected

private

Same class

yes

yes

yes

Derived classes

yes

yes

no

Outside classes

yes

no

no

A derived class inherits all base class methods with the following exceptions:

Constructors, destructors and copy constructors of the base class.


Overloaded operators of the base class.
The friend functions of the base class.
Type of Inheritance:
When deriving a class from a base class, the base class may be inherited through public,
protected or private inheritance. The type of inheritance is specified by the access-specifier
as explained above.
We hardly use protected or private inheritance, but public inheritance is commonly used.
While using different type of inheritance, following rules are applied:
Public
Inheritance: When
deriving
a
class
from
a public base
class, public members of the base class become public members of the derived class
and protected members of the base class become protected members of the derived
class. A base class'sprivate members are never accessible directly from a derived
class, but can be accessed through calls to the public andprotected members of the
base class.
Protected
Inheritance: When
deriving
from
a protected base
class, public and protected members of the base class becomeprotected members of
the derived class.
Private
Inheritance: When
deriving
from
a private base
class,public and protected members of the base class become privatemembers of
the derived class.
2.6 Constructors and Destructors in derived Classes
EDIT : Summary of answers
down
In the following, B is a subclass of A.
votefavorite
It's a matter of terminology; ctors and dtors are not inherited, in the sense that
6
the ctor/dtor of B willnot be borrowed from A's interface. A class has at least
one constructor, and has exactly one destructor.

Constructors:

B does not inherit constructors from A;

Unless B's ctor explicitely calls one of A's ctor, the default ctor
from A will be called automatically before B's ctor body (the idea
being that A needs to be initialized before B gets created).

Destructors:

B does not inherit A's dtor;

After it exits, B's destructor will automatically call A's


destructor.
#include <cstdio>
/******************************/
// Base class
struct A
{
A() { printf( "\tInstance counter = %d (ctor)\n", ++instance_counter ); }
~A() { printf( "\tInstance counter = %d (dtor)\n", --instance_counter ); }

static int instance_counter;


};
// Inherited class with default ctor/dtor
class B : public A {};
// Inherited class with defined ctor/dtor
struct C : public A
{
C() { printf("\tC says hi!\n"); }
~C() { printf("\tC says bye!\n"); }
};
/******************************/
// Initialize counter
int A::instance_counter = 0;
/******************************/
// A few tests
int main()
{
printf("Create A\n"); A a;
printf("Delete A\n"); a.~A();
printf("Create B\n"); B b;
printf("Delete B\n"); b.~B();
printf("Create new B stored as A*\n"); A *a_ptr = new B();
printf("Delete previous pointer\n"); delete a_ptr;
printf("Create C\n"); C c;
printf("Delete C\n"); c.~C();
}
and here is the output (compiled with g++ 4.4.3):
Create A
Instance counter = 1 (ctor)
Delete A
Instance counter = 0 (dtor)
Create B
Instance counter = 1 (ctor)
Delete B
Instance counter = 0 (dtor)
Create new B stored as A*
Instance counter = 1 (ctor)
Delete previous pointer

Instance counter = 0 (dtor)


Create C
Instance counter = 1 (ctor)
C says hi!
Delete C
C says bye!
Instance counter = 0 (dtor) // We exit main() now
C says bye!
Instance counter = -1 (dtor)
Instance counter = -2 (dtor)
Instance counter = -3 (dtor)

2.7 Implicit Derived


class A {};
class B : public A {};
template <typename T> class Base {};
class Derived : public Base<B> {};
int main() {
Derived d;
Base<A>* base = new Derived();
}
Basically, I have a template base class Base that I derive Derived : public Base<B> from.
Then I have to cast it to the most general occuring form of Base, which is Base<A>.
I would have thought that I can cast an Object deriving from Base<B> to Base<A> implicitly,
as Bderives from A. Am I doing something wrong or how could I cast that implicitly? This is
important as I need to accept all types of Derived classes in a method of Base as a parameter.

2,8 Class Object To Base Class Object Conversion


class A
{
public:
A():x(2){};
private:
int x;
};
class B : public A
{
public:

B():A(),y(5){};
private:
int y;
};
class C : public B
{
public:
C():B(),z(9){};
private:
int z;
};
int main()
{
C *CObj = new C;
B *pB = static_cast<B*>(CObj);
delete CObj;
}
2.9 Composition Vs. Inheritance
Composition syntax
Actually, youve been using composition all along to create classes. Youve just been
composing classes primarily with built-in types (and sometimes strings). It turns out to be
almost as easy to use composition with user-defined types.
Consider a class that is valuable for some reason:
//: C14:Useful.h
// A class to reuse
#ifndef USEFUL_H
#define USEFUL_H
class X {
int i;
public:
X() { i = 0; }
void set(int ii) { i = ii; }
int read() const { return i; }
int permute() { return i = i * 47; }
};
#endif // USEFUL_H ///:~
The data members are private in this class, so its completely safe to embed an object of
type X as a public object in a new class, which makes the interface straightforward:
//: C14:Composition.cpp

// Reuse code with composition


#include "Useful.h"
class Y {
int i;
public:
X x; // Embedded object
Y() { i = 0; }
void f(int ii) { i = ii; }
int g() const { return i; }
};
int main() {
Y y;
y.f(47);
y.x.set(37); // Access the embedded object
} ///:~
Accessing the member functions of the embedded object (referred to as a subobject) simply
requires another member selection.
Its more common to make the embedded objects private, so they become part of the
underlying implementation (which means you can change the implementation if you want).
The public interface functions for your new class then involve the use of the embedded
object, but they dont necessarily mimic the objects interface:
//: C14:Composition2.cpp
// Private embedded objects
#include "Useful.h"
class Y {
int i;
X x; // Embedded object
public:
Y() { i = 0; }
void f(int ii) { i = ii; x.set(ii); }
int g() const { return i * x.read(); }
void permute() { x.permute(); }
};
int main() {
Y y;
y.f(47);
y.permute();
} ///:~
Here, the permute( ) function is carried through to the new class interface, but the other
member functions of X are used within the members of Y.

Inheritance syntax
The syntax for composition is obvious, but to perform inheritance theres a new and different
form.
When you inherit, you are saying, This new class is like that old class. You state this in
code by giving the name of the class as usual, but before the opening brace of the class body,
you put a colon and the name of the base class (or base classes, separated by commas,
formultiple inheritance). When you do this, you automatically get all the data members and
member functions in the base class. Heres an example:
//: C14:Inheritance.cpp
// Simple inheritance
#include "Useful.h"
#include <iostream>
using namespace std;
class Y : public X {
int i; // Different from X's i
public:
Y() { i = 0; }
int change() {
i = permute(); // Different name call
return i;
}
void set(int ii) {
i = ii;
X::set(ii); // Same-name function call
}
};
int main() {
cout << "sizeof(X) = " << sizeof(X) << endl;
cout << "sizeof(Y) = "
<< sizeof(Y) << endl;
Y D;
D.change();
// X function interface comes through:
D.read();
D.permute();
// Redefined functions hide base versions:
D.set(12);
} ///:~
You can see Y being inherited from X, which means that Y will contain all the data elements
in X and all the member functions in X. In fact, Y contains a subobject of X just as if you had
created a member object of X inside Y instead of inheriting from X. Both member objects
and base class storage are referred to as subobjects.

All the private elements of X are still private in Y; that is, just because Y inherits
from X doesnt mean Y can break the protection mechanism. The private elements of X are
still there, they take up space you just cant access them directly.
In main( ) you can see that Ys data elements are combined with Xs because the sizeof(Y) is
twice as big as sizeof(X).
Youll notice that the base class is preceded by public. During inheritance, everything
defaults to private. If the base class were not preceded by public, it would mean that all of
the public members of the base class would be private in the derived class. This is almost
never what you want[51]; the desired result is to keep all the public members of the base
class public in the derived class. You do this by using the public keyword during inheritance.
In change( ), the base-class permute( ) function is called. The derived class has direct access
to all the public base-class functions.
The set( ) function in the derived class redefines the set( ) function in the base class. That is,
if you call the functions read( ) andpermute( ) for an object of type Y, youll get the baseclass versions of those functions (you can see this happen inside main( )). But if you
call set( ) for a Y object, you get the redefined version. This means that if you dont like the
version of a function you get during inheritance, you can change what it does. (You can also
add completely new functions like change( ).)
However, when youre redefining a function, you may still want to call the base-class
version. If, inside set( ), you simply call set( ) youll get the local version of the function a
recursive function call. To call the base-class version, you must explicitly name the base class
using the scope resolution operator.

2.10 Virtual functions


If you want to execute the member function of derived class then, you can
declare display( ) in the base class virtual which makes that function existing in
appearance only but, you can't call that function. In order to make a function virtual,
you have to add keyword virtual in front of a function.
/* Example to demonstrate the working of virtual function in C++ programming. */
#include <iostream>
using namespace std;
class B
{
public:
virtual void display()
/* Virtual function */
{ cout<<"Content of base class.\n"; }
};

class D1 : public B
{
public:
void display()
{ cout<<"Content of first derived class.\n"; }
};
class D2 : public B
{
public:
void display()
{ cout<<"Content of second derived class.\n"; }
};
int main()
{
B *b;
D1 d1;
D2 d2;
/* b->display(); // You cannot use this code here because the function of base class
is virtual. */
b = &d1;
b->display(); /* calls display() of class derived D1 */
b = &d2;
b->display(); /* calls display() of class derived D2 */
return 0;
}
Output
Content of first derived class.
Content of second derived class.
After the function of base class is made virtual, code b->display( ) will call
the display( ) of the derived class depending upon the content of pointer.
In this program, display( ) function of two different classes are called with same code
which is one of the example of polymorphism in C++ programming using virtual
functions.

2.11 This Pointer


Every object in C++ has access to its own address through an important pointer
called this pointer. The this pointer is an implicit parameter to all member functions.
Therefore, inside a member function, this may be used to refer to the invoking object.

Friend functions do not have a this pointer, because friends are not members of a class. Only
member functions have a this pointer.
Let us try the following example to understand the concept of this pointer:
#include <iostream>
using namespace std;
class Box
{
public:
// Constructor definition
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
}
double Volume()
{
return length * breadth * height;
}
int compare(Box box)
{
return this->Volume() > box.Volume();
}
private:
double length; // Length of a box
double breadth; // Breadth of a box
double height; // Height of a box
};
int main(void)
{
Box Box1(3.3, 1.2, 1.5);
Box Box2(8.5, 6.0, 2.0);

// Declare box1
// Declare box2

if(Box1.compare(Box2))
{
cout << "Box2 is smaller than Box1" <<endl;
}
else
{
cout << "Box2 is equal to or larger than Box1" <<endl;
}
return 0;
}

When the above code is compiled and executed, it produces the following result:
Constructor called.
Constructor called.
Box2 is equal to or larger than Box1
2.12 Abstract Base Classes and Concrete Classes
An abstract class is a class for which one or more methods are declared but not defined,
meaning that the compiler knows these methods are part of the class, but not what code to
execute for that method. These are called abstract methods. Here is an example of an abstract
class.
class shape {
public:
virtual void draw() = 0;
};
This declares an abstract class which specifies that any descendants of the class should
implement the draw method if the class is to be concrete. You cannot instantiate this class
because it is abstract, after all, the compiler wouldn't know what code to execute if you called
member draw. So you can not do the following:
shape my_shape();
my_shape.draw();
To be able to actually use the draw method you would need to derive classes from this
abstract class, which do implement the draw method, making the classes concrete:
class circle : public shape {
public:
circle(int x, int y, int radius) {
/* set up the circle */
}
virtual draw() {
/* do stuff to draw the circle */
}
};
class rectangle : public shape {
public:
rectangle(int min_x, int min_y, int max_x, int max_y) {
/* set up rectangle */
}
virtual draw() {
/* do stuff to draw the rectangle */
}
};

Now you can instantiate the concrete objects circle and rectangle and use their draw methods:
circle my_circle(40, 30, 10);
rectangle my_rectangle(20, 10, 50, 15);
my_circle.draw();
my_rectangle.draw();
Now of course the question is, why would you want to do this? Couldn't you just as well have
defined the circle and rectangle classes and have done away with the whole shape class? You
could, but then you wouldn't be able to take advantage of their inheritance:
std::vector<shape*> my_scene;
my_scene.push_back(new circle(40, 30, 10));
my_scene.push_back(new rectangle(20, 10, 50, 15));
std::for_each(my_scene.begin(), my_scene.end(), std::mem_fun_ref(&shape::draw)
This code let's you collect all your shapes into one container. This makes it a lot easier if you
have a lot of shapes and many different shapes in your scene. For example we can now draw
all the shapes in one go, and the code that does so doesn't even need to know about the
different types of shapes we have.
Now finally we need to know why the draw function of shape is abstract, and not just an
empty function, i.e. why didn't we just define:
class shape {
public:
virtual void draw() {
/* do nothing */
}
};
The reason for this is that we don't really want objects of type shape, they wouldn't be real
things anyway, they would be abstract. So it doesn't make any sense to define an
implementation for the draw method, even an empty one. Making the shape class abstract
prevents us from mistakenly instantiating the shape class, or mistakenly calling the empty
draw function of the base class instead of the draw function of the derived classes. In effect
we define an interface for any class that would like to behave like a shape, we say that any
such class should have a draw method that looks like we have specified it should.
To answer you last question, there isn't any such thing as a 'normal derived class' every class
is either abstract or concrete. A class that has any abstract methods is abstract, any class that
doesn't is concrete. It's just a way to differentiate the two types of classes. A base class can be
either abstract or concrete and a derived class can be either abstract or concrete:
class abstract_base {
public:
virtual void abstract_method1() = 0;

virtual void abstract_method2() = 0;


};
class concrete_base {
public:
void concrete_method1() {
/* do something */
}
};
class abstract_derived1 : public abstract_base {
public:
virtual void abstract_method3() = 0;
};
class abstract_derived2 : public concrete_base {
public:
virtual void abstract_method3() = 0;
};
class abstract_derived3 : public abstract_base {
public:
virtual abstract_method1() {
/* do something */
}
/* note that we do not provide an implementation for
abstract_method2 so the class is still abstract */
};
class concrete_derived1 : public concrete_base {
public:
void concrete_method2() {
/* do something */
}
};
class concrete_derived2 : public abstract_base {
public:
virtual void abstract_method1() {
/* do something */
}
virtual void abstract_method2() {
/* do something */
}
/* This class is now concrete because no abstract methods remain */
};
2.13 Virtual Destructors

he only thing we will need to change is the destructor in the Base class and heres what it will
look like note that we highlighted the part of the code where the virtual keyword has been
added in red:
class Base
{
public:
Base(){ cout<<"Constructing Base";}
// this is a virtual destructor:
virtual ~Base(){ cout<<"Destroying Base";}
};
Now, with that change, the output after running the code above will be:
Constructing Base
Constructing Derive
Destroying Derive
Destroying Base
Note that the derived class destructor will be called before the base class.
2.14 Dynamic Binding.
Static Binding:

By default, matching of function call with the correct function definition happens at compile
time. This is called static binding or early binding or compile-time binding. Static binding is
achieved using function overloading and operator overloading. Even though there are two or
more functions with same name, compiler uniquely identifies each function depending on the
parameters passed to those functions.
Dynamic Binding:

C++ provides facility to specify that the compiler should match function calls with the correct
definition at the run time; this is called dynamic binding or late binding or run-time binding.
Dynamic binding is achieved using virtual functions. Base class pointer points to derived
class object. And a function is declared virtual in base class, then the matching function is
identified at run-time using virtual table entry.
In OOPs Dynamic Binding refers to linking a procedure call to the code that will be
executed only at run time. The code associated with the procedure in not known until the
program is executed, which is also known as late binding.

Example:
#include <iostream.h>
int Square(int x)
{ return x*x; }
int Cube(int x)
{ return x*x*x; }
int main()
{
int x =10;
int choice;
do
{
cout << "Enter 0 for square value, 1 for cube value: ";
cin >> choice;
} while (choice < 0 || choice > 1);
int (*ptr) (int); switch (choice)
{
case 0: ptr = Square; break;
case 1: ptr = Cube; break;
} cout << "The result is: " << ptr(x) << endl;
return 0; }
Result:
Enter 0 for square value,
1 for cube value:0
The result is:100
In the above OOPs example the functions "Square" and "Cube" are called only at runtime
based on the value given for "choice". Then a pointer "ptr" is used to call the appropriate
function to get the result.

UNIT-3

LINEAR DATA STRUCTURES

3.1 Abstract Data Types (ADTs)


An abstract data type (ADT) is a set of objects together with a set of operations.
Abstract data types are mathematical abstractions; nowhere in an ADTs definition is there
any mention of how the set of operations is implemented. Objects such as lists, sets, and
graphs, along with their operations, can be viewed as ADTs, just as integers, reals, and
booleans are data types. Integers, reals, and booleans have operations associated with them,
and so do ADTs. For the set ADT, we might have such operations as add, remove, size, and
contains. Alternatively, we might only want the two operations union and find, which would
define a different ADT on the set. The C++ class allows for the implementation of ADTs,
with appropriate hiding of implementation details. Thus, any other part of the program that
needs to perform an operation on the ADT can do so by calling the appropriate method. If for
some reason implementation details need to be changed, it should be easy to do so by merely
changing the routines that perform the ADT operations. This change, in a perfect world,
would be completely transparent to the rest of the program.
3.2 List ADT
We will deal with a general list of the form A0, A1, A2, ..., AN1. We say that the size of this
list is N. We will call the special list of size 0 an empty list. For any list except the empty list,
we say that Ai follows (or succeeds) Ai1 (i < N) and that Ai1 precedes Ai (i > 0). The first
element of the list is A0, and the last element is AN1. We will not define the predecessor of
A0 or the successor of AN1. The position of element Ai in a list is i. Throughout this
discussion, we will assume, to simplify matters, that the elements in the list are integers, but
in general, arbitrarily complex elements are allowed (and easily handled by a class template).
Associated with these definitions is a set of operations that we would like to perform on the
List ADT. Some popular operations are printList and makeEmpty, which do the obvious
things; find, which returns the position of the first occurrence of an item; insert and remove,
which generally insert and remove some element from some position in the list; and findKth,
which returns the element in some position (specified as an argument). If the list is 34, 12, 52,
16, 12, then find(52) might return 2; insert(x,2) might make the list into 34, 12, x, 52, 16, 12
(if we insert into the position given); and remove(52) might turn that list into 34, 12, x, 16,
12. Of course, the interpretation of what is appropriate for a function is entirely up to the
programmer, as is the handling of special cases (for example, what does find(1) return
above?). We could also add operations such as next and previous, which would take a
position as argument and return the position of the successor and predecessor, respectively.
3.3Aarray-based implementation
All these instructions can be implemented just by using an array. Although arrays are created
with a fixed capacity, the vector class, which internally stores an array, allows the array to
grow by doubling its capacity when needed. This solves the most serious problem with using
an arraynamely, that historically, to use an array, an estimate of the maximum size of the
list was required. This estimate is no longer needed. An array implementation allows printList
to be carried out in linear time, and the findKth operation takes constant time, which is as
good as can be expected. However, insertion and deletion are potentially expensive,
depending on where the insertions and deletions occur. In the worst case, inserting into
position 0 (in other words, at the front of the list) requires pushing the entire array down one
spot to make room, and deleting the first element requires shifting all the elements in the list
up one spot, so the worst case for these operations is O(N). On average, half of the list needs
to be moved for either operation, so linear time is still required. On the other hand, if all the

operations occur at the high end of the list, then no elements need to be shifted, and then
adding and deleting take O(1) time.
3.4 Linked list implementation
3.5 Singly linked lists
3.6 Polynomial Manipulation
3.7 Stack ADT
A stack is a list with the restriction that insertions and deletions can be performed in only one
position, namely, the end of the list, called the top.
The fundamental operations on a stack are push, which is equivalent to an insert, and pop,
which deletes the most recently inserted element. The most recently inserted element can be
examined prior to performing a pop by use of the top routine. A pop or top on an empty stack
is generally considered an error in the stack ADT. On the other hand, running out of space
when performing a push is an implementation limit but not an ADT error. Stacks are
sometimes known as LIFO (last in, first out) lists. The model depicted in Figure 3.23 signifies
only that pushes are input operations and pops and tops are output. The usual operations to
make empty stacks and test for emptiness are part of the repertoire, but essentially all that you
can do to a stack is push and pop. Figure shows an abstract stack after several operations. The
general model is that there is some element that is at the top of the stack, and it is the only
element that is visible.

3.8 Queue ADT


As with stacks, any list implementation is legal for queues. Like stacks, both the linked list
and array implementations give fast O(1) running times for every operation. The linked
list implementation is straightforward and left as an exercise. We will now discuss an array
implementation of queues.
For each queue data structure, we keep an array, theArray, and the positions front and
back, which represent the ends of the queue.We also keep track of the number of elements
that are actually in the queue, currentSize. The following table shows a queue in some
intermediate state.

3.9 Evaluating arithmetic expressions

UNIT-4

NON-LINEAR DATA STRUCTURES

4.1 Trees
A tree can be defined in several ways. One natural way to define a tree is recursively. A tree is
a collection of nodes. The collection can be empty; otherwise, a tree consists of a
distinguished node, r, called the root, and zero or more nonempty (sub)trees T1, T2, ... , Tk,
each of whose roots are connected by a directed edge from r. The root of each subtree is said
to be a child of r, and r is the parent of each subtree root. Figure 4.1 shows a typical tree using
the recursive definition. From the recursive definition, we find that a tree is a collection of N
nodes, one of which is the root, and N 1 edges. That there are N 1 edges follows from the
fact that each edge connects some node to its parent, and every node except the root has one
parent.
Generic tree

A Tree

4.2 Binary Trees


A binary tree is a tree in which no node can have more than two children. Figure shows that a
binary tree consists of a root and two subtrees, TL and TR, both of which could possibly be
empty.
Generic binary tree

4.3 Binary tree representation and traversals


Representation of a binary tree in memory:
The structure of each node a binary tree contains the data field, a pointer to the left child and
a pointer to the right child. The figure shows this structure.
structures can be created that are much more complicated than linked lists. In this section,
we'll look at one of the most basic and useful structures of this type:binary trees. Each of the

objects in a binary tree contains two pointers, typically called left and right. In addition to
these pointers, of course, the nodes can contain other types of data. For example, a binary tree
of integers could be made up of objects of the following type:
struct TreeNode {
int item;
// The data in this node.
TreeNode *left; // Pointer to the left subtree.
TreeNode *right; // Pointer to the right subtree.
}

4.4 Application of trees:


Trees are one of the most useful data structures in Computer Science. Some of the common
applications of trees are:

The library database in a library, a student database in a school or college, an


employee database in a company, a patient database in a hospital or any database for
that matter would be implemented using trees.
The file system in your computer i.e folders and all files, would be stored as a tree.
When you search for a word in a file or misspell a word and you get a list of possible
correct words, you are using a tree. Because, the file would be indexed as a tree and
for the same reason you get answers instantly.
When you watch a youtube video or surf anything on the internet, the data which
would be present in a computer somewhere in the world would travel to your
computer passing through many intermediate computers called routers. The routers
heavily use trees for routing.
Most probably, Google maps uses trees (apart from using graphs) for all the locations
in it.

4.5 Set representation and Union-Find operations

Equivalence Relations: A relation R is defined on a set S if for every pair of elements (a, b), a,
b S, aRb is either true or false. If aRb is true, then we say that a is related to b. An
equivalence relation is a relation R that satisfies three properties:
1. (Reflexive) aRa, for all a S.
2. (Symmetric) aRb if and only if bRa.
3. (Transitive) aRb and bRc implies that aRc.
The equivalence class of an element a S is the subset of S that contains all the elements
that are related to a. Notice that the equivalence classes form a partition of S: Every member
of S appears in exactly one equivalence class. To decide if a b, we need only to check
whether a and b are in the same equivalence class. This provides our strategy to solve the
equivalence problem. The input is initially a collection of N sets, each with one element. This
initial representation is that all relations (except reflexive relations) are false. Each set has a
different element, so that Si Sj = ; this makes the sets disjoint.
There are two permissible operations. The first is find, which returns the name of the set (that
is, the equivalence class) containing a given element. The second operation adds relations. If
we want to add the relation a b, then we first see if a and b are already related. This is done
by performing finds on both a and b and checking whether they are in the same equivalence
class. If they are not, then we apply union. 1 This operation merges the two equivalence
classes containing a and b into a new equivalence class. From a set point of view, the result of
is to create a new set Sk = Si Sj, destroying the originals and preserving the disjointness
of all the sets. The algorithm to do this is frequently known as the disjoint set union/find
algorithm for this reason.

Eight elements, initially in different sets

After union(4,5)

After union(6,7)

After union(4,6)

Implicit representation of previous tree


4.6 Graph and its representations
A graph G = (V, E) consists of a set of vertices, V, and a set of edges, E. Each edge is a pair
(v, w), where v, w V. Edges are sometimes referred to as arcs. If the pair is ordered, then
the graph is directed. Directed graphs are sometimes referred to as digraphs. Vertex w is
adjacent to v if and only if (v, w) E. In an undirected graph with edge (v, w), and hence (w,
v), w is adjacent to v and v is adjacent to w. Sometimes an edge has a third component,
known as either a weight or a cost
A path in a graph is a sequence of vertices w1, w2, w3, ... , wN such that (wi, wi+1) E for
1 i < N. The length of such a path is the number of edges on the path, which is equal to N
1. We allow a path from a vertex to itself; if this path contains no edges, then the path length
is 0. This is a convenient way to define an otherwise special case. If the graph contains an
edge (v, v) from a vertex to itself, then the path v, v is sometimes referred to as a loop. The
graphs we will consider will generally be loopless. A simple path is a path such that all
vertices are distinct, except that the first and last could be the same.
Following is an example undirected graph with 5 vertices.

Following two are the most commonly used representations of graph.


1. Adjacency Matrix
2. Adjacency List
There are other representations also like, Incidence Matrix and Incidence List. The choice of
the graph representation is situation specific. It totally depends on the type of operations to be
performed and ease of use.

Adjacency Matrix:
Adjacency Matrix is a 2D array of size V x V where V is the number of vertices in a graph.
Let the 2D array be adj[][], a slot adj[i][j] = 1 indicates that there is an edge from vertex i to
vertex j. Adjacency matrix for undirected graph is always symmetric. Adjacency Matrix is
also used to represent weighted graphs. If adj[i][j] = w, then there is an edge from vertex i to
vertex j with weight w.
The adjacency matrix for the above example graph is:

Adjacency Matrix Representation of the above graph


Pros: Representation is easier to implement and follow. Removing an edge takes O(1) time.
Queries like whether there is an edge from vertex u to vertex v are efficient and can be
done O(1).
Cons: Consumes more space O(V^2). Even if the graph is sparse(contains less number of
edges), it consumes the same space. Adding a vertex is O(V^2) time.

Adjacency List:
An array of linked lists is used. Size of the array is equal to number of vertices. Let the array
be array[]. An entry array[i] represents the linked list of vertices adjacent to the ith vertex.
This representation can also be used to represent a weighted graph. The weights of edges can
be stored in nodes of linked lists. Following is adjacency list representation of the above
graph.

Adjacency List Representation of the above Graph


4.7 Graph Traversals

Graph traversal is the problem of visiting all the nodes in a graph in a particular manner,
updating and/or checking their values along the way. Tree traversal is a special case of graph
traversal.
Graph traversal are mainly classified as Breadth-first search and Depth-first search.
4.8 Representation of Graphs
Following two are the most
1. Adjacency Matrix
2. Adjacency List

commonly

used

representations

of

graph.

4.9 Breadth-first search


The aim of DFS algorithm is to traverse the graph in such a way that it tries to go far from the
root node. Stack is used in the implementation of the depth first search. Lets see how depth
first search works with respect to the following graph:

As stated before, in DFS, nodes are visited by going through the depth of the tree from the
starting node. If we do the depth first traversal of the above graph and print the visited node,
it will be A B E F C D. DFS visits the root node and then its children nodes until it reaches
the end node, i.e. E and F nodes, then moves up to the parent nodes.
Algorithmic Steps
Step 1: Push the root node in the Stack.
Step 2: Loop until stack is empty.
Step 3: Peek the node of the stack.
Step 4: If the node has unvisited child nodes, get the unvisited child node, mark it as
traversed and push it on stack.
5.
Step 5: If the node does not have any unvisited child nodes, pop the node from the
stack.
Based upon the above steps, the following Java code shows the implementation of the DFS
algorithm:
1.
2.
3.
4.

public void dfs()


{
//DFS uses Stack data structure
Stack s=new Stack();

s.push(this.rootNode);
rootNode.visited=true;
printNode(rootNode);
while(!s.isEmpty())
{
Node n=(Node)s.peek();
Node child=getUnvisitedChildNode(n);
if(child!=null)
{
child.visited=true;
printNode(child);
s.push(child);
}
else
{
s.pop();
}
}
//Clear visited property of nodes

clearNodes();
}

4.10 Depth-first search


As an example of depth-first search, suppose in the graph of Figure we start at vertex A. Then
we mark A as visited and call dfs(B) recursively. dfs(B) marks B as visited and calls dfs(C)
recursively. dfs(C) marks C as visited and calls dfs(D) recursively. dfs(D) sees both A and B,
but both of these are marked, so no recursive calls are made. dfs(D) also sees that C is
adjacent but marked, so no recursive call is made there, and dfs(D) returns back to dfs(C).
dfs(C) sees B adjacent, ignores it, finds a previously unseen vertex E adjacent, and thus calls
dfs(E). dfs(E) marks E, ignores A and C, and returns to dfs(C). dfs(C) returns to dfs(B).
dfs(B) ignores both A and D and returns. dfs(A) ignores both D and E and returns.
void Graph::dfs( Vertex v )
{
v.visited = true;
for each Vertex w adjacent to v
if( !w.visited )
dfs( w );
}

An undirected graph

Depth-first search of previous graph


4.11 Connected components.
a connected component (or just component) of an undirected graph is a subgraph in which
any two vertices are connected to each other by paths, and which is connected to no
additional vertices in the supergraph. For example, the graph shown in the illustration on the
right has three connected components. A vertex with no incident edges is itself a connected
component. A graph that is itself connected has exactly one connected component, consisting
of the whole graph.

A connected undirected graph is biconnected if there are no vertices whose removal


disconnects the rest of the graph. The graph in Figure is biconnected. If the nodes are
computers and the edges are links, then if any computer goes down, network mail is
unaffected, except, of course, at the down computer. Similarly, if a mass transit system is
biconnected, users always have an alternate route should some terminal be disrupted. If a
graph is not biconnected, the vertices whose removal would disconnect the graph are known
as articulation points.

UNIT-5

SORTING AND SEARCHING

5.1 Sorting algorithms:


5.2 Insertion sort

5.3 Quick sort

5.4 Merge sort

5.5 Searching:
5.6 Linear search
A linear search is the most basic of search algorithm you can have. A linear search
sequentially moves through your collection (or data structure) looking for a matching value.
Implementation
function findIndex(values, target) {
for(var i = 0; i < values.length; ++i){
if (values[i] == target) { return i; }
}
return -1;
}
findIndex([7, 3, 6, 1, 0], 6)
5.7 Binary Search
Binary search relies on a divide and conquer strategy to find a value within an already-sorted
collection. The algorithm is deceptively simple. Pretend I was thinking of a number between
1 and 100. Every guess you take, I'll say higher or lower. The most efficient way to discover
my number is to first guess 50. Higher. 75. Lower. 62. Higher 68. Yes!
Implementation
function findIndex(values, target) {
return binarySearch(values, target, 0, values.length - 1);
};
function binarySearch(values, target, start, end) {
if (start > end) { return -1; } //does not exist
var middle = Math.floor((start + end) / 2);
var value = values[middle];
if (value > target) { return binarySearch(values, target, start, middle-1); }
if (value < target) { return binarySearch(values, target, middle+1, end); }
return middle; //found!
}

findIndex([1, 4, 6, 7, 12, 13, 15, 18, 19, 20, 22, 24], 20);
5.8 Binary Search
Given an integer X and integers A0, A1, ... , AN1, which are presorted and
already in memory, find i such that Ai = X, or return i = 1 if X is not in the
input.
The obvious solution consists of scanning through the list from left to right and
runs in linear time. However, this algorithm does not take advantage of the fact
that the list is sorted and is thus not likely to be best. A better strategy is to
check if X is the middle element. If so, the answer is at hand. If X is smaller than
the middle element, we can apply the same strategy to the sorted subarray to
the left of the middle element; likewise, if X is larger than the middle element,
we look to the right half. (There is also the case of when to stop.) Figure 2.9
shows the code for binary search (the answer is mid). As usual, the code reflects
C++s convention that arrays begin with index 0.

Clearly, all the work done inside the loop takes O(1) per iteration, so the analysis
requires determining the number of times around the loop. The loop starts with
high - low = N 1 and finishes with high - low 1. Every time through the
loop, the value high - low must be at least halved from its previous value; thus,
the number of times around the loop is at most log(N 1) + 2. (As an example,
if high - low = 128, then the maximum values of high - low after each iteration
are 64, 32, 16, 8, 4, 2, 1, 0, 1.) Thus, the running time is O(logN).

Das könnte Ihnen auch gefallen