Escolar Documentos
Profissional Documentos
Cultura Documentos
Tutorial in C++
-Sanchit Karve
born2c0de@hotmail.com
.INDEX.
INTRODUCTION TO OOP:
I. Introduction
II. Constructors
II.A |--->Constructors with Arguments
II.B |--->Default Argument Constructors
III. Destructors
IV. The Complex Class
V. Overloading Operators.I
VI. The Copy Constructor
VII. Overloading Operators.II
VIII. The this Pointer
IX. Declaring Data Members Outside the Class
X. The sizeof() Operator
XI. Structures v/s Classes
CLASSES IN DETAIL:
I. Static Data Members INTRODUCTION TO OOP
I. INTRODUCTION
Before I begin, make sure that you at least have a working knowledge of
non-object oriented C++ Programming. If you know object oriented programming in
another language it will be an advantage. Object Oriented Programming is a
rather difficult subject and it requires a new way of thinking to write
programs. So if you don't understand some parts as you read don't worry.....read
it once again till you understand a bit more and then continue reading. As you
keep going ahead concepts will automatically be clear to you.
In Non Object oriented programming problems are based on the algorithimic
point of view. In object oriented programming we have to think from a design
perspective. Non OOP focuses on the algorithm while OOP focuses on data. This
maybe slightly hard to understand as of now but as we continue you will have a
better understanding of object oriented programming.
Let us by this example see how classes function:
#include <iostream.h>
class sample
{
private:
int data;
public:
void fun1()
{
data=15;
}
void fun2()
{
cout<<data;
}
};
void main()
{
sample a;
sample b;
a.fun1();
a.fun2();
b.fun2();
}
#include <iostream.h>
#include <string.h>
class student
{
private:
char name[20];
char grade;
public:
void setname()
{
strcpy(name,"Sanchit");
grade='F';
}
void display()
{
cout<<"Name = "<<name<<endl
<<"Grade = "<<grade<<endl;
}
};
struct access
{
char name[20];
char grade;
};
void main()
{
student s;
s.setname();
s.display();
struct access *a=(access*)&s;
strcpy(a->name,"Sanchit Karve");
a->grade='A';
s.display();
}
II. CONSTRUCTORS
Usually we need to set default values to variables in our classes. For example
if a class uses integers it would be prudent to set the default value of the
integer to 0 before any operation is done on them. The Same way we used the
setname() function to set the values for the data members. Hence the setname()
function has to be called immediately after the object has been created. In Huge
programs where many objects are created we would have to call the initialising
function everytime for each object to set it's default values. This can get a
bit frustrating.
So the Developers of C++ included a feature in object oriented programming known
as constructors. These functions contain all the initialisation code and is
automatically called everytime an object is created. A constructor has the same
name as the class so that it can be easily distinguished from other functions.
Consider this program which implements contructors:
#include <iostream.h>
#include <string.h>
class student
{
private:
char name[20];
char grade;
public:
student()
{
strcpy(name,"Sanchit Karve");
grade='A';
}
void display()
{
cout<<"Name = "<<name<<endl
<<"Grade = "<<grade<<endl;
}
};
void main()
{
student a;
a.display();
}
Here as you saw we created an object as well as initialised it's variables in
just one statement. Here the constructor is immediately called when a student
object 'a' is created and it sets 'name[]' and 'grade' to "Sanchit Karve" and 'A
'
respectively. You will agree that using constructors is a lot more better way
than using another function call to initialise data members.
Here you will agree that using cout<<a is easier to read and understand than the
other a.display().Here the << operator is overloaded ; the compiler interprets
the cout statement like this:
cout << ( operator <<(cout,a) );
Here if you notice, the real and imag data types are private data members of the
complex class but they are being accessed in the overloaded operator function.
Shouldn't the compiler complain? No, because the declaration of the overloaded
operator has been done inside the class and it has been preceded by a "friend"
keyword. Whenever a friend keyword is used it means that the specified function
( or class...as we see later ) can access the private members of the class it is
declared in. Hence the overloaded operator function is a friend of the complex
class ie. it can access all the private data members of the complex class.
Actually if you see, this violates the rules and concepts of Object Oriented
Programming. The Rules clearly state that an independant class has it's own
private data members and they should not be be accessible to any other class
except to those classes which are inherited from the base class.You will be able
to understand this a lot better when you reach the later half of this tutorial
in PART 2.
VI. THE COPY CONSTRUCTOR
Previously we saw a two step assignment technique which looked like this:
complex c;
c = a + b;
Where,
a and b are complex objects.
This used the assignment overloaded operator function.But sometimes we might
want to set a value to an object as soon as it is instantiated. It means that
our two step assignment technique becomes a one step process likt this:
complex c = a + b;
This can be made possible by using a special constructor known as the copy
constructor.Let us see how this can be done.
#include <iostream.h>
#include <conio.h>
class complex
{
private:
float real;
float imag;
public:
complex(float r=0.0f,float i=0.0f)
{
real=r;
imag=i;
}
complex(complex &c)
{
real = c.real;
imag = c.imag;
}
complex operator +(complex c)
{
complex t;
t.real = real + c.real;
t.imag = imag + c.imag;
return t;
}
// Other Functions
friend ostream& operator <<(ostream& s,complex c);
};
ostream& operator <<(ostream &s,complex c)
{
s<<"Real = "<<c.real<<endl
<<"Imaginary = "<<c.imag<<endl;
return s;
}
void main()
{
complex a(1.0f,2.0f),b(3.0,4.0);
complex c = a + b;
cout<<c;
}
A Copy Constructor is a constructor which takes another object of it's own class
as a parameter. Hence in the statement complex c = a + b; The compiler
interprets it like this:
complex &c(a.operator+(b));
All this time whenever i showed the interpretation of the compiler in overloaded
functions I forgot to mention that you can even use statements like that in our
programs. So in this example instead of "complex c=a+b;" we can use this as well
:
complex &c(a.operator+(b));
Be very careful while writing a copy constructor. You might notice that the
parameters received are passed by reference.The reason is quite simple. If an
object is passed by value another object would have to be created.The Copy
Constructor would then be called again, which means that the program would be
stuck in an infinite loop.But nowadays most compilers detect when a copy
constructor argument is passed by value and report it. But still we must be
careful since a lot of free compilers still don't report such errors.
VII. OVERLOADING OPERATORS.II
Earlier we saw how we can overload binary operators such as +,-,*,/ etc. Now if
we just wanted to increment or decrement an object we can overload that as well.
There is however a slight difference. Overloading unary operators are of two
types namely prefix and postfix operations. The prefix method increments/
decrements the object before the expression is evaluated while the postfix
method is quite just the opposite. Let's see how each one is done.Here's the
prefix method first:
#include <iostream.h>
class sample
{
private:
int data;
public:
sample()
{
data=0;
}
void display()
{
cout<<"data="<<data<<endl;
}
void operator++()
{
data++;
}
};
void main()
{
sample a;
a.display();
++a;
++a;
++a;
a.display();
}
The output here is:
data=0
data=3
Here if you see the syntax for overloading the prefix operator is the same like
overloading any other operator. You can use the prefix operator in places where
the multiplication operator ( or any other operator ) is overloaded. In such a
case the increment is done before the operation is completed. Now comes the
question of overloading the post-increment operator. The syntax for overloading
a post-increment operator is a bit different and is as follows:
void operator++(int)
{
data++;
}
The int here has got no special significance here. It is just for the compiler
to know that we are overloading the postfix operator. the postfix operator can
be used in the same way as we did in this example.
VIII. THE this POINTER
Remember the Complex Class Program we wrote earlier? It's multiplication
operator overloaded function was like this:
complex operator *(complex c)
{
complex tmp;
tmp.real = ( real * c.real ) - ( imag * c.imag );
tmp.imag = ( real * c.imag ) - ( imag * c.real );
return tmp;
}
Now while just quickly reading we may wonder what data type real is. Of course
it seems obvious here but while developing large applications, such confusions
are common. So to avoid such a confusion we have a "this" pointer that points to
itself. So let us by using the this pointer make a change in the previous
overloaded function.
complex operator *(complex c)
{
complex tmp;
tmp.real = ( this->real * c.real ) - ( this->imag * c.imag );
tmp.imag = ( this->real * c.imag ) - ( this->imag * c.real );
return tmp;
}
Isn't it a lot more readable now? this->real stands for the real data type
present in the current class. The this pointer can also be used for assignment
and displaying data. Have a look at this program to see how this is done:
#include <iostream.h>
class sample
{
private:
int data;
public:
sample()
{
data=0;
}
void display()
{
cout<<"data="<<data<<endl;
cout<<"this->data="<<data<<endl; // Another way of outputting data
cout<<"object's address="<<this<<endl; // getting address of object
}
void set(int item)
{
this->data=item; // Another way of assigning data
cout<<"object's address="<<this<<endl; // Getting address of object
}
};
void main()
{
sample a;
a.set(3);
a.display();
}
The Result on my PC was:
object's address=0x0012ff88
data=3
this->data=3
object's address=0x0012ff88
The object's address may vary from computer to computer. The Program along with
the comments seems self-explanatory.
The next example shall however show the real use of the this pointer in day to
day applications. Here is the Program:
#include <iostream.h>
class circle
{
private:
int radius;
float x,y;
public:
circle()
{
}
circle(int r,float x1,float y1)
{
radius=r;
x=x1;
y=y1;
}
circle operator=(circle c)
{
cout<<"*Assignment operator invoked*"<<endl;
radius=c.radius;
x=c.x;
y=c.y;
return circle(radius,x,y);
}
void display()
{
cout<<"Radius="<<radius<<endl
<<"X-Coordinate="<<x<<endl
<<"Y-Coordinate="<<y<<endl<<endl;
}
};
void main()
{
circle c1(20,5.5,5.5),c2,c3;
c3=c2=c1;
c1.display();
c2.display();
c3.display();
}
Most of the program is straightforward. The important thing here is that the
operator = is overloaded. This overloaded dunction is called when the statement
c3=c2=c1 gets executed. The function does all the copying of the member data
from one object to the other.It returns a value by creating a temporary circle
object and initialising it using the three argument constructor. The value
returned is a copy of the object of which the overloaded operator function is a
member. Returning a value makes it possible to chain = operators such as in like
c3=c2=c1. But returning by value creates an extra copy of the object leading to
wastage of space. When an object is returned by reference no new object is
created. Hence we can return the object by reference using the this pointer as
we now return a reference of the current object itself. Now the Assignment
operator function can be modified as follows:
circle operator=(circle c)
{
cout<<"*Assignment operator invoked*"<<endl;
radius=c.radius;
x=c.x;
y=c.y;
return *this;
}
Since this is a pointer to the object of which the above function is a member,
*this is that object itself. So the statement return *this returns this object
by reference.
IX. DECLARING DATA MEMBERS OUTSIDE THE CLASS
All this time, we were writing all our class functions within the class body
itself. Though this is acceptable, it is not practical in the longer run as it
affects readability when there are too many functions that make the class large.
So from now onwards, we will follow a new approach to writing classes. It's
simple, we just place the function prototypes in the class body and write the
function body outside the class. This way we can find out exactly how many
functions exist in a class and the class looks more neat. Even adding new
functions to the class gets simpler. Look at this example:
#include <iostream.h>
class sample
{
private:
int data;
public:
sample();
void display();
void func1();
~sample();
};
sample::sample()
{
cout<<"In Constructor\n";
}
void sample::display()
{
cout<<"In display()\n";
func1();
}
void sample::func1()
{
cout<<"In func1()\n";
}
sample::~sample()
{
cout<<"In Destructor\n";
}
void main()
{
sample s;
s.display();
}
The Class Body half of this program is simple. It just contains the function
prototypes of the functions that form a part of the class. What is important
here, is the function declaration outiside the class. Let us see the display
function declaration ie. void sample::display()
The '::' as most of you all know is the access modifier. Here it has to be
interpreted like this:
-> Function display of class sample that doesnt return a value ie. void
Same case here with constructors and destructors. Since neither of the two
return any values, only the latter half of the function declaration is used.
As you can see, in larger classes this method improves readability remarkably.
So as I said earlier, we will be using this method of writing classes from now
on.
X. The sizeof() Operator
Many Teachers blindly tell you that an integer occupies 2 bytes in memory. It's
time for you to know the truth. Not that they are wrong, but it's like they WERE
right, at some point of time. In 16-bit Processors, integers take up 2 bytes.
But in 32-bit processors, integers take up 4 solid bytes. Let's look at a
stronger evidence.
The sizeof() operator is used to find out how many bytes a particular data type,
class or structure takes up in memory. This is especially useful in binary File
Handling and for memory allocation where the size of the record or the data type
is not known. Meanwhile, study this example:
#include <iostream.h>
class fun1
{
};
class fun2
{
int i;
char c;
void function();
};
void main()
{
int intv;
cout<<sizeof(fun2)<<" bytes is size of class fun2\n"
<<sizeof(fun1)<<" bytes is size of class fun1\n"
<<sizeof(intv)<<" bytes is size of Integer\n\n"
<<sizeof(__int8)<<" bytes for 8-bit Integers\n"
<<sizeof(__int16)<<" bytes for 16-bit Integers\n"
<<sizeof(__int64)<<" bytes for 32-bit Integers\n"
<<sizeof(__int64)<<" bytes for 64-bit Integers\n";
}
Output:
5 bytes is size of class fun2
1 bytes is size of class fun1
4 bytes is size of Integer
1 bytes for 8-bit Integers
2 bytes for 16-bit Integers
8 bytes for 32-bit Integers
8 bytes for 64-bit Integers
Here we have to remember that the size of a class is determined only by it's
data members and not it's functions as functions are usually shared among each
class. Let us see how class fun2 occupies it's space:
1) class fun2: 5 Bytes
(char) + (int)
1 + 4
= 5
Notice the last half of output which shows the integer size for 8,16,32 and 64
bit respectively. Now you may have realised that the class fun1 has no data
members yet it's size is 1 and not 0. This is because if a class occupies 0
Bytes it is said to be non-existent. Hence the size of an empty class must be
the smallest positive non-zero number. Since 1 is the smallest positive non-zero
number each empty class occupies 1 Byte in memory.
XI. Structures v/s Classes
Basically, the purpose of both structures and classes is to group similar data
into one entity. Structures were designed for C while classes for C++.
In Theory, both can be used interchangeably. The only difference is that by
default, data members in structures are public while that in classes are private
.
Note that Structures in C cannot be extended like structures in C++. A Structure
in C cannot contain functions like that in C++ can.
Programmers generally use structures to group data together and classes to group
data and functions together.
CLASSES IN DETAIL
I. Static Data Members
We all know that each object has it's own copy of it's data members while the
member functions are shared among each object. However, when a data member is
declared as static only one copy of that data type is created for all objects
irrespective of the number of objects. It remains in scope till the end of the
program, which makes it like a usual static variable. Though data members are
used for operations on functions, static data members are meant for operations
on objects themselves. Let us see this example to clarify this point.
#include <iostream.h>
class sample
{
private:
int data;
static int objcount; // Declaration of objcount
public:
sample()
{
objcount++;
data++;
}
void display()
{
cout<<"data = "<<data<<endl
<<"objcount = "<<objcount<<endl<<endl;
}
};
int sample::objcount=0; // Definition of objcount
void main()
{
sample a,b,c;
a.display();
b.display();
c.display();
}
OUTPUT:
data = 2
objcount = 3
data = 4261613
objcount = 3
data = 4237585
objcount = 3
As you can see, as soon as an object is created the constructor increments both
the variables out of which objcount is static. Since 3 objects have been created
objcount holds the current number of objects of it's class. However the values
of data in all three cases are different ( 2, 4261613, 4237585 ) as each object
has it's own copy of the data variable. Since data has not been initialised it's
values are all junk.
Static data members require an unusual format. Ordinary Variables like data are
declared and defined in the same statemen. static data members like objcount,
requires two seperate statements. The variable's declaration is present within
it's class but it is defined outside the class.
The memory space for static data is allocated only once before the program
starts executing. A point to note: If you include the declaration of a static
variable but forget to write it's definition, the compiler would pass it but
the linker would complain that you're trying to reference an undeclared external
variable. This even happens if you include the definition but forget the class
name ie. the sample:: in the program above.
Lastly, the most important question: When would we require to use static member
data? Imagine a situation where an object is required to know how many objects
of it's class are in existence. In that case a similar program like the one
above can be used with the static member objcount that would be shared by all
objects.