CS6301 – Programming and Data Structures – II
III Semester – IT
UNIT – II Object Oriented Programming Concepts
String Handling – Copy Constructor - Polymorphism – compile time and run time polymorphisms – function overloading – operators overloading – dynamic memory allocation - Nested classes - Inheritance – virtual functions.
String Handling
String is defined as sequence of charecters.C++ provides following two types of string representations:
- The C-style character string.
- The string class type introduced with Standard C++.
The C-Style Character String:
The C-style character string originated within the C language and continues to be supported within C++. This string is actually a one-dimensional array of characters which is terminated by a null character '\0'. Thus a null-terminated string contains the characters that comprise the string followed by a null.
The following declaration and initialization create a string consisting of the word "Hello". To hold the null character at the end of the array, the size of the character array containing the string is one more than the number of characters in the word "Hello."
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
If you follow the rule of array initialization, then you can write the above statement as follows:
char greeting[] = "Hello";
Following is the memory presentation of above defined string in C/C++:
Actually, you do not place the null character at the end of a string constant. The C++ compiler automatically places the '\0' at the end of the string when it initializes the array. Let us try to print above-mentioned string:
#include <iostream>
using namespace std;
int main ()
{
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
cout < "Greeting message: ";
cout < greeting < endl;
return 0;
}
When the above code is compiled and executed, it produces result something as follows:
Greeting message: Hello
C++ supports a wide range of functions that manipulate null-terminated strings:
S.No / Function & Purpose1 / strcpy(s1, s2);
Copies string s2 into string s1.
2 / strcat(s1, s2);
Concatenates string s2 onto the end of string s1.
3 / strlen(s1);
Returns the length of string s1.
4 / strcmp(s1, s2);
Returns 0 if s1 and s2 are the same; less than 0 if s1<s2; greater than 0 if s1>s2.
5 / strchr(s1, ch);
Returns a pointer to the first occurrence of character ch in string s1.
6 / strstr(s1, s2);
Returns a pointer to the first occurrence of string s2 in string s1.
Following example makes use of few of the above-mentioned functions:
#include <iostream>
#include <cstring>
using namespace std;
int main ()
{
char str1[10] = "Hello";
char str2[10] = "World";
char str3[10];
int len ;
// copy str1 into str3
strcpy( str3, str1);
cout < "strcpy( str3, str1) : " < str3 < endl;
// concatenates str1 and str2
strcat( str1, str2);
cout < "strcat( str1, str2): " < str1 < endl;
// total lenghth of str1 after concatenation
len = strlen(str1);
cout < "strlen(str1) : " < len < endl;
return 0;
}
When the above code is compiled and executed, it produces result something as follows:
strcpy( str3, str1) : Hello
strcat( str1, str2): HelloWorld
strlen(str1) : 10
The String Class in C++:
The standard C++ library provides a string class type that supports all the operations mentioned above, additionally much more functionality. We will study this class in C++ Standard Library but for now let us check following example:
At this point, you may not understand this example because so far we have not discussed Classes and Objects. So can have a look and proceed until you have understanding on Object Oriented Concepts.
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "Hello";
string str2 = "World";
string str3;
int len ;
// copy str1 into str3
str3 = str1;
cout < "str3 : " < str3 < endl;
// concatenates str1 and str2
str3 = str1 + str2;
cout < "str1 + str2 : " < str3 < endl;
// total lenghth of str3 after concatenation
len = str3.size();
cout < "str3.size() : " < len < endl;
return 0;
}
When the above code is compiled and executed, it produces result something as follows:
str3 : Hello
str1 + str2 : HelloWorld
str3.size() : 10
Copy Constructor
In this Constructor we pass the object of class into the Another Object of Same Class. As name Suggests you Copy, means Copy the values of one Object into the another Object of Class .This is used for Copying the values of class object into an another object of class So we call them as Copy Constructor and For Copying the values We have to pass the name of object whose values we wants to Copying and When we are using or passing an Object to a Constructor then we must have to use the & Ampersand or Address Operator.
Normally the compiler automatically creates a copy constructor for each class (known as a default copy constructor) but for special cases the programmer creates the copy constructor, known as a user-defined copy constructor. In such cases, the compiler does not create one.
A user-defined copy constructor is generally needed when an object owns pointers or non-shareable references, such as to a file, in which case a destructor and an assignment operator should also be written (see Rule of three).
Definition
Copying of objects is achieved by the use of a copy constructor and a assignment operator. A copy constructor has as its first parameter a (possibly const or volatile) reference to its own class type. It can have more arguments, but the rest must have default values associated with them.
Syntax:
Class::Class(const Class&);
The following would be valid copy constructors for class X:
X(const X& copyFromMe);
X(X& copyFromMe);
X(const volatile X& copyFromMe);
X(volatile X& copyFromMe);
X(const X& copyFromMe, int = 10);
X(const X& copyFromMe, double = 1.0, int = 40);
The first one should be used unless there is a good reason to use one of the others. One of the differences between the first and the second is that temporaries can be copied with the first. For example:
X a = X(); // valid if you have X(const X& copyFromMe) but not valid if you have X(X& copyFromMe)
// because the second wants a non-const X&
// to create a, the compiler first creates a temporary by invoking the default constructor
// of X, then uses the copy constructor to initialize a as a copy of that temporary.
// However, for some compilers both the first and the second actually work.
Another difference between them is the obvious:
X const a;
X b = a; // valid if you have X(X const& copyFromMe) but not valid if you have X(X& copyFromMe)
// because the second wants a non-const X&
The X& form of the copy constructor is used when it is necessary to modify the copied object. This is very rare but it can be seen used in the standard library's std::auto_ptr. A reference must be provided:
X a;
X b = a; // valid if any of the copy constructors is defined
// since you're passing in a reference
The following are invalid copy constructors (Reason - copyFromMe is not passed as reference):
X(X copyFromMe);
X(const X copyFromMe);
because the call to those constructors would require a copy as well, which would result in an infinitely recursive call.
The following cases may result in a call to a copy constructor:
- When an object is returned by value
- When an object is passed (to a function) by value as an argument
- When an object is thrown
- When an object is caught
- When an object is placed in a brace-enclosed initializer list
These cases are collectively called copy-initialization and are equivalent to:[2] T x = a;
It is however, not guaranteed that a copy constructor will be called in these cases, because the C++ Standard allows the compiler to optimize the copy away in certain cases, one example being the return value optimization (sometimes referred to as RVO).
Explicit assignment in an expression
Object A;
Object B;
A = B; // translates as Object::operator=(const Object&), thus A.operator=(B) is called
// (invoke simple copy, not copy constructor!)
Initialization
An object can be initialized by any one of the following ways.
a. Through declaration
Object B = A; // translates as Object::Object(const Object&) (invoke copy constructor)
b. Through function arguments type function (Object a);
c. Through function return value
Object a = function();
The copy constructor is used only for initializations, and does not apply to assignments where the assignment operator is used instead.
The implicit copy constructor of a class calls base copy constructors and copies its members by means appropriate to their type. If it is a class type, the copy constructor is called. If it is a scalar type, the built-in assignment operator is used. Finally, if it is an array, each element is copied in the manner appropriate to its type.[3]
By using a user-defined copy constructor the programmer can define the behavior to be performed when an object is copied.
Implicit copy constructor
Let us consider the following example.
//copy constructor
#include <iostream>
class Person
{
public:
int age;
explicit Person(int age)
: age(age) {}
};
int main()
{
Person timmy(10);
Person sally(15);
Person timmy_clone = timmy;
std::cout < timmy.age < " " < sally.age < " " < timmy_clone.age < std::endl;
timmy.age = 23;
std::cout < timmy.age < " " < sally.age < " " < timmy_clone.age < std::endl;
return 0;
}
Output
10 15 10
23 15 10
As expected, timmy has been copied to the new object, timmy_clone. While timmy's age was changed, timmy_clone's age remained the same. This is because they are totally different objects.
The compiler has generated a copy constructor for us, and it could be written like this:
Person(const Person& copy)
: age(copy.age) {}
So, when do we really need a user-defined copy constructor? The next section will explore that question.
User-defined copy constructor
Now, consider a very simple dynamic array class like the following:
#include <iostream>
class Array
{
public:
int size;
int* data;
explicit Array(int size)
: size(size), data(new int[size]) {}
~Array()
{
delete[] data;
}
};
int main()
{
Array first(20);
first.data[0] = 25;
{
Array copy = first;
std::cout < first.data[0] < " " < copy.data[0] < std::endl;
} // (1)
first.data[0] = 10; // (2)
return 0;
}
Output
25 25
Segmentation fault
For example to invoke a copy constructor the programmer writes:
Exforsys e3(e2);
or
Exforsys e3=e2;
Both the above formats can be sued to invoke a copy constructor
Example
#include <iostream.h>
class Exforsys()
{
private:
int a;
public:
Exforsys()
{ }
Exforsys(int w)
{
a=w;
}
Exforsys(Exforsys& e)
{
a=e.a;
cout<” Example of Copy Constructor”;
}
void result()
{
cout< a;
}
};
void main()
{
Exforsys e1(50);
Exforsys e3(e1);
cout< “\ne3=”;e3.result();
}
In the above the copy constructor takes one argument an object of type Exforsys which is passed by reference. The output of the above program is
Example of Copy Constructor
e3=50
Example:
Class rectangle {
private:
float height;
float width;
int xpos;
int ypos;
public:
rectangle(float, float); // constructor
rectangle(const rectangle&); // copy constructor
void draw(); // draw member function
void posn(int, int); // position member function
void move(int, int); // move member function
};
rectangle::rectangle(const rectangle& old_rc)
{
height = old_rc.height;
width = old_rc.width;
xpos = old_rc.xpos;
ypos = old_rc.ypos;
}
void main()
{
rectangle rc1(3.0, 2.0); // use constructor
rectangle rc2(rc1); // use copy constructor
rectangle rc3 = rc1; // alternative syntax for
}
Difference between copy constructor and constructor
- Constructor is called when an object is created.Copy constructor is called when the copy of an object is made. For e.g. passing parameter to function by value function returning by value.
- Copy constructor takes the parameter as const reference to the object.
- A copy constructor is called whenever an object is passed by value, returned by value or explicitly copied. statements;
Polymorphism
- The word polymorphism means having many forms. Typically, polymorphism occurs when there is a hierarchy of classes and they are related by inheritance.
- C++ polymorphism means that a call to a member function will cause a different function to be executed depending on the type of object that invokes the function
Consider the following example where a base class has been derived by other two classes:
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout < "Parent class area :" <endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout < "Rectangle class area :" <endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout < "Triangle class area :" <endl;
return (width * height / 2);
}
};
// Main function for the program
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// store the address of Rectangle
shape = &rec;
// call rectangle area.
shape->area();
// store the address of Triangle
shape = &tri;
// call triangle area.
shape->area();
return 0;
}
When the above code is compiled and executed, it produces the following result:
Parent class area
Parent class area
The reason for the incorrect output is that the call of the function area() is being set once by the compiler as the version defined in the base class. This is called static resolution of the function call, or static linkage - the function call is fixed before the program is executed. This is also sometimes called early binding because the area() function is set during the compilation of the program.
But now, let's make a slight modification in our program and precede the declaration of area() in the Shape class with the keyword virtual so that it looks like this:
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout < "Parent class area :" <endl;
return 0;
}
};
After this slight modification, when the previous example code is compiled and executed, it produces the following result:
Rectangle class area
Triangle class area
This time, the compiler looks at the contents of the pointer instead of it's type. Hence, since addresses of objects of tri and rec classes are stored in *shape the respective area() function is called.
As you can see, each of the child classes has a separate implementation for the function area(). This is how polymorphism is generally used. You have different classes with a function of the same name, and even the same parameters, but with different implementations.
Compile time Polymorphism:
C++ support polymorphism. One function multiple purpose, or in short many functions having same name but with different function body.
For every function call compiler binds the call to one function definition at compile time. This decision of binding among several functions is taken by considering formal arguments of the function, their data type and their sequence.
Example of compile time polymorphism:
Example 1: example of compile time polymorphism; static time binding
void f(int i){cout<"int";}
void f(char c){cout<"char";}
int main()
{
f(10);
return 0;
}
Output: int
Run time polymorphism:
C++ allows binding to be delayed till run time. When you have a function with same name, equal number of arguments and same data type in same sequence in base class as well derived class and a function call of form: base_class_type_ptr->member_function(args); will always call base class member function. The keyword virtual on a member function in base class indicates to the compiler to delay the binding till run time.
Every class with atleast one virtual function has a vtable that helps in binding at run time. Looking at the content of base class type pointer it will correctly call the member function of one of possible derived / base class member function.
Example of run time polymorphism:
class Base
{
public:
virtual void display(int i)
{ cout<"Base::"<i; }
};
class Derv: public Base
{
public:
void display(int j)
{ cout<"Derv::"<j; }
};
int main()
{
Base *ptr=new Derv;
ptr->display(10);
return 0;
}
Output: Derv::10
“Overriding is the example of run-time polymorphism”
“Overloading is the example of compile-time polymorphism.”
Virtual table, vtable, virtual function table, dynamic dispatch table:
vtable is an implementation of compiler that helps in resolving a virtual function call, run time binding. Before going ahead, its important to clearly understand inheritance and virtual functions in C++.
Consider a base class Cat and its subclasses: Tiger, Cheetah, PetCat.
class Cat
{
public:
virtual void roar()
{cout<"i m cat";
};
};
class Tiger: public Cat
{
public:
/* roar() is not implemented */
};
class Cheetah: public Cat
{
public:
void roar()
{cout<"kuuuraahhh";
}
};
class PetCat: public Cat
{
public:
void roar()
{cout<"mewwwwah";
}
};
Now a virtual function roar() is implemented in each of these classes.
Also a base class "Cat" type pointer can point to any object of its derived class and at run time using the type of the object, virtual function will be executed.
int main()
{
Cat *cat;
PetCat pc;
cat=&pc;
cat->roar(); // calls PetCat::roar();
Tiger ti;
cat = &ti;
cat->roar(); // calls Cat::roar();
}
To achieve binding at run time, vtable is created by compiler at static time.
A vtable is created for every class that has atleast one virtual function. Note that a virtual function in a base class is also virtual in derived class. So vtable is created for all 4 classes above.
vtable of a class will have function pointers for each virtual function that can be called by object of that class, pointing to a function of some class which is most derived from it. Compiler would normally implement a hidden virtual pointer, vptr, and will initialize to vtable in the constructor.
Now if pointer of base class type Cat is pointing to object of type Tiger, then for a call like catPtr->roar(); will cause to refer vtable of Tiger.
If class Tiger overriddes roar(), then it is the function of the most derived class that can be called and is set in Tiger's vtable. So roar() of Tiger is called. If roar() is not overridden in Tiger, then roar() of Cat is the most derived class function that can be called and is set in Tiger's vtable. So roar() of Cat is called.
Virtual Function:
- A virtual function is a function in a base class that is declared using the keyword virtual. Defining in a base class a virtual function, with another version in a derived class, signals to the compiler that we don't want static linkage for this function.
- What we do want is the selection of the function to be called at any given point in the program to be based on the kind of object for which it is called. This sort of operation is referred to as dynamic linkage, or late binding.
Pure Virtual Functions: