C Material
C Material
com
INDEX
CHAPTER - 1
INTRODUCTION TO OBJECT ORIENTED PROGRAMMING
There are different programming paradigms (model of programming) like Imperative, Functional, and
Object-Oriented.
Imperative Paradigm: that describes computation in terms of statements that change a
program state ; a program is a sequence of instructions.
o Ex: FORTRAN, Algol, Pascal, C
Functional Paradigm: The natural abstraction here is the function; a program is a collection of
functions.
o Ex: C
Object-Oriented: programs are made up of collection of individual units called objects that
have a distinct purpose.
o Ex: Simula67, Smalltalk, C++, Java, Eiffel, Java, C#
We are already aware of the structured programming language. Before we get into the Object oriented
programming language, let us list out the advantages and limitations of structured programming
languages.
These programming advantages make working with a structured programming language like C a very
popular choice. However, there are certain inherent difficulties associated with structured programming
in general and C in particular. Some of the most visible of these are given below
These “limitations” make developing large applications very difficult in C. To overcome these specific
issues i.e. data hiding and functions not being related to data, the concept of objects were developed.
In object Model languages, data was considered as a critical element in program development. In
these types of languages, data and the code acting upon the data is combined into a single unit
termed as objects. Or in other words, object is a self-contained entity that consists of both data and
operations to manipulate that data. An entity can be considered as an object only if it has
An identity
A state
A behavior
The identity is the feature of an object, which distinguishes the object uniquely.
Objects are identified and distinguished from one another through their identities.
At a given point of time during execution, two objects may have the same state and the same
behavior, but they are distinguishable through their identities.
Object World
Object based programming languages can be further categorized into two types:
Languages that have only Encapsulation and Polymorphism
The concept of Object Based programming language is just of academic interest to us. At this point of
time, we are more interested in the concept of object oriented programming.
The ultimate aim of an object oriented approach is to design / model a real world entity.
An application program in OOP’s will be a collection of interacting objects. We have already seen that
object is a real world entity. So when it comes to designing of real world complex systems, first step is
to trace out all the real world entities in that system which can be considered as objects.
For e.g.: consider a college management system. It can be broken down into the following real world
entities.
Courses Departmen
ts
Computer Projector
Each and every real world entity will be surrounded by large number of details. So the next step is to
trace out those details which are very relevant to the current application. This process is known as
abstraction.
Abstraction
For e.g.: let’s consider the real world entity student. As a person in the real world, he will be
surrounded by so many details as shown below.
Fees paid
Medical Details
Fees paid
From the above example we can understand that abstraction gives programmers, an ability to cope
with the complexity issues without getting overwhelmed by the details.
Summarizing:
Abstraction allows the user to remove the extras and concentrate only on the essential
details of an object that distinguish it from all other kinds of objects.
.
Once the real world entities are traced out, the next step is to technically implement each and every
real world entities. This technical implementation of abstraction is known as encapsulation.
Encapsulation
As far as an object oriented programmer is concerned, his entire world revolves around objects. And
object is nothing but a collection of data and functions acting upon the data. In this context, interface
refers to function names and implementation refers to data and the definition of the functions. So by
separating the interface and implementation, object oriented programmer achieves one of the major
goals, i.e. data hiding or information hiding.
Using abstraction and encapsulation, the entities traced out in the real world system is technically
implemented. All these entities in the real world are related to each other. For e.g.: in the college
management system, a student is related to college at the same time student is even related to
course. In the same manner course is related to department at the same time course is related to
instructor also.
INTRODUCTION TO C++
C++ is a superset of C, and offers backward compatibility with the language. This is important because
it offers the user the advantage of
Because of its rich ‘C’ legacy, it has retained some of its drawbacks including manual memory
management, unchecked array bounds and pointers.
So is C++ better than C? The primary reason for a C programmer to switch to C++ is its Object
oriented programming style.
C programmers who do not prefer to switch to object-oriented programming still benefit from a
migration to C++ because of its tighter type-safety, strongly typed pointers, improved memory
management, and many other features that make a programmer's life easier.
The up-gradations in C++ also included a new data type – the bool type. A bool variable can be used
like any other data type. It uses one byte of memory and can have only two values defined by two new
C++ keywords: true and false. The values of these keywords are
true = 1
false = 0
C++ Standards
Unlike many other programming languages, C++ does not have versions. Rather, it has an
International ANSI/ISO Standard, ratified in 1998, that defines the core language, its standard libraries,
and implementation requirements. The C++ Standard is treated as a skeleton on which vendors might
add their own platform-specific extensions, mostly by means of code libraries. However, it's possible to
develop large-scale applications using pure standard C++, thereby ensuring code portability and
easier maintenance.
However unlike C, C++ is an unfinished language. This means that a lot of additions are continually
made to the language, thus making the language better and easier for programming. Since compiler
designed always lag behind due to the additions of new features in the language, C++ has become a
highly compiler dependent programming language. Current C++ standard is ISO/IEC 9899:1999 also
known as the C99 standards.
Lets first try to concentrate on the procedural side of programming in C++ and let’s see what are the
additional features added along with C++ which makes even the procedural approach of C++ better
than C.
Clearly, one can see a lot of new terms in this program. Let us take them up one by one. Let’s start
from the first line.
As you might have guessed, the first line indicates comments. Since C++ have backward compatibility
with C language, it is perfectly legal to use C specific comment syntax, ie) /* * /. But sticking on to C++
programming syntax, // is used for single line comments and / * */ is used for multi-line comments.
Second line is the pre-processor directive which instructs the pre-processor to include the contents of
the header file mentioned in angular braces. “iostream.h” is a header file which has the functionality
similar to “stdio.h”. ie) it is going to help in input /output functionalities. Just like C, in C++ also, the
starting point of any program is main.
The next line indicates variable declaration. Only difference here is, in C++ there is flexible variable
declaration which is indicated by the declaration of distance just before its usage.
Statement starting with cout and cin behaves like printf and scanf functions in C. To understand the
working of these statements, a thorough knowledge of generalization and operator overlading is
needed. For the time-being, lets briefly see how it is working.
“cout” and “cin” are pre-defined objects of standard output and input stream respectively. First let’s
start with the term stream. Stream just refers to flow of data. Data can flow either towards the monitor
ie) standard output device or data can flow from the keyboard ie) standard input device. If the data is
flowing towards the monitor,stream associated is output stream and if data is flowing from the
keyboard, stream associated is input stream.
“cout” is an object associated with output stream. Ie) cout helps the program to communicate with the
monitor. In the similar manner, cin is an object associated with input stream. Ie) cin helps the program
to interact with the keyboard.
If you notice, in the cout and cin statements, in between the data and the predefined objects, two
operators << and >> are used. These operators were used as shift operators in C language. But in this
context << is termed as put to / insertion operator and >> is termed as get from / extraction operator.
Put to operator puts / inserts the contents on its right to the object cout which will push the contents
directly to the monitor.
Monitor variable
(standard output) cout <<
Get from operator gets / extracts the contents of the object cin and puts it into the variable on the right.
Keyboard
(Standard input) cin >> variable
The only other thing unfamiliar in this program is endl. “endl” comes under a classification called
manipulators. Manipulators are functions specifically designed to be used in conjunction with the
insertion (<<) and extraction (>>) operators on stream objects.
Manipulator endl works exactly like the newline character, ‘\n’ in the C language.
Try to compile this program and notice the warning arising. The warning in this program is due to
the .h extension in the preprocessor directive. In C++, .h extensions are not used along with header
file. Instead another new concept known as namespaces is introduced. First let’s see what namespace
are. Then we will see how it is related to header files.
Namespaces
A namespace is a mechanism for expressing logical grouping. That is, if some Names logically belong
together according to some criteria, they can be put in a common namespace to express that
fact.Names in C++ can refer to variable names, function names, structure names or enumerator
names.
When a program uses different global names written by different programmers, there is a possibility
that two programmers may use the same name for two different things. Duplication of these names will
result in name-clashes. A way to deal with this problem is namespaces.
C++ adds the ability to create named declarative regions in the global section one whose main
purpose is to provide an area in which we can declare logically related names. These named
declarative regions are termed as namespaces. The names in one name space don’t conflict with the
same names declared in other namespaces and there are mechanisms for letting other parts of a
program use items declared in a namespace.
Namespaces can be located at the global level or inside other namespaces but they cannot be placed
in a block. Thus the names declared in a namespace will have external linkage. The syntax for
namespace is :
namespace namespace_name
{
//Declared names
}
}
namespace ns2
{
float a=10.1;
The names in the namespace can be accessed outside by using either of the following two things.
Scope resolution operator ( : : )
Using keyword
writing the main for the above namespace using scope resolution operator :
int main()
{
Cout<<ns1::a<<endl;
Cout<<ns2::a<<endl;
}
Using Keyword
Having to qualify names every time they are used is not a good programming practice. So C++
provides two mechanisms, “using declaration” and “using directives” to simplify using namespace
names. The using declaration lets you make particular names available while using directives makes
the entire names in namespace available.
int main()
{
Cout<<using ns1 :: a<<endl;
Cout<<using ns2::a<<endl;
}
int main()
{
using namespace ns1 ;
cout<<a<<endl;
}
Note : Find the difference between namespace declaration and namespace directive.
In C++ all the standard names like cout, cin are put in a single namespace unit called as standard
namespace.We use it along with our header file iostream. All existing c libraries are prefixed with letter
c and used in c++.For e.g stdio.h will be used as cstdio, math.h will be used as cmath.
CHAPTER -2
Default arguments
int main(void)
{
float Hours, Money;
cout << “Enter the number of hours and money per hour” << endl;
cin >> Hours >> Money;
cout << “ wage of the person who worked for ” << Hours << “hours is = ”;
cout << Calculate_Wage(Hours, Money) << endl;
return 0;
}
float Calculate_Wage(float HoursWorked, float MoneyPerHour)
In{ C language, if a function is defined with N arguments, we have to invoke that function by
return (HoursWorked * MoneyPerHour);
}
In a C language,if a function is defined with N arguments,we have to invoke that function by
passing the exact number of argument. For e.g. : in the above function Calculate_Wage(float, float)
which is defined with two arguments, we have to invoke this function by passing two arguments only.
But in C++, using the concept of default arguments, we can invoke a function which is taking N
arguments either by passing N number of arguments or by passing a lesser number of arguments.
The supplying of these arguments is not done randomly. There are certain rules that have to be
followed for the same.
They are
When a function is called with missing arguments, it is always assumed that the trailing
arguments are missing. So the default arguments should be supplied by the programmer
from trailing edge to leading edge. i.e. RHS to LHS.
The default value given for an argument can be a global constant, a global variable, or a
function call (return value).
#include <iostream>
using namespace std;
float Calculate_Wage(float, float = 50);
int main(void)
{
float Hours, Money;
cout << “Enter the number of hours and money per hour ” << endl;
cin >> Hours>>Money;
cout << “ wage of the person who worked for ” << Hours << “hours is = ”;
cout << Calculate_Wage(Hours, Money) << endl;
cout << “Enter the number of hours ” << endl;
cin >> Hours;
cout << “ wage of the person who worked for ” << Hours << “hours is = ”;
cout << Calculate_Wage(Hours) << endl;
return 0;
}
OUTPUT
When the function was called with both arguments, the user supplied arguments were used by the
function. In the second case, when only one argument was supplied, the function uses the user
supplied value for the 1st argument and the default value for the 2nd argument.
Inline functions
From C language, we have seen the advantages of using functions in our programs. But functions do
have its own disadvantages and the most important among them is function call overheads. To avoid
the disadvantages of functions, macros were introduced.
Macros are a preprocessor directive which performs in-place code expansion. Macros are very
powerful programming tools that are advantageous for the following reasons
In C++, an attempt was made to remove these disadvantages and retain the advantages. Thus inline
functions were created. The advantages of inline functions are
Syntax:
inline return_type function_name (argument declaration)
{
function body
}
int main(void)
{
int x=5;
cout << square(x+2) << endl;
return 0;
}
OUTPUT
49
© Cranes Varsity V2.1-2020-21 14/ 126
C++ www.cranesvarsity.com
All the functions with the keyword inline attached to it won’t be considered as inline functions. Some
situations where inline request will be ignored by some compliers are
A reference is an alias or alternative name for variable. References are internally treated as constant
pointers implying that references, like all constants have to be initialized at the point of declaration.
Syntax:
Note that the “&” operator appears on the left hand side and not on the right hand side of the
assignment operator. After declaration and initialization is done, a reference variable is used exactly
like a normal variable would be.
Example:
Here ref_var is a reference to a integer variable and is initialized to integer variable var.
P & var
3 var
ref_var &var
Any changes to the reference (ref_var) variable will be reflected to the referred variable (var) also. In
C++ using references is preferred over using pointers because
Fewer chances of memory corruption since they are internally treated like constant pointers
Pass by value:
The pass by value is same as in C i.e. a copy of the variable is passed to the called function. Any
change made to the copy of the variable in the called function is not reflected in the calling function
Call by reference:
C++ references are used for implementing call by references. This method is simpler compared to
using pass by address using pointers.
int main()
{
int Num1, Num2;
Get_Values(Num1, Num2);
cout << “Values before swapping” << Num1 << ‘\t’ << Num2 << endl;
Swap_Values(Num1, Num2);
Show_Values(Num1, Num2);
}
void Get_Values(int &Val1, int &Val2)
{
cout << “Enter two values” << endl;
cin >> Val1 >> Val2;
}
Temp = Val1;
Val1 = Val2;
Val2 = Temp;
}
void Show_Values(int &Val1, int &Val2)
{
cout << “Values after swapping” << Val1 << ‘\t’ << Val2 << endl;
}
OUTPUT
© Values before
swapping 10
Cranes Varsity 20 V2.1-2020-21 16/ 126
Values after swapping 20 10
C++ www.cranesvarsity.com
Function Overloading
Function overloading provides the flexibility of grouping together functions performing similar jobs
under a single name. The compiler binds the function call to the function definition based on the
function name and argument list. The arguments of the overloaded function should differ in terms of
Type of arguments
Order of arguments
Number of arguments
Constantness of Pointer and References
Volatileness of Pointer and References
Note:
One important point here is that the return type of a function in no manner helps in performing function
overloading
#define PI 3.14
void Area(int);
void Area(float);
void Area(int, int);
int main()
{
int Side = 10;
float Rad = 12.2f;
int Length = 15, Breadth = 4;
Area(Side);
Area(Rad);
Area(Length, Breadth);
}
OUTPUT
Note:
How does C++ keep track of which overloaded function is which? This is achieved using “Name
mangling” or “Name decoration”. During Name Mangling, C++ compiler internally converts the function
name into a distinct name based on function name and the formal parameter types specified in the
function prototype.
**********
CHAPTER - 3
OBJECT ORIENTED APPROACH IN C++
We have already seen what abstraction is with an example. Now let’s see how encapsulation is done.
Encapsulation is achieved using classes and objects in C++.
Introduction to classes
A class is a single unit that is a representation of the abstracted information. It is a model, like the blue
print of a house. The blue print of the house gives all the information for building a house. Using the
same blue print, more than one house can be constructed. Same way the class is just an information
provider from which objects are created. The same class can be reused to create many objects. The
process of creation of objects is called instantiation.
A class in C++ looks very similar to the structures used in C. The difference is in the use. Before we
get into the differences between a class and structure, let us look at the differences between a
structure in C and C++. A structure in C is just a collection of similar and / or dissimilar data types. A
structure in C++ contains data and the functions that work on the data. Moreover in C++, in-order to
achieve data security, access specifiers are used along with structures.
struct structure_name
{
Access specifiers:
Data members & Function Members
};
Few more differences between C and C++ structures are shown below in the table.
The difference between a C++ structure and a C++ class is by default the data members and function
members of a structure are public where as in classes it is private.
Syntax of a C++ class:
Keyword
Keyword
Using this basic concept of classes, lets try to do the encapsulation of the previously done abstraction
Student. We have already traced out the attributes of student. Now let’s try to include few behaviors
and let’s put it in a diagrammatic representation.
Fees = f;
Total_Marks = 0;
}
void Set_Marks(float total)
{
Total_Marks = total;
}
float Calculate_Percentage(int no_subjects)
{
return (Total_Marks / no_subjects);
}
};
int main()
{
char name[20];
int id;
char course[20];
float fees;
Student Anu;
cout << "Enter the name, id, course selected and fees" << endl;
cin >> name >> id >> course >> fees;
float marks;
int no_subjects;
cout << "Enter total marks and total no of subjects" << endl;
cin >> marks >> no_subjects;
Anu.Set_Marks(marks);
cout << Anu.Calculate_Percentage(no_subjects) << endl;
return 0;
}
The member functions defined in the class are by default considered as inline functions. The functions
can be just declared inside the class and defined outside. While defining these functions outside the
class body, we have to link the definition of the function to its declaration in the class. This is done with
the help of a scope resolution operator.
Syntax:
return_type class_name :: function_name ( argument declaration )
Function body
Programming tips
Visualize the application and decide on what is an important abstraction for the
application. For example, for creation of an employee database the abstraction is the
employee, which can be made a class. The data members of this class can be employee
id, designation, address and the functions could be setting and getting of these values
Follow the practice that a class member function should never have cin’s and cout’s.
Convention says that the class name should start with an upper case letter
A better programming practice is to follow the layered approach.i.e header file, application layer and
library layer. Header file should contain all structure / class declarations, global function declarations,
macros and other symbolic literals. Library layer should contain the definitions of all the functions
declared in the header file and application layer is the user interacting part.
Modifying the above program using layered approach.
#include <iostream>
using namespace std;
class Student
{
private:
char Name[20];
int College_Id;
char Course[20];
float Total_Marks;
float Fees;
public:
void Enroll_Student(char *, int, char *, float);
void Set_Marks(float);
float Calculate_Percentage(int);
};
//Student_lib.cpp – Library layer
#include "student.h"
#include "student.h"
int main()
{
char name[20];
int id;
char course[20];
float fees;
Student Anu;
cout << "Enter the name, id, course selected and fees" << endl;
cin >> name >> id >> course >> fees;
Anu.Enroll_Student(name, id, course, fees);
float marks;
int no_subjects;
cout << "Enter total marks and total no of subjects" << endl;
cin >> marks >> no_subjects;
Anu.Set_Marks(marks);
cout << Anu.Calculate_Percentage(no_subjects) << endl;
return 0;
}
Memory of an object
The memory occupied by an object is equal to the sum of its data members. Functions are stored in
the text segment (equivalent machine instructions). It is important that we distinguish between the way
the objects look at functions and data members of a class
In other words, data is per object whereas functions are shared. For the above class student, if three
objects are created, lets say Anu, Vinu and Sonu, the memory organization will be :
Enroll_Student ()
Set_Marks()
Calculate_Percentage()
We know by now that each object gets its own set of data members, but all objects of a class share a
single set of member functions i.e. a single copy of each functions exists The question then is, if only
one copy of each member function exists and is used by multiple objects, how does the function
understands which object to be acted upon ?
For this, the compiler supplies an implicit pointer along with the functions, named as “this pointer”
which always points to the object that is invoking the function.
“This” pointer is defined as an implicit pointer supplied by the compiler, which always points
to the object, which is invoking the member function of a class.
class Student
{
private:
char Name[20];
int College_Id;
char Course[20];
float Total_Marks;
float Fees;
public:
void Enroll_Student(char *, int, char *, float);
Student& Set_Marks(float);
float Calculate_Percentage(int);
};
//student_lib.cpp – Library layer
#include "student.h"
#include "student.h"
int main()
{
char name[20];
int id;
char course[20];
float fees;
Student Anu;
cout << "Enter the name, id, course selected and fees" << endl;
cin >> name >> id >> course >> fees;
float marks;
int no_subjects;
cout << "Enter total marks and total no of subjects" << endl;
cin >> marks >> no_subjects;
cout<<Anu.Set_Marks(marks).Calculate_Percentage(no_subjects)<<endl;
return 0;
}
Note : Find the changes made in the program and analyze how it is working.
**********
CHAPTER - 4
CLASS INTERNALS – CONSTRUCTORS AND DESTRUCTORS
Constructors
Consider the abstraction and encapsulation of the course which is part of our school management
system.
Course
- Course_Name: String
- Course_Id: Integer
- Duration: Integer
- No_Modules: Integer
- Course_Fees: float
#include <iostream>
using namespace std;
class Course
{
private:
char Course_Name[20];
int Course_Id;
int Duration;
int No_Modules;
float Course_Fees;
public:
void Set_Details(char*, int, int, int, float);
void Include_Modules(int);
void Change_Fees(float);
char* Ret_Course();
int Ret_Duration();
int Ret_Modules();
float Ret_Fees();
};
#include "course.h"
void Course :: Set_Details(char *c, int i, int d, int n, float f)
{
strcpy(Course_Name, c);
Course_Id = i;
Duration = d;
No_Modules = n;
Course_Fees = f;
}
void Course :: Include_Modules(int m)
{
No_Modules += m;
}
void Course :: Change_Fees(float f)
{
Course_Fees = f;
}
char* Course :: Ret_Course()
{
return Course_Name;
}
int Course :: Ret_Duration()
{
return Duration;
}
int Course :: Ret_Modules()
{
return No_Modules;
}
float Course :: Ret_Fees()
{
return Course_Fees;
}
In the encapsulation of Course, a Set_Details() function is defined which should be explicitly called
each and every time after the creation of an object for these classes and these functions are
responsible for assigning valid values to the attributes of the objects. A failure to invoke these
functions will result in an undefined state of an object i.e., object’s attributes initialized to garbage.
C++ compiler provides a set of special member functions for a class which gets invoked automatically
at the time of instantiation. These special member functions are termed as constructors.Constructors
are special member function which takes care of initialization of an object’s data members and
performs resource allocation if needed. The resource can be heap memory, files or any system wide
resources. Certain features associated with a constructor are as below
A constructor is used to initialize an object when it is created instead of making a separate
call to a function.
A constructor shares its name with the class.
The call to constructor is implicit.
A constructor should be defined under the public access specification (try defining it under
the private access specification)
A constructor is invoked separately for each object created.
A constructor does not have return type.
A single class can have multiple constructors defined inside it. ie) we can write more than one
constructor functions which share the same name inside a class. But each and every constructor
should differ in terms of the arguments they take. The arguments should differ either in terms of
number of arguments, or in terms of type of arguments or in terms of order of arguments. And when
the arguments are pointers or references they can differ in their constantness or/and volatileness.
The above discussion directly implies that constructor functions are working based on function
overloading principles.
Points to remember:
When no constructor is present in a class the compiler provides you with a default
constructor. In addition to the default constructor, compiler also provides a copy
constructor as and when needed.
Once we declare any constructor other than the default constructor it is necessary to
define the implicit constructor (default or zero argument constructors) as well.
Lets try to modify our above encapsulation for course using default and parameterized constructors.
Encapsulation of Course with constructors
int main()
{
Course Electronics;
return 0;
}
In the above program, the data members of the class is getting initialized in the body of the
constructor. Another way of initialization using constructor is using initializer list.
Syntax:
class class_name
{
data_type data1;
data_type data2;
public:
class_name():data1(value1),data2(value2)
};
Summarizing
Constructors are special member functions of the class which gets invoked automatically at the
time of creation of objects and which takes up the responsibility of initialization of the objects.
Destructor
The complement of a constructor is a destructor. If not defined by the user, compiler wills supply one
destructor for each program implicitly. In many circumstances, an object will need to perform some
action or actions when it is destroyed.
Local objects are created when the control enters the block and are destroyed when the control goes
out of the block. Global objects are destroyed when the program terminates.
When an object is destroyed, its destructor function (compiler supplied / user defined) is automatically
called. In C++, it is the destructor function that handles the clean up operations. A destructor has the
same name as the constructor but is preceded by a ~.
Note:
Constructor does not allocate memory to an object
Destructor does not remove any object from memory
There is only one type of destructor. That which takes zero arguments
The destructor is called when the created object is coming out of the scope.
The actual purpose of destructors comes into picture only when the attribute of the class encounters
dynamic memory allocation.
To give a user more control over creating and deleting memory for any type of variable, both C and C+
+ provides facility for performing dynamic memory allocation. In C, dynamic memory allocation is
achieved with the help of the functions like malloc ( ) and dynamic memory deallocation using free ( ).
They have certain drawbacks as listed below
In C++, the dynamic memory allocation is still possible using malloc and free for backward
compatibility with C. C++ however prefers to make memory allocation simpler by making use of two
operators: new and delete.
The operator new is a substitute for malloc () and operator delete is a substitute for free(). The syntax
for new and delete to allocate and de-allocate memory for a single variable is:
Syntax:
Data_type pointer_var = new data_type;
delete pointer_var;
There are separate operators for allocating and deleting the memory for an array. The syntax of new
and delete for allocating and de-allocating an array of memory is
The new [] allots some number bytes that is held by the pointer. The pointer holds the address of the
first memory location
*pointer_variable
Here, using “delete”, will only de-allocate the 1 st block of memory. The result is a condition called
memory leak, where the memory is reserved but is in-accessible. In the above case, memory is still
held from the second block onwards but is inaccessible since the pointer has de-allocated the 1 st block
of memory.
To avoid such problems, we make use of a special version of delete [ ] which de-allocates the entire
block of memory.
The delete operator must be used only with a valid pointer previously allocated by using new. Using
any other type of pointer with delete is undefined and may cause serious problems. The new and
delete operators have several advantages over malloc and free:
No type casting needed – automatically returns a pointer to the type for which memory is
being allocated.
No sizeof operator required – internally calculates the size of the data type.
new and delete calls the constructor and destructor functions internally when memory is
allocated for object dynamically.
New can be used to initialize the allocated memory. The syntax for that is
Data_type pointer_var = new data_type (Value);
Note: With new, memory initialization is possible only for a single location. but not for array of
locations.
Difference between malloc and new
Simpler syntax.
Another difference between new and malloc is , when malloc fails it returns a NULL pointer
where as when new fails it returns a run time error or more specifically termed as exception.
Since we will not be dealing with handling the exceptions in our module, we will be using the
old version of new named as no throw version which will return us NULL on failure.
Student :: Student() {}
#include "student.h"
int main()
{
char name[20];
int id;
char course[20];
float fees;
cout << "Enter the name, id, course selected and fees" << endl;
cin >> name >> id >> course >> fees;
Student *Anu;
Anu = new (nothrow) Student(name, id, course, fees);
if (Anu == NULL)
exit(1);
int no_subjects;
float marks;
cout << "Enter total marks and total no of subjects" << endl;
cin >> marks >> no_subjects;
return 0;
}
**********
CHAPTER-5
CLASS INTERNALS – COPY CONSTRUCTORS
Since the compiler supplies a copy constructor by itself, what then is the need to define a new copy
constructor? In most cases, the compiler-supplied definition is sufficient. But when we have pointers
and dynamic memory allocation in the class, then it becomes necessary to define the copy
constructor. Consider the following code.
//Encapsulation of Student
//student.h – Header file
#include <iostream>
using namespace std;
class Student
{
private:
char *Name;
int College_Id;
char *Course;
float Total_Marks;
float Fees;
public:
Student();
Student(char *, int, char *, float, float = 0);
void Set_Marks(float);
float Calculate_Percentage(int);
char* Ret_Name();
int Ret_Id();
char* Ret_Course();
~Student();
};
void Student_Details(Student);
//student_lib.cpp – Library layer
#include "student.h"
#include "student.h"
Student :: Student() { }
}
char* Student :: Ret_Name()
{
return Name;
}
int Student :: Ret_Id()
{
return College_Id;
}
char* Student :: Ret_Course()
{
return Course;
}
Student :: ~Student()
{
delete [ ] Name;
delete [ ] Course;
}
void Student_Details(Student user)
{
cout << "Student name is " << user.Ret_Name() << endl;
cout << "Student id is " << user.Ret_Id() << endl;
cout << "Course Enrolled is " << user.Ret_Course() << endl;
}
If you notice the output of the program, when the details of Anu is displayed for the second time, the
name field and course field is not displayed or sometimes even a garbage value may be encountered.
The reason for this is, when the object Anu is passed to the function Student_Details() for the first
time, to copy Anu to the new object user, the copy constructor provided by the compiler gets invoked.
This compiler provided copy constructor do a bit by bit copy which results in a memory picture as
shown below
Stack Heap
Anu
Name 1000
College_Id 1
1000
Course 2000
Total_Marks 250
Fees 12000
User
Name 1000
College_Id 2
Course 2000
2000
Total_Marks 400
Fees 20000
When the local object user comes out of the scope, the destructor of the class Student will be
automatically called which de-allocates the memory allocated dynamically. i.e., locations 1000 and
2000 is de-allocated which indirectly implies that the pointers of Anu is now pointing to memory
locations which are already freed.
The bit by bit copy or member by member copy done by the copy constructor is technically termed as
shallow copy. To overcome this problem, a programmer can supply the definition of the copy
constructor in which, each and every pointer gets itw own memory location and then the contents of
one location are copied on to the other. This is known as deep copy.
//Encapsulation of Student with copy constructor
//student.h – Header file
#include <iostream>
using namespace std;
class Student
{
private:
char *Name;
int College_Id;
char *Course;
float Total_Marks;
float Fees;
public:
Student();
Student(char *, int, char *, float, float = 0);
Student(const Student&);
void Set_Marks(float);
float Calculate_Percentage(int);
char* Ret_Name();
int Ret_Id();
char* Ret_Course();
~Student();
};
void Student_Details(Student);
Student :: Student() {}
}
char* Student :: Ret_Name()
{
return Name;
}
int Student :: Ret_Id()
{
return College_Id;
}
char* Student :: Ret_Course()
{
return Course;
}
Student :: ~Student()
{
delete [ ] Name;
delete [ ] Course;
}
void Student_Details(Student user)
{
cout << "Student name is " << user.Ret_Name() << endl;
cout << "Student id is " << user.Ret_Id() << endl;
cout << "Course Enrolled is " << user.Ret_Course() << endl;
}
//student_app.cpp
#include “student.h”
int main()
{
Student Anu("Anu", 1, "ECE", 12000, 250);
Student Vinu("Vinu", 2, "CSE", 20000, 400);
cout << "Displaying the details of Anu" << endl;
Student_Details(Anu);
cout << "Displaying the details of vinu" << endl;
Student_Details(Vinu);
cout << "Once more displaying the details of Anu" << endl;
Student_Details(Anu);
return 0;
}
STACK HEAP
ANU
Name 1000
College_Id 1
Course 1500
1000
Total_Marks 250
Fees 12000
USER 1500
Name
User2000
College_Id 2
Course 2500
2000
Total_Marks 400
Fees 20000
2500
Now when user comes out of the scope, destructor de-allocates 2000 and 2500 which doesn’t affect
the object Anu. So the output will be modified as
Note: Try to find out why the argument of copy constructor is a constant reference object of the class.
Summarizing :
Good to know
class Student
{
private:
char Name[20];
public:
Student();
Student(char *);
};
Student :: Student()
{
strcpy(Name, "");
}
Student :: Student(char *n)
{
strcpy(Name, n);
}
//student_app.cpp – Application layer
#include "student.h"
int main()
{
Student Anu = "Anushri";
Student Vinu;
Vinu = "Vinukumar";
return 0;
}
In the main, we can see an invalid conversion from built in string to a user defined object. Even though
it is invalid, the compiler makes the above statements work by considering the one argument
constructor in the class as a conversion function or more technically conversion constructor which
converts built in string to user defined object.
In order to avoid these type of situations, when ever we have a single argument constructor in the
class, a keyword “explicit” is attached with it. So modifying the above program :
class Student
{
private:
char Name[20];
public:
Student();
explicit Student(char *);
};
Student :: Student()
{
strcpy(Name, "");
}
int main()
{
Student Anu = "Anushri";
Student Vinu;
Vinu = "Vinukumar";
return 0;
}
Now if you try to compile the program, compiler explicitly throws you an error telling that the above
conversion is an invalid conversion.
Static
The “static” keyword is used in the global scope or the local scope in C++ in a manner exactly similar
to C.
In the global scope, it is used along with both data and functions. In both the cases, it
helps enforce “internal linkage”. In fact usage of static key word is the only way that we
can enforce a small amount of data hiding in C.
In the local scope, a static variable is used to retain its value between function calls.
The following code snippet should refresh the use of static keyword.
void fun_1()
{
global_count++;
//Globa_count++;
}
void fun_2()
{
int local_count = 0; //retains values between function calls
global_count++;//updates the last value
local_count++;
cout << "global = " << global_count << endl;
cout << "local = " << local_count << endl;
}
int main()
{
cout << "Test for static" << endl;
fun_1();
fun_2();
}
Static in a class
Additionally, in C++ static can be used inside a class as “static data members” and “static member
functions”.
Static Data
Static data is used when different objects of the same class have to share information or update a
common parameter. Static data can be under any of the access specifiers
public: behaves exactly like global static variables. Can be accessed from all parts of the
program
private: is private to the class. Is used by different objects of the class to share
information.
protected: will be taken with inheritance
Static data is a shared data. Therefore, there is only one copy of the static data member of a
class. This is irrespective of the access specification under which it is defined. Hence static
data members are declared in the class but defined outside the class using class name and the
scope resolution operator.
Syntax:
class class_name
{
private:
static data_type var1_name; //private static data declaration
protected:
static data_type var2_name; //protected static data declaration
public:
static data_type var3_name; //public static data declaration
};
Data_type class_name : : var1_name; // definition of private static data
Data_type class_name : : var2_name; // definition of protected static data
Data_type class_name : : var3_name; // definition of public static data
Note:
As above, the access specifications, does not matter. All static data members need to be
defined outside the class.
Not declaring static variables outside will cause linker error.
Static functions
Static member functions have class scope. Unlike the non-static member functions, these functions
have no implicit this argument; therefore, they can use only static data members and enumerators
directly. Static member functions can be accessed without using an object of the corresponding class
type. Static member functions of a class are independent of objects.
Syntax
class class_name
{
--------
--------
public:
-------
static return_type function_name(arg list);
};
void Student
Static::member functions cannot
Enroll_Student(char *n, int i)access non-static data members of a class since the
memory of the non-static data is dependent on object creation.
{
Static member functions do not have this pointer.
if(Student :: Check_Strength()) //instead of this the below one can be replaced
We can call static member functions and data independent of the object with the help of the
if( ! Student::Check_Strength()
class )
name and the scope resolution operator.
{
Student *New_Student;
try
The following { is a simplified abstraction of Student illustrating the use of static variables and static
functions: New_Student = new Student(n, i);
Max_Students++;
//Program to illustrate the use of static variables and static functions
cout <<
// student.h – Header file"Successfully enrolled" << endl;
delete
#include <iostream> New_Student;
using namespace std;
}
const int Class_Strength = 25;
class Student catch(bad_alloc obj)
{ {
char cout << obj.what() << endl;
Name[20];
cout << "Enrolling new student failed" << endl;
int College_Id;
}static int Max_Students;
}public:
else Student();
{ Student(char *n, int C);
cout
static<< "Maximum
bool class strength acheived" << endl;
Check_Strength();
cout <<
static "Have
void to wait for next year's
Enroll_Student(char*, int); admissions" << endl;
} static void Leave_School();
}};
void Student :: Leave_School()
{//student_lib.cpp – Library layer
cout << "Your certificates are returned" << endl;
Max_Students--;
#include "student.h"
}
//student_app.cpp – Application layer
int Student :: Max_Students;
#include "student.h"
int main() Student() {}
Student::
{
Studentchar:: option;
Student(char *n, int i)
{ cout << "Enroll : press e" << endl;
cout << "Leaven);: press l" << endl;
strcpy(Name,
College_Id = i;
} cin >> option;
switch(option)
bool Student :: Check_Strength()
{ {
case 'e' :
if(Class_Strength < Max_Students)
{return false;
else char name[20];
returninttrue;
id;
} cout << "Enter the name and id of the student" << endl;
cin >> name >> id;
Student :: Enroll_Student(name, id);
break;
}
© Cranes Varsity V2.1-2020-21 47/ 126
C++ www.cranesvarsity.com
case 'l' :
Student :: Leave_School();
break;
default :
break;
}
}
Const in C++
Constants in Class
Constants are used in a class as const data members and const member functions.
Const data
Writing data members in a class is just declaration. Constants, have to be initialized as soon
as they are declared. Since it cannot be done at the time of declaration, member initializer list
have to used for this.
Syntax:
class class_name
{
const data_type1 data1;
const data_type2 data2;
public:
class_name():data1(val1),data2(val2)
{
-----
}
class_name(arg1, arg2) : data1(arg1), data2(arg2)
{
------
}
};
NOTE:
The initializer list can be used to initialize both const and non-const data.
Multiple data members’ initialization is separated by comma operator
It is extremely powerful programming technique, since it allows different constant values to
be set for different objects of the same class.
Only the member functions of a class can be made a constant member functions.
Const member functions can access/use all the data members of a class but cannot change
any of the values.
A const function is also called an “accessor”, i.e. It can access all the data members of a
class but cannot modify any of these values
The exceptions to this rule are the static data members and the location pointed by the
pointer data members of the class.
If the declaration and definition of const member function are separate then the const key
word (appears) should be used in both declaration and definition after the argument list of
the function
In constant member functions, this pointer will be acting as a constant pointer to a
constant object. Ie) there can be a constant and non constant version of the same function
Syntax
class class_name
{
data_type1 data1;
data_type2 data2;
-------;
public:
------
------
return_type function_name(argument list) const ;
};
Constant Objects
User defines data types – objects – can be qualified constants exactly like the built-in data types.
A constant object can make a call to constant member functions of the class
The exception to this rule is the constructor that will be invoked when an object is
created and the destructor will be invoked (that has to be called) when the object is
being destroyed.
The values of the data members of a const object cannot be changed.
Syntax:
#include "course.h"
return 0;
}
**********
CHAPTER - 6
FRIEND KEYWORD AND OPERATOR OVERLOADING
Friends in C++
Any data or function which is declared as private inside a class is not accessible outside the
class. A function which is not a member of the class can access such private data. There are
instances where programmers will need to access the private data from non-member functions and
external classes. C++ offers the concept of “friend” to facilitate such cases.
A class can allow non-member functions and other classes to access its own private data, by
making them as friends. Once a non-member function is declared as a friend, it can access the private
data of the class. Similarly when a class is declared as a friend, the friend class can have access to
the private data of the class, which made it as a friend
Syntax
class class_name
{
---------
---------
friend return_type function_name(argument);//declaring friend function
};
Operator Overloading
Operator overloading allows all the C++ operators to be given new definitions so that they can
be used as easily with user-defined data types as with built-in data types. To write operator overloaded
functions, we can either use member functions or friend functions.
If you notice the above syntax functions names of the operator function have two parts
The “operator” key word
The symbol of the operator, represented by the “#” in the syntax of the function.The
operator that we want to overload can substitute the # in the syntax.
Lets see operator overloading of few operators which we commonly use in our programming.
Complex
-Real : double
-Imaginary : double
+Complex()
+Complex(double, double)
+ operator+=(const Complex&): Complex&
+ Ret_Real() const : double
+ Ret_Imaginary() const : double
class Complex
{
private:
double Real;
double Imaginary;
public:
Complex();
Complex(double, double);
Complex& operator+=(const Complex&);
double Ret_Real() const;
double Ret_Imaginary() const;
};
//complex_lib.cpp
#include "complex.h"
Output :
Complex& Complex :: operator+= (const Complex &obj)
{ Real part of o1 = 1.1
Real += obj.Real; Imaginary part of o1 = 2.2
Imaginary += obj.Imaginary;
return *this;
}
double Complex :: Ret_Real() const
{
return Real;
}
//complex_app.cpp
#include "complex.h"
int main()
{
Complex o1, o2(1.1, 2.2);
o1 += o2;
return 0;
}
-Real : double
-Imaginary : double
+Complex()
+Complex(double, double)
+ Ret_Real() const : double
+ Ret_Imaginary() const : double
friend operator+=(Complex&,const Complex&):Complex &
class Complex
{
private:
double Real;
double Imaginary;
public:
Complex();
Complex(double, double);
double Ret_Real( ) const;
double Ret_Imaginary( ) const;
friend Complex& operator+=(Complex&, const Complex&);
};
//complex_lib.cpp
#include "complex.h"
int main()
{
Complex o1, o2(1.1, 2.2);
o1 += o2;
return 0;
}
Output :
-Real : double
-Imaginary : double
+Complex()
+Complex(double, double)
+Ret_Real() const : double
+ Ret_Imaginary() const : double
+ operator+(int) : Complex
friend operator+(int, const Complex&) :Complex
class Complex
{
private:
double Real;
double Imaginary;
public:
Complex();
Complex(double, double);
double Ret_Real() const;
double Ret_Imaginary() const;
Complex operator+(int);
friend Complex operator+(int, const Complex&);
};
//complex_lib.cpp – Library layer
#include "complex.h"
int main()
{
Complex o1, o2(1.1, 2.2), o3(3.3, 4.4), o4;
o1 = o2 + 10;
cout << "Real part of o1 = " << o1.Ret_Real() << endl;
cout << "Imaginary part of imaginary = " << o1.Ret_Imaginary() << endl;
o4 = 10 + o3;
cout << "Real part of o4 = " << o4.Ret_Real() << endl;
cout << "Imaginary part of o4 = " << o4.Ret_Imaginary() << endl;
return 0;
}
Output:
-Real : double
-Imaginary : double
+Complex()
+Complex(double, double)
+ Ret_Real() const :double
+ Ret_Imaginary() const :double
+ operator++(): Complex&
friend operator++(Complex&, int) : Complex
class Complex
{
private:
double Real;
double Imaginary;
public:
Complex();
Complex(double, double);
double Ret_Real() const;
double Ret_Imaginary() const;
Complex& operator++();
friend Complex operator++(Complex&, int);
};
int main()
{
Complex o1, o2(1.1, 2.2), o3(3.3, 4.4), o4;
Output:
o1 = ++o2;
Real part of o1 = 2.1
cout << "Real part of o1 = " << o1.Ret_Real() << endl; Imaginary part of o1 = 3.2
cout << "Imaginary part of o1 = " << o1.Ret_Imaginary() << endl;
Real part of o2 = 2.1
cout << "Real part of o2 = " << o2.Ret_Real() << endl; Imaginary part of o2 = 3.2
cout << "Imaginary part of o2 = " << o2.Ret_Imaginary() << endl;
Real part of o4 = 3.3
o4 = o3++; Imaginary part of o4 = 4.4
cout << "Real part of o4 = " << o4.Ret_Real() << endl; Real part of o3 = 4.3
Imaginary part of o3 = 5.4
cout << "Imaginary part of o4 = " << o4.Ret_Imaginary() << endl;
cout << "Real part of o3 = " << o3.Ret_Real() << endl;
cout << "Imaginary part of o3 = " << o3.Ret_Imaginary() << endl;
return 0;
}
Note : Try to overload prefix using friend and postfix using member also.
Insertion and extraction always works with two pre-defined objects, cout and cin which belong to
classes ostream and istream respectively. These objects are not capable of invoking the member
functions of our class. So both these operators can be overloaded only using friend functions.
Complex
-Real : double
-Imaginary : double
+Complex()
+Complex(double, double)
+ Ret_Real() const : double
+ Ret_Imaginary() const: double
friend operator << (ostream&, const Complex&) : ostream&
class Complex
{
private:
double Real;
double Imaginary;
public:
Complex();
Complex(double, double);
friend ostream& operator<< (ostream&, const Complex&);
friend istream& operator>> (istream&, Complex&);
};
Output:
//complex_lib.cpp – Library layer
#include "complex.h" Enter the real part
1.1
Complex :: Complex() : Real(0.0), Imaginary(0.0) {} Enter the imaginary part
2.2
Enter the real part
Complex :: Complex(double r, double i) : Real(r), Imaginary(i) {}
3.3
Enter the imaginary part
ostream& operator<< (ostream &c, const Complex &o) 4.4
{
c << "Real part is = " << o.Real << endl; Real part is = 1.1
c << "Imaginary part is = " << o.Imaginary << endl; Imaginary part is = 2.2
return c;
Real part is = 3.3
}
Imaginary part is = 4.4
int main()
{
Complex o1, o2;
return 0;
}
Note:
If you notice, the arguments of istream and ostream are passed by reference. This is because
the copy constructor of both these classes are in the protected section of the class.
For the above class Complex, when an expression ‘o1 = o2’ is encountered, the compiler
provided overloaded assignment operator translates it into
o1.Real = o2.Real;
o1.imaginary = o2.Imaginary;
In other words assignment operator is doing a member wise assignment or shallow copy.
As long as the class doesn’t contain any pointer attribute, compiler provided assignment operator
is adequate. But for a class with pointer attributes, compiler provided assignment operator could create
serious problems.
Student
-Name : String
-College_Id : Integer
+Student()
+Student(char*, int)
+Student(const Student&)
+ Ret_Name() const :const char *
+ Ret_Id() const : int
+~Student()
friend Change_Id(Student) :Student
class Student
{
private:
char* Name;
int College_Id;
public:
Student( );
Student(char *, int);
Student(const Student&);
const char* Ret_Name( ) const;
int Ret_Id() const;
~Student();
friend Student Change_Id(Student);
};
Student :: Student() {}
Student :: Student(char *n, int i)
{
Name = new (nothrow) char[strlen(n) + 1];
if (Name == NULL)
exit(1);
strcpy(Name, n);
College_Id = i;
}
Student :: Student(const Student &s)
{
Name = new (nothrow) char[strlen(s.Name) + 1];
if (Name == NULL)
exit(1);
strcpy(Name, s.Name);
College_Id = s.College_Id;
}
const char* Student :: Ret_Name() const
{
return Name;
}
Student :: ~Student()
{
delete [ ] Name;
}
int main()
{
Student Anu("Anushri", 1);
Anu = Change_Id(Anu);
return 0;
}
If you analyze the memory picture of the above program before the Change_Id function returns:
Stack Heap
Anu
Name : 5000
A n u s h r i \0
College_Id : 1 5000
Sample
A n u s h r i \0
Name : 5500
5500
College_Id : 1000
Temporary object
Name : 6000
A n u s h r i \0
College_Id : 1000
6000
Once the function returns sample, the temporary object created internally corresponding to sample
invokes the compiler provided assignment operator to do the copying between Anu and temporary
object.
i.e Anu = temporary object
when the assignment operator do a member wise copy, the above memory picture becomes :
Stack Heap
Anu
5000
Name : 6000
A n u s h r i \0
College_Id : 1000
5500
Sample
A n u s h r i \0
Name : 5500
College_Id : 1000
A n u s h r i \0
Temporary object
Name : 6000
6000
College_Id : 1000
Here object Anu and Temporary object is pointing to the same memory location. There are two side
effects in the above program. First of all link to 5000 is lost and secondly when temporary object gets
de-allocated, memory pointed by Anu is also de-allocated. There fore the output of the above program
will be
Output :
Name of anu is =
Id of anu = 1000
So here, the assignment operator have to again overloaded to avoid the member wise or shallow
copy.
Assignment operator along with the following three operators should be overloaded only using
member functions.
()
[]
->
This is to ensure that the LHS operand is always an object of the same class so that we can avoid any
l-value errors.
Student
-Name : String
-College_Id : Integer
+Student()
+Student(char*, int)
+Student(const Student&)
+ operator=(const Student&) : const Student&
+ Ret_Name() const : const char*
+ Ret_Id() const :int
+~Student()
friend Change_Id(Student) : Student
#include <iostream>
using namespace std;
class Student
{
private:
char* Name;
int College_Id;
public:
Student();
Student(char *, int);
Student(const Student&);
const Student& operator= (const Student&);
const char* Ret_Name() const;
int Ret_Id() const;
~Student();
friend Student Change_Id(Student);
};
//student_lib.cpp – Library layer
#include "student.h"
Student :: Student( ) { }
Student :: Student(char *n, int i)
{
Name = new (nothrow) char[strlen(n) + 1];
if (Name == NULL)
exit(1);
strcpy(Name, n);
College_Id = i;
}
#include "student.h"
int main()
{
Student Anu("Anushri", 1);
Anu = Change_Id(Anu);
cout << "Name of anu is = " << Anu.Ret_Name() << endl;
cout << "Id of anu = " << Anu.Ret_Id() << endl;
return 0;
}
From all the examples, summarizing few general rules of operator overloading
At least one of the arguments passed to the overloaded functions has to be a user-defined
data types
The number of arguments to be passed to these functions is fixed depending on the type of
operator and the type of function(member or friend) performing the overloading
Unary Binary
Member Zero arguments One arguments
Friend One arguments Two arguments
These rules are strictly implemented. If you change the argument list, then the compiler generates
errors like “too many arguments “or “too few arguments”.
? : Operator which works with three operands at a time .So complexity increases.
The operators . .* :: operators have name association which the concept of overloading
absurd.
The operators sizeof(), typeid() and 4 casting operators are already working for user
defined data type.
# and ## are preprocessor directives , we should not change the actual meaning.
+ - * / % ^ & | ~
! = < > += -= *= /= %=
If you notice the concept of operator overloading, you can see that, a single interface (in this
case a single operator) is getting used for multiple functionalities. Or generally speaking, “Single
interface and multiple implementations”. This is technically termed as polymorphism.
We have already come across a similar situation where in we were using single interface and
multiple implementations. That was in function overloading. So function overloading can also be
considered as a kind of polymorphism.
Since in function overloading as well as operator overloading, binding (relating function call to function
definition) is taking place during compile time, we call this type of polymorphism as Compile time
polymorphism.
**********
CHAPTER 7
GENERIC PROGRAMMING : STANDARD TEMPLATE LIBRARY (STL)
The Standard Template Library (STL) is a general-purpose C++ library of algorithms and data
structures, originated by Alexander Stepanov and Meng Lee. The STL, based on a concept known as
generic programming, is part of the standard ANSI C++ library. The STL is implemented by means of
the C++ template mechanism, hence its name.
While some aspects of the library are very complex, it can often be applied in a very straightforward
way, facilitating reuse of the sophisticated data structures and algorithms it contains.
The STL consists of five main components, 3 of which we'll concentrate on first
Container: object that is able to keep and administer any type of data.(built in as well as user
defined)
Algorithm: computational procedure that is able to work on different containers
Iterator: abstraction of "pointer" or "index" - a means to access the appropriate element.
Container Container
Objects
Objects
Iterator
Iterator
Algorith
m
Algorithm
Iterator
Containers:
Containers are data structures that manage a collection of elements and are responsible for the
allocation and de-allocation of those elements. Containers typically have constructors and destructors
along with operations for inserting and deleting elements.
Elements are stored in containers as whole objects; no pointers are used to access the elements in a
container. This results in containers that are type safe and efficient. The STL provides three categories
of containers:
Sequence containers:
Sequence containers store elements in sequential order. These containers group a finite set of
elements in a linear arrangement. The STL includes class templates for vectors, lists, and deques.
deque Like vector, but can be accessed Quick random access Slow to insert or
at either end delete from in-
between
Quick access at
both ends
The syntax for creation of object for STL classes is exactly similar to the creation of object in normal
template classes. If you take vector as an example, the syntax will be:
vector <data_type> obj_name;
Associative containers:
Associative Containers provide for fast retrieval of objects from the collection based on keys. The size
of the collection can vary at runtime. The collection is maintained in order, based on a comparison
function object of type Compare (a default template parameter according to the STL standard, but a
non-default template parameter in the current implementation).
There are two kinds of associative containers in STL .Sets and Maps.
characteristics
set Stores only the key objects. Only one key of each value allowed
multiset Stores only the key objects. Multiple key values allowed
map Associates key object with value object. Only one key of each value
allowed.
multimap Associates key object with value object. Multiple key values allowed.
Container adapters:
Container adapters are the special purpose containers, created from the normal containers. The
special containers implemented from container adapters in STL are stacks, queues, and priority
queues.
Containers use member functions to perform simpler tasks that are specific to a particular type of
container. The table below shows most commonly used member functions
Name Purpose
size() Returns the number of items in the container
empty() Returns true if the container is empty.
max_size() Returns size of the largest possible container
begin() Returns an iterator to the start of the container, for iterating forward
through the container
end() Returns an iterator to the past-the-end location in the container, used to
end forward iteration.
rbegin() Returns a reverse iterator to the end of the container, for iterating
backward through the container.
rend() Returns a reverse iterator to the beginning of the container, used to end
backward iteration.
Algorithms:
An algorithm is a function that does something to the objects of the containers. Algorithms in STL are
not member functions or friends of container classes, as they are in earlier container libraries, but are
standalone template functions. They can be used with built in c++ arrays, or with container classes you
create yourself.
Algorithm purpose
find Returns the first element equivalent to a specified value.
count Returns the number of elements that have specified value.
equal Compares the contents of the two containers and returns true if all
corresponding elements are equal.
search Looks for a sequence of values from one container that correspond
with the same sequence in another container
copy Copies a sequence of values from one container to another(or to a
different location in the same container).
swap Exchanges a value in one location with a value in another.
inter_swap Exchanges a sequence of values in one location with a sequence of
values in another location.
fill Copies a value into a sequence of locations.
sort Sorts the values in the container according to a specified order.
merge Combines two sorted ranges of elements to make a larger sorted
range.
accumulate Returns the sum of the elements in a given range.
for_each Executes a specified function for each element in the container.
Iterators:
Iterators are the pointer-like entities that are used to access individual data items in a
container. They are used to move sequentially from element to element, through the container.
You can increment the iterators using the ++ operator and * operator to obtain the value of the
element they point to. In STL iterators are represented by an object of an iterator class.Different
classes of iterators are used with different types of containers.
//vector
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myints;
std::cout << "0. size: " << myints.size() << '\n';
myints.insert (myints.end(),10,100);
std::cout << "2. size: " << myints.size() << '\n';
myints.pop_back();
std::cout << "3. size: " << myints.size() << '\n';
return 0;
}
// List basics
#include <iostream>
#include <list>
using namespace std;
int main()
{
list<int> lst; // create an empty list
int i;
for(i=0; i<10; i++) lst.push_back(i);
cout << "Size = " << lst.size() << endl;
cout << "Contents: ";
list<int>::iterator p = lst.begin();
while(p != lst.end()) {
cout << *p << " ";
p++;
}
cout << "\n\n";
// change contents of list
p = lst.begin();
while(p != lst.end()) {
*p = *p + 100;
p++;
}
cout << "Contents modified: ";
p = lst.begin();
while(p != lst.end()) {
cout << *p << " ";
p++;
}
return 0;
}
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<char, int> m;
int i;
//stack
#include <iostream>
#include <stack>
using namespace std;
int main()
{
stack<int> st;
// push three elements into the stack
st.push(1);
st.push(2);
st.push(3);
// pop and print two elements from the stack
cout << st.top() << ‘ ‘:
st.pop();
cout << st.top() << ‘ ‘;
st.pop();
// modify top element
st.top() = 77;
// push two new elements
st.push(4);
st.push(5);
// pop one element without processing it
st.pop();
// pop and print remaining elements
while (!st.empty()) {
cout << st.top() << ‘ ‘:
st.pop();
}
cout << endl;
//queue
#include <iostream>
#include <queue>
#include <string>
using namespace std;
int main()
{
queue<string> q;
// insert three elements into the queue
q.push("These ");
q.push("are ");
q.push("more than ");
// read and print two elements from the queue
cout << q.front();
q.pop();
cout << q.front();
q.pop();
// insert two new elements
q.push("four ");
q.push("words!");
// skip one element
q.pop();
// read and print two elements
cout << q.front();
q.pop();
cout << q.front() << endl;
q.pop();
// print number of elements in the queue
cout << "number of elements in the queue: " << q.size()
<< endl;
}
//priority queue
#include <iostream>
#include <queue>
using namespace std;
int main()
{
priority_queue<float> q;
// insert three elements into the priority queue
q.push(66.6);
q.push(22.2);
q.push(44.4);
// read and print two elements
cout << q.top() << ‘ ‘:
q.pop();
cout << q.top() << endl;
q.pop();
// insert three more elements
q.push(11.1);
q.push(55.5);
q.push(33.3);
// skip one element
q.pop();
// pop and print remaining elements
while (!q.empty()) {
cout << q.top() << ‘ ‘:
q.pop();
}
cout << endl;
}
int main () {
std::vector<int> myvector (8); // myvector: 0 0 0 0 0 0 0 0
return 0;
}
int main () {
// counting elements in array:
int myints[] = {10,20,30,30,20,10,10,20}; // 8 elements
int mycount = std::count (myints, myints+8, 10);
std::cout << "10 appears " << mycount << " times.\n";
return 0;
}
// find example
#include <iostream> // std::cout
#include <algorithm> // std::find
#include <vector> // std::vector
int main () {
// using std::find with array and pointer:
int myints[] = { 10, 20, 30, 40 };
int * p;
return 0;
}
int main () {
int myints[]={10,20,30,40,50,60,70};
std::vector<int> myvector (7);
return 0;
}
**********
CHAPTER - 8
GENERALIZATION - INHERITANCE
Inheritance is built around a specific relationship between different classes generally read as “is – a”
relationship. For instance, a "fruit" is a generalization of "apple", "orange", "mango" and many others.
We say that fruit is an abstraction of apple, orange, etc. Conversely, we can say that since apples are
fruit (i.e. an apple is-a fruit), they inherit all the properties common to all fruit, such as being a fleshy
container for the seed of a plant
Thus, one of the major advantages of inheritance is that modules with sufficiently similar interfaces can
be commanded by shared controlling code, reducing the complexity of the program. Inheritance
therefore has another view, a dual, called polymorphism, which describes many pieces of code being
controlled by shared control code.
What we have already covered with the compile time polymorphism is the basic understanding of
polymorphism. For all object oriented languages, the most powerful and useful application of
polymorphism is with inheritance with the help of virtual functions. Before we cover run – time
polymorphism, let us understand inheritance.
Creating or deriving a new class using an existing class base is called inheritance. The new class
created is called a Derived class and the existing class used as a base is called a Base class in C++
inheritance terminology. The derived class inherits all the features of the base class and also adds its
own features, i.e. data members and member functions.
Syntax of inheritance
class baseclass_name
{
--------
};
class deriver-class_name : mode of inheritance base-class_name
{
--------
};
While working with inheritance, we need to take care of the “mode” of inheritance.
The mode can be any of the three
private
public
protected
The mode of inheritance helps us to decide the access specification of a base class member in the
derived class. The table below shows the mode of inheritance and the affect it has in inheritance
Mode of inheritance
Base class Member Private Protected Public
Private Not Accessible Not Accessible Not Accessible
Protected Private Protected Protected
Public Private Protected Public
The above table clearly highlights the difference between the private and the protected access
specifiers. Under no circumstances can the base class private attributes be accessed in the derived
class.
Types of Inheritance
The concept of inheritance is not limited to a single base class. Inheritance can be used to build
complex class hierarchies and extend the uses of inheritance to the hierarchy. There are mainly five
types of inheritance classifications. These are
Single Inheritance
};
Let’s consider our college management systems and let’s try to trace out a single inheritance
hierarchy of student
Student
-Name : String
-College_Id : Integer
-Course: String
-Fees : Float
#Total_Marks : Float
Engineering_Student
-Project_Title : String
-Project_Marks : Float
+Set_Project_Title(char *):void
+Set_Project_Marks(float):void
+Update_Total_Marks():void
}
//engg_student.h – Header file of engg_student (derived
class)
#include "student.h"
Syntax :
Class Base
{
-------
}; Derived1
Class Derived1 : mode Base
{
----------
};
Class Derived2 : mode Derived1
Derived2
{
-------
};
Lets try to extend the student hierarchy for multilevel inheritance
Student
Engineering_Student
Computer_Engg_Student
Note : Try to implement the above mentioned hierarchy by putting attributes and behaviours in each
and every class.
Hierarchical Inheritance
Syntax: Base
class Base
{
----
};
class Derived1 : mode Base
{ Derived1 Derived2
-----
};
Class Derived2 : mode Base
{
------ Derived3 Derived4
};
Class Derived3 : mode Derived1
{
-----
};
Class Derived4 : mode Derived 2
{
----
};
Extending the students examples with hierarchical inheritance
Student
Engineering_Student Medical_Student
Note : Try to implement the above mentioned hierarchy with proper attributes and behaviours.
Multiple Inheritance
Syntax
BASE1 BASE2
class Base1
{
-------
};
class Base2 DERIVED
{
-------
};
class Derived: mode Base1, mode Base2
{
-------
};
Let’s take the classes electronics engineer and computer engineer in the student hierarchy and try to
extend it for multiple inheritances.
Electronics_Engg Computer_Engg
+Set_Language(char *):void
+Set_Device_Name(char*):void
+Set_Device_No(int):void
Embedded_Engg
+Ret_Device_Name():char*
+Ret_Device_No():int
+Ret_Programming_Lang():char *
#ifndef __ELECT_H
#define __ELECT_H
class Electronics_Engg
{
protected:
char Device_Name[20];
int Device_No;
public:
void Set_Device_Name(char*);
void Set_Device_No(int);
};
#endif
class Computer_Engg
{
protected:
char Programming_Lang[20];
public:
void Set_Language(char*);
};
#endif
#ifndef __EMBB_H
#define __EMBB_H
int main()
{
Embedded_Engg Anu;
char dev_name[20], prog_lang[20];
int dev_no;
Anu.Set_Device_Name(dev_name);
Anu.Set_Device_No(dev_no);
Anu.Set_Language(prog_lang);
return 0;
}
Output:
NOTE: Each base class should mention the mode of inheritance separately
Base
Syntax :
class Base
{
--------
};
Derived1 Derived2
class Derived1: mode Base
{
--------
};
class Derived2 : mode Base
{
--------
};
class Derived: mode Derived1,mode Derived2 Derived
{
-------
};
From the above syntax it is quite clear that the final derived class “Derived” will have a problem since it
gets multiple copies of class “Base” from both of the intermediate classes “Derived1” and “Derived2”.
This multiplicity or duplication of code in the final derived class is known as Death of diamond or
Dreaded diamond. This can be solved by using the virtual keyword while inheriting the base class into
its derived classes and this concept is known as virtual base class concept.
So the above syntax has to be modified as
class Base
{
--------
};
class Derived1: virtual mode Base
{
--------
};
class Derived2 : virtual mode Base
{
--------
};
class Derived: mode Derived1,mode Derived2
{
-------
};
Lets see a technical implementation of hybrid inheritance using virtual concept. We will concentrate on
the same student’s example here.
Engineering_Student
#Project_Title : String
#Project_Marks : float
+Set_Project_Title(char*):void
+Set_Project_Marks(float):void
Electronics_Engg_Student Computer_Engg_Student
#Device_No : Integer
#Device_Name : String #Programming_Lang : String
+Set_Device_Name(char*):void
+Set_Device_No(int):void +Set_Language(char *):void
Embedded_Engg_Student
+Ret_Device_Name():char*
+Ret_Device_No():int
+Ret_Programming_Lang():char*
#ifndef __ENGG_STUDENT
#define __ENGG_STUDENT
class Engineering_Student
{
protected:
char Project_Title[20];
float Project_Marks;
public:
void Set_Project_Title(char *);
void Set_Project_Marks(float);
};
#endif
#ifndef __COMP_H
#define __COMP_H
#ifndef __ELECT_H
#define __ELECT_H
//elect_engg_lib.cpp
#include "elect_engg.h"
#ifndef __EMBB_H
#define __EMBB_H
int main()
{
Embedded_Engg_Student Anu;
Anu.Set_Project_Title(proj_title);
Anu.Set_Project_Marks(proj_marks);
Anu.Set_Device_Name(dev_name);
Anu.Set_Device_No(dev_no);
Anu.Set_Language(prog_lang);
return 0;
}
Output :
The behavior of constructors and destructors in inheritance is an important concept for discussion.
There are three types of member functions of a class that can be accessed but never inherited. Some
member functions can only be accessed through the derived class objects and not inherited. These
are
Constructors
Overloaded assignment operator
There are certain important concepts to make note of when working with constructors and destructors
in inheritance.
Order of construction: The base class constructors are always executed first followed
by the derived class constructors
Order of destruction: The derived class destructor is always executed before the base
class destructor
If the parameterized constructor is present in the base class, then the immediate derived
class constructor has to take care of supplying the arguments to the constructor of the
base class
By default, all the derived class constructors invoke base class default constructors
To invoke any other constructor, make use of the derived constructor’s initializer list.
//Program illustrating the call of constructors and destructors: Multiple inheritance
//comp_engg.h – Header file of computer engineering student
#include <iostream>
using namespace std;
#ifndef __COMP_H
#define __COMP_H
class Computer_Engg_Student
{
protected:
char Programming_Lang[20];
public:
Computer_Engg_Student();
Computer_Engg_Student(char*);
};
#endif
//embb_engg_lib.cpp
#include "embb_engg.h"
Embedded_Engg_Student :: Embedded_Engg_Student() {}
int main( )
{
Embedded_Engg_Student Anu("Java", "PCB", 1020);
return 0;
}
Output :
In multiple inheritances, the order of construction is the order in which the base classes appear in the
declaration of the derived class. The order of destruction is the reverse of the order of destruction
base2 base1()
derived()
C++ www.cranesvarsity.com
Note : Try out constructor calls in single, multilevel and hierarchical inheritance in a similar manner.
The rule of immediate derived class passing arguments to base is valid in all the inheritance except
hybrid / diamond inheritance. In hybrid inheritance, since Derived1 and Derived2 are immediate
derived classes of base, passing arguments to base through both these classes will result in re-
initialization of the
//comp_engg.h single base
– Header location.
file of computerSo in hybrid inheritance, the indirect derived class, ie) Derived
engineer
will have to take
#include <iostream> up the responsibility of passing arguments to base.
#include "engg_student.h"
Modifying
using the hybrid
namespace std;inheritance example with constructors
#ifndef __COMP_H
//Program illustrating parameter passing in hybrid inheritance
#define __COMP_H
//engg_student.h – Header file of engineering student
#include <iostream>
class
using Computer_Engg_Student
namespace std; : virtual public Engineering_Student
{ __ENGG_STUDENT
#ifndef
protected:
#define __ENGG_STUDENT
char Programming_Lang[20];
class Engineering_Student
{ public:
Computer_Engg_Student();
protected:
Computer_Engg_Student(char*,
char Project_Title[20]; float, char*);
}; float Project_Marks;
#endif public:
//comp_engg_lib.cpp – Library layer of computer engineer
Engineering_Student();
#include "comp_engg.h"
Engineering_Student(char*, float);
};
Computer_Engg_Student
#endif :: Computer_Engg_Student()
{ //engg_student_lib.cpp – Library layer of engineering student
strcpy(Programming_Lang,
#include "engg_student.h" "");
} Engineering_Student :: Engineering_Student()
{
Computer_Engg_Student
strcpy(Project_Title, ""); :: Computer_Engg_Student(char *p, float m, char *l) :
Engineering_Student(p,m)
Project_Marks = 0;
{}
strcpy(Programming_Lang,
Engineering_Student l);
:: Engineering_Student(char *p, float m)
}{
//elect_engg.h – Header file
strcpy(Project_Title, p);of electronics engineer
#include <iostream> = m;
Project_Marks
#include
} "engg_student.h"
using namespace std;
#ifndef __ELECT_H
#define __ELECT_H
Electronics_Engg_Student::Electronics_Engg_Student()
{
strcpy(Device_Name, "");
Device_No = 0;
}
#ifndef __EMBB_H
#define __EMBB_H
Embedded_Engg_Student :: Embedded_Engg_Student() {}
Output:
NOTE: Try out what will happen if you are not passing the arguments for base through final derived
class.
Few points to be noted:
While working with inheritance, use upward facing arrow, since the base class cannot look into
the derived class where as the derived class knows everything about the base class.
If the mode of inheritance is not specified, the default mode is private for class.
The size of the derived class is equal to the sum of the size of base class data members
plus size of derived class data members
The difference between the private and the protected modes of inheritance is that once
inherited through the private mode, further access of the base class members through the
derived class becomes impossible
CHAPTER - 9
RUN TIME POLYMORPHISM
In this chapter, let’s see, what is the purpose of going for run time polymorphism and how to
implement run time polymorphism in C++.
In inheritance, a pointer created for base class can point either to base class objects or any of its
derived class objects.
Base class pointer pointing to derived class objects is technically termed as “upcasting” and
it is perfectly legal in C++.
Derived class pointer pointing to base class objects is illegal and is technically termed as
“downcasting”.
While establishing “is a relationship”, there is a facility of writing functions with the same
name, arguments and return type in the base class and the derived class. This is known as
function overriding.
Let’s analyze the behaviour of a program with the above mentioned concepts.
Student
-Name: String
#Attendence: Float
#Internal_Marks: Float
#External_Marks: Float
#Total_Marks: Float
+ Student ()
+Student (char*, float, float, float)
+Calculate_Marks(): void
+Calculate_Percentage(int)
Engineering_Student Medical_Student
+Engineering_Student() +Medical_Student()
+Engineering_Student(char*, float, float, float) +Medical_Student(char*, float, float, float)
+Calculate_Marks(): void
};
#endif
#ifndef __MED_H
#define __MED_H
class Medical_Student : public Student
{
public:
Medical_Student();
Medical_Student(char*, float, float, float);
};
#endif
Medical_Student :: Medical_Student() {}
Medical_Student :: Medical_Student(char *n, float a, float i, float e)
: Student(n, a, i, e) {}
//med_engg_app.cpp – Application layer
#include "student.h"
#include "engg_student.h"
#include "medical_student.h"
int main()
{
Student *Sptr, Anu("Anu", 80, 200, 200);
Engineering_Student Vinu("Vinu", 94, 400, 400);
Medical_Student Sonu("Sonu", 55, 100, 100);
int no_subjects = 10;
Sptr = &Anu;
Sptr -> Calculate_Marks();
cout << "Anu's marks = ";
cout << Sptr -> Calculate_Percentage(no_subjects) << endl;
Sptr = &Sonu;
Sptr -> Calculate_Marks();
cout << "Sonu's marks = ";
cout << Sptr -> Calculate_Percentage(no_subjects) << endl;
return 0;
}
But when you compile the program the program and see the behaviour, the output you will obtain is :
Output :
Anu's marks = 40
Vinu's marks = 80
Sonu's marks = 20
As you can notice we are not getting the expected output while calculating vinu (derived class object)
This variation in the output is a result of compile time binding or compile time polymorphism. Here
compiler is just checking the type of the pointer (which is always student*) and doing the binding
according to the type. So instead of calling the overridden functions in the ‘Engineering_Student’ class,
always the function inherited from ‘Student class’ will get invoked. It is in this type of situations, the
real need of run time polymorphism arises.
Run time binding or late binding is achieved by using the virtual keyword when declaring the functions
in the base class. To create a virtual function, precede the declaration of the function with the keyword
virtual. Note, only the declaration need the virtual keyword, not the definition.
#ifndef __STUDENT_H
#define __STUDENT_H
class Student
{
private:
char Name[20];
protected:
float Attendence;
float Internal_Marks;
float External_Marks;
float Total_Marks;
public:
Student();
Student(char*, float, float, float);
virtual void Calculate_Marks();
float Calculate_Percentage(int);
};
#endif
//student_lib.cpp – Library layer of student
#include "student.h"
Student :: Student()
{
strcpy(Name, "");
Attendence = 0.0;
Internal_Marks = 0.0;
External_Marks = 0.0;
Total_Marks = 0;
}
#ifndef __ENGG_H
#define __ENGG_H
#ifndef __MED_H
#define __MED_H
class Medical_Student : public Student
{
public:
Medical_Student();
Medical_Student(char*, float, float, float);
};
#endif
//medical_student_lib.cpp – Library layer of medical student
#include "medical_student.h"
Medical_Student :: Medical_Student() {}
int main()
{
Student *Sptr, Anu("Anu", 80, 200, 200);
Engineering_Student Vinu("Vinu", 94, 400, 400);
Medical_Student Sonu("Sonu", 55, 100, 100);
Sptr = &Anu;
Sptr -> Calculate_Marks();
cout << "Anu's marks = ";
cout << Sptr -> Calculate_Percentage(no_subjects) << endl;
Sptr = &Vinu;
Sptr -> Calculate_Marks();
cout << "Vinu's marks = ";
cout << Sptr -> Calculate_Percentage(no_subjects) << endl;
Sptr = &Sonu;
Sptr -> Calculate_Marks();
cout << "Sonu's marks = "; Output :
cout << Sptr -> Calculate_Percentage(no_subjects) << endl; Anu's marks = 40
Vinu's marks = 80.5
return 0;
Sonu's marks = 20
}
If a function is made as virtual in the base class, all the overridden functions in the derived class also
becomes virtual. The keyword virtual tells the compiler to automatically install all the mechanisms
needed for late binding. As part of it, compiler creates a single table called “Virtual Table or Vtable” for
each class. Compiler places the address of the virtual functions for that particular class in the Vtable.
In each class with virtual functions, compiler places an implicit pointer called the virtual pointer (vptr)
when it compiles the virtual functions of a class. It inserts code along with the constructor to initialize
the virtual pointer.The vptr holds the address of the V-table of that class.
When a virtual function call is made through a base class pointer, the compiler quickly inserts code to
fetch the Vptr and look up the function address in the Vtable thus calling the correct function and
causing late binding to take place.
Virtual Destructors
#ifndef __STUDENT_H
#define __STUDENT_H
class Student
{
private:
char Name[20];
protected:
float Attendence;
float Internal_Marks;
float External_Marks;
float Total_Marks;
public:
Student();
Student(char*, float, float, float);
virtual void Calculate_Marks();
float Calculate_Percentage(int);
~Student();
};
#endif
//student_lib.cpp – Library layer of student
#include "student.h"
Student :: Student()
{
strcpy(Name, "");
Attendence = 0.0;
Internal_Marks = 0.0;
External_Marks = 0.0;
Total_Marks = 0;
}
When you run this program, you can see that delete Sptr calls only the base class destructor (Student
class destructor). The problem here is since Sptr is of type base, the compiler can only call the base
class destructor. So this problem introduces memory leakage in the program. Here also the solution is
going to be with virtual keyword.
};
#endif
//student_lib.cpp – Library layer of student
#include "student.h"
Student :: Student()
{
strcpy(Name, "");
Attendence = 0.0;
Internal_Marks = 0.0;
External_Marks = 0.0;
Total_Marks = 0;
}
#include "student.h"
#include "engg_student.h"
int main()
{
Student *Sptr;
Sptr = new Engineering_Student;
delete Sptr;
return 0;
}
Once the base class destructor is made as virtual, deleting Sptr automatically calls
Engineering_Student’s destructor as well as Student’s destructor.
Note: Even though the destructor, like the constructor is an exceptional function, it is possible to make
destructor as virtual. But constructors can never me made as virtual. This is because, during
construction, the object already knows what type it is where as during destruction it doesn’t know that.
Abstract Class
Sometimes in our design, we may want our base class to act only as an Interface class. Or in other
workds we may not want to create an object of base class but we may still want to inherit it. This is
accomplished by using the concept of “abstract classes”.
Abstract classes are classes form which we can never create objects.A class can be made abstract by
declaring at least one pure virtual function inside it. A pure virtual function is one which is equated to
zero.
Syntax :
virtual return_type function_name( )=0;
When we write a pure virtual function, compiler reserves a slot for the function in the Vtable but no
address will be put in the slot. This makes the Vtable incomplete which further makes the class
definition as incomplete. This prevents the creation of object for such a class
When an abstract class is inherited, all the pure virtual function should be overridden in all the derived
classes. Else, the derived class will also become abstract.
Lets modify the “Student” program with the concept of abstract classes.
#ifndef __STUDENT_H
#define __STUDENT_H
class Student
{
private:
char Name[20];
protected:
float Attendence;
float Internal_Marks;
float External_Marks;
float Total_Marks;
public:
Student();
Student(char*, float, float, float);
virtual void Calculate_Marks() = 0;
float Calculate_Percentage(int);
};
#endif
Student :: Student()
{
strcpy(Name, "");
Attendence = 0.0;
Internal_Marks = 0.0;
External_Marks = 0.0;
Total_Marks = 0;
}
#ifndef __ENGG_H
#define __ENGG_H
Engineering_Student :: Engineering_Student() {}
//medical_student.h
#include <iostream>
#include "student.h"
using namespace std;
#ifndef __MED_H
#define __MED_H
class Medical_Student : public Student
{
public:
Medical_Student();
Medical_Student(char*, float, float, float);
void Calculate_Marks();
};
#endif
Medical_Student :: Medical_Student() {}
{
Student *Sptr;
Engineering_Student Vinu("Vinu", 94, 400, 400);
Medical_Student Sonu("Sonu", 55, 100, 100);
Sptr = &Vinu;
Sptr -> Calculate_Marks();
cout << "Vinu's marks = ";
cout << Sptr -> Calculate_Percentage(no_subjects) << endl;
Sptr = &Sonu;
Sptr -> Calculate_Marks();
cout << "Sonu's marks = ";
cout << Sptr -> Calculate_Percentage(no_subjects) << endl;
return 0;
}
Output :
Vinu's marks = 80.5
Sonu's marks = 20
CHAPTER - 10
EXCEPTION HANDLING
An exception is a problem that arises during the execution of a program. A C++ exception is a
response to an exceptional circumstance that arises while a program is running, such as an attempt to
divide by zero.
Exceptions provide a way to transfer control from one part of a program to another. C++ exception
handling is built upon three keywords: try, catch, and throw.
throw − A program throws an exception when a problem shows up. This is done using a throw
keyword.
catch − A program catches an exception with an exception handler at the place in a program where
you want to handle the problem. The catch keyword indicates the catching of an exception.
try − A try block identifies a block of code for which particular exceptions will be activated. It's followed
by one or more catch blocks.
Assuming a block will raise an exception, a method catches an exception using a combination of the
try and catch keywords. A try/catch block is placed around the code that might generate an exception.
Code within a try/catch block is referred to as protected code, and the syntax for using try/catch as
follows −
try {
// protected code
} catch( ExceptionName e1 ) {
// catch block
} catch( ExceptionName e2 ) {
// catch block
} catch( ExceptionName eN ) {
// catch block
}
You can list down multiple catch statements to catch different type of exceptions in case your try block
raises more than one exception in different situations.
Throwing Exceptions
Exceptions can be thrown anywhere within a code block using throw statement. The operand of the
throw statement determines a type for the exception and can be any expression and the type of the
result of the expression determines the type of exception thrown.
Catching Exceptions
The catch block following the try block catches any exception. You can specify what type of exception
you want to catch and this is determined by the exception declaration that appears in parentheses
following the keyword catch.
try
{
// protected code
}
catch( ExceptionName e )
{
// code to handle ExceptionName exception
}
Above code will catch an exception of Exception Name type. If you want to specify that a catch block
should handle any type of exception that is thrown in a try block, you must put an ellipsis, ..., between
the parentheses enclosing the exception declaration as follows −
Try
{
// protected code
}
catch(...)
{
// code to handle any exception
}
The following is an example, which throws a division by zero exception and we catch it in catch block.
#include <iostream>
using namespace std;
double division(int a, int b)
{
if( b == 0 )
{
throw "Division by zero condition!";
}
return (a/b);
}
int main ()
{
int x = 50;
int y = 0;
double z = 0;
Try
{
z = division(x, y);
cout << z << endl;
}
catch (const char* msg)
{
cerr << msg << endl;
}
return 0;
}
Because we are raising an exception of type const char*, so while catching this exception, we have to
use const char* in catch block. If we compile and run above code, this would produce the following
result
1) Following is a simple example to show exception handling in C++. The output of program explains
flow of execution of try/catch blocks.
#include <iostream>
Using namespace std;
Int main()
{
intx = -1;
// Some code
cout <<"Before try \n";
Try
{
cout <<"Inside try \n";
if(x <0)
{
throwx;
cout <<"After throw (Never executed) \n";
}
}
catch(intx )
{
cout <<"Exception Caught \n";
}
cout <<"After catch (Will be executed) \n";
return0;
}
Output:
Before try
Inside try
Exception Caught
After catch (Will be executed)
2) There is a special catch block called ‘catch all’ catch(…) that can be used to catch all types of
exceptions. For example, in the following program, an int is thrown as an exception, but there is no
catch block for int, so catch(…) block will be executed.
#include <iostream>
usingnamespacestd;
intmain()
{
try {
throw10;
}
catch(char*excp) {
cout <<"Caught "<<excp;
}
catch(...) {
cout <<"Default Exception\n";
}
return 0;
}
Output:
Default Exception
3) Implicit type conversion doesn’t happen for primitive types. For example, in the following program ‘a’
is not implicitly converted to int
#include <iostream>
Int main()
try {
throw'a';
catch(intx) {
catch(...) {
return0;
Output:
Default Exception
4) If an exception is thrown and not caught anywhere, the program terminates abnormally. For
example, in the following program, a char is thrown, but there is no catch block to catch a char.
#include <iostream>
usingnamespacestd;
Int main()
{
try {
throw'a';
}
catch(intx) {
cout <<"Caught ";
}
return0;
}
Output:
6) Like Java, C++ library has a standard exception class which is base class for all standard
exceptions. All objects thrown by components of the standard library are derived from this class.
Therefore, all standard exceptions can be caught by catching this type
7) Unlike Java, in C++, all exceptions are unchecked. Compiler doesn’t check whether an exception is
caught or not . For example, in C++, it is not necessary to specify all uncaught exceptions in a function
declaration. Although it’s a recommended practice to do so. For example, the following program
compiles fine, but ideally signature of fun() should list unchecked exceptions.
filter_none
edit
play_arrow
brightness_4
#include <iostream>
usingnamespacestd;
int main()
{
try{
fun(NULL, 0);
}
catch(...) {
cout <<"Caught exception from fun()";
}
return0;
}
Output:
8) In C++, try-catch blocks can be nested. Also, an exception can be re-thrown using “throw; ”
#include <iostream>
usingnamespacestd;
intmain()
{
try{
try {
throw20;
}
catch(intn) {
cout <<"Handle Partially ";
throw; //Re-throwing an exception
}
}
catch(intn) {
cout <<"Handle remaining ";
}
return0;
}
Output:
A function can also re-throw a function using same “throw; “. A function can handle a part and can ask
the caller to handle remaining.
9) When an exception is thrown, all objects created inside the enclosing try block are destructed
before the control is transferred to catch block.
#include <iostream>
usingnamespacestd;
classTest {
public:
Test() { cout <<"Constructor of Test "<<endl; }
~Test() { cout <<"Destructor of Test " <<endl; }
};
intmain() {
try{
Test t1;
throw10;
} catch(inti) {
cout <<"Caught "<<i <<endl;
}
}
Output:
Constructor of Test
Destructor of Test
Caught 10
Here is the small description of each exception mentioned in the above hierarchy −
std::bad_alloc
2
This can be thrown by new.
std::bad_cast
3
This can be thrown by dynamic_cast.
std::bad_exception
4
This is useful device to handle unexpected exceptions in a C++ program.
std::bad_typeid
5
This can be thrown by typeid.
std::logic_error
6
An exception that theoretically can be detected by reading the code.
std::domain_error
7
This is an exception thrown when a mathematically invalid domain is used.
std::invalid_argument
8
This is thrown due to invalid arguments.
std::length_error
9
This is thrown when a too big std::string is created.
std::out_of_range
10 This can be thrown by the 'at'method, for example a std::vector and std::bitset<>::operator[]
().
std::runtime_error
11
An exception that theoretically cannot be detected by reading the code.
12 std::overflow_error
std::range_error
13
This is occurred when you try to store a value which is out of range.
std::underflow_error
14
This is thrown if a mathematical underflow occurs.
#include <iostream>
#include <exception>
using namespace std;
int main() {
try {
throw MyException();
} catch(MyException& e) {
std::cout << "MyException caught" << std::endl;
std::cout << e.what() << std::endl;
} catch(std::exception& e) {
//Other errors
}
}
MyException caught
C++ Exception
Here, what() is a public method provided by exception class and it has been overridden by all the child
exception classes. This returns the cause of an exception.
CHAPTER - 11
MULTITHREADING
Multithreading is a specialized form of multitasking and a multitasking is the feature that allows your
computer to run two or more programs concurrently. In general, there are two types of multitasking:
process-based and thread-based.
A multithreaded program contains two or more parts that can run concurrently. Each part of such a
program is called a thread, and each thread defines a separate path of execution.
C++ does not contain any built-in support for multithreaded applications. Instead, it relies entirely upon
the operating system to provide this feature.
Creating Threads
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
Here, pthread_create creates a new thread and makes it executable. This routine can be called any
number of times from anywhere within your code. Here is the description of the parameters −
The maximum number of threads that may be created by a process is implementation dependent.
Once created, threads are peers, and may create other threads. There is no implied hierarchy or
dependency between threads.
Terminating Threads
Here pthread_exit is used to explicitly exit a thread. Typically, the pthread_exit() routine is called after
a thread has completed its work and is no longer required to exist.
If main() finishes before the threads it has created, and exits with pthread_exit(), the other threads will
continue to execute. Otherwise, they will be automatically terminated when main() finishes.
Example
This simple example code creates 5 threads with the pthread_create() routine. Each thread prints a
"Hello World!" message, and then terminates with a call to pthread_exit().
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid;
tid = (long)threadid;
cout << "Hello World! Thread ID, " << tid << endl;
pthread_exit(NULL);
}
int main () {
pthread_t threads[NUM_THREADS];
int rc;
int i;
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
struct thread_data {
int thread_id;
char *message;
};
pthread_exit(NULL);
}
int main () {
pthread_t threads[NUM_THREADS];
struct thread_data td[NUM_THREADS];
int rc;
int i;
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
When the above code is compiled and executed, it produces the following result −
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
There are following two routines which we can use to join or detach threads −
pthread_join (threadid, status)
pthread_detach (threadid)
The pthread_join() subroutine blocks the calling thread until the specified 'threadid' thread terminates.
When a thread is created, one of its attributes defines whether it is joinable or detached. Only threads
that are created as joinable can be joined. If a thread is created as detached, it can never be joined.
This example demonstrates how to wait for thread completions by using the Pthread join routine.
#include <iostream>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
using namespace std;
#define NUM_THREADS 5
void *wait(void *t) {
int i;
long tid;
tid = (long)t;
sleep(1);
cout << "Sleeping in thread " << endl;
cout << "Thread with id : " << tid << " ...exiting " << endl;
pthread_exit(NULL);
}
int main () {
int rc;
int i;
pthread_t threads[NUM_THREADS];
pthread_attr_t attr;
void *status;
if (rc) {
cout << "Error:unable to join," << rc << endl;
exit(-1);
}
cout << "Main: completed thread id :" << i ;
cout << " exiting with status :" << status << endl;
}
**********