• Nem Talált Eredményt

III. fejezet - Object-oriented programming in C++

2. Classes and objects

2.1. From structures to classes

2.1.3. Data hiding

};

It can be seen at first glance, that the function IncreaseSalary() does not receive the class type variable (object) as a parameter, since it carries out an operation on the object by default. The main () function that demonstrates the usage of the objects of type Employee, since we only call now the member function corresponding to the

cout << engineer.salary << endl;

Employee *pAccountant = new Employee;

pAccountant->employeeID = 1235;

pAccountant->name = "Sarah Rich";

pAccountant->salary = 3e5;

pAccountant->IncreaseSalary(10);

cout << pAccountant->salary << endl;

delete pAccountant;

}

2.1.3. Data hiding

Accessing class type variables (objects) directly contradicts the principle of data hiding. In object-oriented programming it is required that the data members of classes could not be accessed directly from the outside. The type struct offers complete access to its members by default, whereas the class type completely hides its members from the outside, which complies much more with the principles of OOP. It should be noted that the access of class elements can be defined by programmers as well with the keywords private, protected and public.

public members can be accessed anywhere within the code where the object itself is accessible. On the contrary, private members can be accessed only from the the member functions of their class. (The access type protected will be applied in the case of inheritance treated in Chapter 3. szakasz - Inheritance (derivation)

Within a class, a member group of any number of elements can be created with the usage of the keywords (private, protected, public) and there is no restriction on the order of groups.

If we stick to the previous example, it is necessary to write further member functions with which we can set and get in a controlled way the value of data members because of their restricted access modes. The setting functions may also check that only valid data could appear in the object of type Employee. Getting functions are often defined as a constant, which means that the value of data members cannot be modified with that function. In a constant member function, the reserved word const is put between the header and the body of that function. In our example, GetSalary() is a constant member function.

class Employee{

void IncreaseSalary(float percent) { salary *= (1 + percent/100);

float GetSalary() const {

engineer.SetData(1234, "Tony Clever", 2e5);

engineer.IncreaseSalary(12);

cout << engineer.GetSalary() << endl;

Employee *pAccountant = new Employee;

pAccountant->SetData(1235, "Sarah Rich", 3e5);

pAccountant->IncreaseSalary(10);

cout << pAccountant->GetSalary() << endl;

delete pAccountant;

}

It should be noted that data members can be changed with the help of constant member functions, too, in case they are declared with the keyword mutable, for example:

mutable float salary;

However, these solutions are rarely used.

It should be noted that if all data members of a class have public access, the object can be initialised by the solution already presented in the case of structures, for example:

Employee doorman = {1122, "John Secure", 1e5};

Since the useability of the formula above will be later restricted by other constraints as well (it should not be a derived class, it cannot have virtual member functions), it is recommended that they be initialised by the special member functions of classes, namely by the so-called constructors.

2.1.4. Constructors

In program codes using classes, one of the most frequent operations is creating objects. Some objects are created by programmers themselves by static or dynamic memory allocation (see example above); however, there are cases when the compiler creates so-called temporary object instances. How can we assign the data members of objects to be created an initial value? By data members that are called constructors.

class Employee{

void IncreaseSalary(float percent) { salary *= (1 + percent/100);

}

void SetName(string n) {

employee.SetName("Stephen Smith");

Employee engineer(1234, "Tony Clever", 2e5);

engineer.IncreaseSalary(12);

cout << engineer.GetSalary() << endl;

Employee firstEngineer = engineer;

// or: Employee firstEngineer(engineer);

firstEngineer.IncreaseSalary(50);

cout << firstEngineer.GetSalary() << endl;

Employee *pEmployee = new Employee;

pEmployee->SetName("Stephen Smith");

delete pEmployee;

Employee *pAccountant;

pAccountant = new Employee(1235, "Sarah Rich", 3e5);

pAccountant->IncreaseSalary(10);

cout << pAccountant->GetSalary() << endl;

delete pAccountant;

Employee *pFirstEngineer=new Employee(engineer);

pFirstEngineer->IncreaseSalary(50);

cout << pFirstEngineer->GetSalary() << endl;

delete pFirstEngineer;

}

In the example above, we created a constructor without parameters, a constructor with parameters and a copy constructor, making use of function name overloading. Thus, a constructor is a member function the name of which corresponds to the name of the class and has no return type. The constructor of a class is called automatically by compilers every time an object of the given class is created. A constructor has no return value.

Except from that, it behaves like any other member function. With redefined (overloaded) constructors, an object can be initialised in many ways.

A constructor does not allocate memory space for the object to be created, it only has to initialise the memory space already allocated for it. However, if the object contains a pointer, then the constructor has to ensure that a memory space to which the pointer points is allocated.

A class has two constructors by default: a constructor without parameters (default) and a copy constructor. If we create a personalised constructor, then the default constructor is not available any more, so it has to be defined as well. We generally use our own copy constructor if a dynamic memory space is associated with the instances of a class.

Constructors with and without parameters are often contracted by introducing default arguments:

class Employee{

}

2.1.4.1. Using member initialisation lists

The members of a class can be assigned a value in two different ways from a constructor. We have already mentioned the solution consisting of assigning values within the body of a constructor. Besides that, there is another possibility in C++, namely member initialisation lists. An initialisation list is provided after a colon after the header of a constructor. The elements, separated from each other by a comma, are the data members of the class, and they are followed by their initial value enclosed within parentheses. If member initialisation lists are used, the constructors of the example above become empty:

class Employee{

private:

int employeeID;

string name;

float salary;

public:

Employee(int code=0, string n="", float s=0) : employeeID(code), name(n), salary(s) { } Employee(const Employee & a)

: employeeID(a.employeeID), name (a.name), salary(a.salary) { }

}

It should be noted that when a constructor is called, it is processing the initialisation list that takes place first, and the execution of the body of the constructor only after.

2.1.4.2. Explicit initialisation of objects

In the case of constructors having only one parameter, compilers carry out implicit type conversion, if needed, in order to choose the appropriate constructor. If the keyword explicit is used before a constructor, conversion can be hindered after the constructor is called.

In the following example, we differentiate between the two types of initial value assignment (explicit and implicit):

class Number {

private:

int n;

public:

explicit Number( int x) { n = x;

cout << "int: " << n << endl;

}

Number( float x) {

n = x < 0 ? int(x-0.5) : int(x+0.5);

cout << "float: " << n << endl;

} };

int main() {

Number a(123); // explicit call

Number b = 123; // implicit (not explicit) call }

When the object a is created, it is the explicit constructor that is called, whereas in the case of the object b, it is the constructor with the parameter of type float. If the keyword explicit is missing, it is the first constructor that is activated in both cases.

2.1.5. Destructor

The resources (memory, files, etc.) allocated often during the object creation have to be freed, when the object is destroying. Otherwise, these resources will become unavailable for the program.

For that purpose, C++ offers a special member function, the destructor, in which we can free the allocated resources. The name of a destructor has to be provided as a class name with the tidle character (~). A destructor, just like constructors, does not return any value.

In the following example, a dynamically allocated array with 12 elements is created in the constructors in order that the work hours done by employees could be stored for every month. The memory allocated for the array is freed in the destructor.

void IncreaseSalary(float percent) { salary *= (1 + percent/100);

Employee engineer(1234, "Tony Clever", 2e5);

engineer.IncreaseSalary(12);

engineer.SetWorkhours(3,192);

cout << engineer.GetSalary() << endl;

Employee *pAccountant;

pAccountant = new Employee(1235, "Sarah Rich", 3e5);

pAccountant->IncreaseSalary(10);

pAccountant->SetWorkhours(1,160);

pAccountant->SetWorkhours(12,140);

cout << pAccountant->GetSalary() << endl;

delete pAccountant;

}

Compilers call the destructor of a class every time when the object is not valid any more. An exception to that rule is constituted by dynamic objects created by the operator new, the destructor of which can be activated only

by the operator delete. It should be noted that a destructor does not delete the object itself but automatically do some "cleaning" tasks that is told to it.

When this example code is executed, the following text is printed out:

224000 330000

Sarah Rich deleted Tony Clever deleted

So, it can be clearly seen that first it is the destructor of the object *pAccountant that is called when the operator delete is used. Then, when the closing curly bracket of the body of the main () function has been reached, the destructor of the object engineer is automatically called.

If a destructor is not written for a class, the compiler automatically adds an empty destructor for that class.

In document Mechatronic Systems Programming in C++ (Pldal 180-185)