Beruflich Dokumente
Kultur Dokumente
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.
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.
OVERVIEW OF C++
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
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.
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;
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.
1.6.
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
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 );
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;
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;
return 0;
}
If we compile and run above code, this would produce the following result:
Value of pvalue : 29495
1.9.
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>
// Declare box1
// Declare box2
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
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>
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.
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;
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)
{
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
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.
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
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.
};
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.
}
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:
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():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
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.
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.
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;
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
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.
UNIT-4
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
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.
}
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.
After union(4,5)
After union(6,7)
After union(4,6)
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 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.
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.
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.
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();
}
An undirected graph
UNIT-5
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).