Lab Sheet 4
Additional Components of a Class
1. Constructor
A constructor is basically a member function of a class used for startup tasks such as initialization of data members, allocation of memory, and so on. It is convenient if an object can initialize itself when it is first created, without the need to make a separate call to a member function. This member function has the same name as its class. A constructor is automatically called when an object is created either statically or dynamically using the new operator. A constructor does not return a value, so it does not have a return type. Since the constructor is called automatically at the object creation, there is no reason for it to return anything. The compiler knows that they are constructors by looking at their names. Let us see an example in which an object of the Date class is created and is initialized with a specific value each time.
class Date
{
private:
int dd, yy;
char mm[4];
public:
Date() //default constructor
{
dd=27;
strcpy(mm,"Jun");
yy=2026;
}
void showDate() //display
{
cout<<dd<<" "<<mm<<" "<<yy<<endl;
}
};
int main()
{
Date d1; //constructor invoked
d1.showDate();
return 0;
}
Here, when the object d1 is being created, the constructor will be invoked, and the data members dd, mm, and yy are initialized. Note that the constructor here does not take any arguments. So, it is called a default constructor or non-parameterized constructor.
Data members of an object can be initialized by passing arguments to the constructor when the object is created. The constructor that takes arguments is called a parameterized constructor. The following example adds a parameterized constructor in the above example program.
class Date
{
//...
public:
//...
Date(int d, const char m[], int y) //parameterized constructor
{
dd=d;
strcpy(mm,m);
yy=y;
}
//....
};
int main()
{
Date d1; //default constructor invoked
Date d2(10,"Jul",2020); //parameterized constructor invoked
d1.showDate();
d2.showDate();
return 0;
}
2. Copy Constructor
We already know that no argument constructor can initialize data members to some specific values, and a parameterized constructor can initialize data members to values passed as arguments. There is also another way to initialize an object with another object of the same type. It is called the copy constructor. It is a one-argument constructor whose argument is an object of the same class (actually a reference to an object of the same type to avoid infinite recursion). The Date class in the above example can also be used as follows.
int main()
{
Date d1(16,"Sep",2015);
Date d2(d1); //copy constructor called
Date d3=d1; //another way of invoking copy constructor
d1.showDate();
d2.showDate();
d3.showDate();
return 0;
}
The compiler implicitly generates a default copy constructor which copies all the members of the objects one by one to another object. We can override the default copy constructor by making our own copy constructor as follows.
class Date
{
//...
public:
//...
Date(Date &dt) //copy constructor
{
dd=dt.dd;
strcpy(mm,dt.mm);
yy=dt.yy;
cout<<"Copy constructor called"<<endl;
}
//....
};
If the argument to the copy constructor is a constant reference, it will eventually prevent changes to the argument.
Date(const Date &dt) //copy constructor with const reference
{
//....
}
3. Destructor
Constructors serve to automatically initialize data members of an object at the point of creation. Destructors are complementary to constructors. They serve to clean up or do the final finishing task of objects when they are destroyed.
A destructor may be called either when the object goes out of scope or when it is destroyed explicitly using the delete operator. A destructor, like a constructor, has the same name as that of the class but is prefixed by the tilde ('~') symbol. A class cannot have more than one destructor. A destructor can't take arguments or specify a return value. The most common use of the destructor is to de-allocate memory that was allocated for the object by the constructor. A destructor for the above program can be created as:
class Date
{
//...
public:
//...
~Date() //destructor
{
cout<<"Destructor called"<<endl;
}
//....
};
The destructors are called in the reverse order of the constructor call.
4. Dynamic Memory Allocation
Sometimes we don't know the exact number of elements for an array at compile time. In that case, we need to ask the user for the number of elements, or the programming context determines the required number of elements and allocates memory for that number dynamically. In C++, dynamic memory allocation at runtime is done by using the new operator, and deallocation is done by using the delete or delete[] operator.
The new operator allocates memory on the heap and returns a pointer to it. The delete operator releases the memory allocated by new for a single variable, while delete[] releases memory allocated for an array.
General syntax.
data_type * pointer_variable1;
data_type * pointer_variable2;
pointer_variable1 = new data_type; // allocate single variable
pointer_variable2 = new data_type[size]; // allocate array
To release the memory, the syntax is as follows.
delete pointer_var1; // deallocate single variable
delete[] pointer_var2; // deallocate array
It is important to release the dynamically allocated memory when it is no longer needed. If the memory is not released after its use, it causes a memory leak.
The new operator for a single variable must be used with delete, and new[] for an array must be used with delete[]. Mixing them causes undefined behavior.
For example.
int * iptr1;
int * iptr2;
iptr1 = new int; // allocate single variable
iptr2 = new int[size]; // allocate array
//...
delete iptr1; // deallocate single variable
delete[] iptr2; // deallocate array
Similarly, in some cases, the exact amount of memory needed by an object's member is not known at compile time. In such cases, memory can be allocated dynamically at runtime using the new operator inside the constructor and released using the delete or delete[] operator inside the destructor. The destructor is the ideal place to release this memory since it is called automatically when the object goes out of scope or is deleted.
The following example demonstrates dynamic memory allocation inside a constructor and deallocation inside a destructor.
For example.
class Student
{
private:
int roll;
char *name; // pointer to hold name dynamically
public:
Student(int r, const char *n) // parameterized constructor
{
roll = r;
name = new char[strlen(n) + 1]; // allocate memory for name one more for null character
strcpy(name, n); // copy the name
}
void showData() const
{
cout << "Roll: " << roll << endl;
cout << "Name: " << name << endl;
}
~Student() // destructor
{
cout << "Memory being released for: " << name << endl;
delete[] name; // release allocated memory
}
};
int main()
{
Student s1(1, "Ram Thapa");
Student s2(2, "Sita Sharma");
s1.showData();
s2.showData();
return 0; // destructor called automatically for s1 and s2
}
Here, strlen(n) + 1 is used to allocate enough memory for the string plus the null character '\0'. When main() ends, the destructor is called automatically for each object in the reverse order of their creation, first for s2, then for s1, and the dynamically allocated memory for name is released in each case.
5. Constant Member Function
A function is made a constant function by placing the keyword const after the function header before the function body. A member function that does not change the value of its object but acquires data from its object is an obvious candidate for a constant function. The syntax to declare a constant function is as follows.
class ClassName
{
//...
return_type func_name(argument list) const //const function
{
//Function body;
}
//...
}
For example.
class Date
{
//...
public:
//...
void showDate() const //constant function
{
cout<<dd<<" "<<mm<<" "<<yy<<endl;
}
//....
};
6. Constant Object
When an object is declared as constant, we can't modify it. This is useful when you want to ensure an object's state remains unchanged throughout its use. A constant object can only call its constant member functions because they are the only ones that guarantee not to modify its value.
General syntax:
const class_name object_name; //creation of constant object
For example.
int main()
{
Date d1; // non-const object
d1.showDate(); // OK — const fn callable on any object
const Date d2; // const object
d2.showDate(); // OK — only const fn allowed here
return 0;
}
If showDate() is not declared as a const function, then a call to showDate() as d2.showDate() generates an error.
7. Constant Reference Argument
When we don't want to modify the arguments passed to the function but do not want to create objects/variables for the parameters, the constant reference parameters are made. For example
return_type func_name(const int &a, float &b) //const ref argument
{
//function body;
//a cannot be changed here, but b can be.
//changing b changes the second argument that is passed
}
The const reference argument avoids copying large objects while still preventing modification.
8. Static Data Members
If a data item in a class is declared as static, then only one copy of that item is created for the entire class, no matter how many objects are created. All the objects share a common item of information. The static data must be defined separately outside the class, since they are not created during object creation. The static data members can be accessed directly through the class along with the objects. That means static data members can be accessed before the object is created.
General syntax:
class class_name
{
//......
static data_type variable_name; //declaration of static data member
//.....
};
data_type class_name::variable_name; //definition of static data member
Static members can also be initialized during definition as
data_type class_name::variable_name = value;
For example.
class BankAccount
{
private:
int accNo;
char holderName[30];
float balance;
public:
static float interestRate; // same rate applies to all accounts
//...
};
float BankAccount::interestRate=6.5;
int main()
{
cout<<"Interest Rate = "<<BankAccount::interestRate<<endl;
BankAccount::interestRate=5.5; //accessed through class
BankAccount bakt; // object created;
cout<<"Interest Rate = "<<bakt.interestRate<<endl;
//....
return 0;
}
9. Static Member Function
When the static data members are declared as private, they cannot be accessed outside the class. The static members can be accessed directly through the class. A static member function can have access to only other static members declared in the same class. The static member function can be called via the class using the class name.
General Syntax:
static return_type func_name (argument list); //declaration of static member function
//....
class_name::fun_name(argument passed) //static member function call
For Example:
class BankAccount
{
private:
int accNo;
char holderName[30];
float balance;
static float interestRate; // same rate applies to all accounts
public:
//...
void showAccount() //Non static function
{
cout << "Acc No : " << accNo << endl;
cout << "Holder : " << holderName << endl;
cout << "Balance : Rs." << balance << endl;
}
static void setInterest(float ir) //static function
{
interestRate = ir;
}
static void showInterest() //static function
{
cout << "Interest Rate : " << interestRate << "%" << endl;
}
};
float BankAccount::interestRate=6.5;
int main()
{
//BankAccount::interestRate=5.5; //error private
BankAccount::setInterest(5.5); //set private static data member through public static function via class name
BankAccount::showInterest(); //access private static data member through public static function via class name
//BankAccount::showAccount(); //non static functions cannot be accessed through class
BankAccount bakt; // object created;
bakt.showInterest(); //static function can be accessed through object also
bakt.showAccount(); //non-static functions can be accessed only through objects
//....
return 0;
}
Exercises
Write a class that can set Department ID and Department Name with constructors to initialize its members. Write a destructor member in the same class to display the message "Object n (Department ID) goes out of scope". Your program should be made such that it shows the order of the constructor and the destructor invocation.
Write a program that creates a class containing a dynamically allocated character array as its data member. Create two objects, one initialized with the string "Engineers are" and another with "Creators of logic". Implement a member function join() that accepts two objects as arguments, concatenates the strings in those objects, and stores the resulting string in the calling object's data member. Create a third object, call the function join(), and store the concatenated string in its data member. Implement another member function to display the stored string. Use constructors to dynamically allocate memory and initialize the character array. Also, implement a destructor to properly release the allocated memory.
Assume that one constructor initializes data members, such as vehicle_id, hour, and rate. A parking lot provides a 10% discount if the number of vehicles exceeds 10. Display the total parking charge for each car. Write your own copy constructor that performs a member-by-member copy and displays a message when it is called.
Write a program that has a class to represent time. Write a default constructor to initialize hour, minute, and second to 0, and a parameterized constructor to initialize them to values passed as arguments. Create constant member functions to show the value of the time object in 12-hour and 24-hour formats. Also, create constant and non-constant objects of the Time class to illustrate the following relationships and comment on whether they are correct or not, and explain why.
i) const_object.non_const_mem_function
ii) const_object.const_mem_function
iii) non_const_object.non_const_mem_function
iv) non_const_object.const_mem_function
Create a class with a data member to hold a "serial number" for each object created from the class. That is, the first object created will be numbered 1, the second 2, and so on, by using the static data member. Use a static member function to show the total number of objects created, and other member functions to show details of objects.
Write a class called Rectangle with private data members for length and width. Write a member function that accepts a Rectangle object as a constant reference argument, compares the object's area with the area of the rectangle passed as an argument, and returns true if the calling object has a larger area than the object passed as an argument, and false otherwise. Ensure the member function does not modify either object.