Showing posts with label new. Show all posts
Showing posts with label new. Show all posts

Dynamic allocation of objects with pointers: The new and delete operators

In this tutorial of our Object Orientation Course in C++, we will learn how to use pointers with objects, and see a new way to allocate memory.


Object Pointers

Initially, we will create a class, called Student, which will have two variables (math and english), which will store the student's grades. There will also be a get and set, and a getAverage() call to calculate the average for each student.

Our class, all complete and cute looks like this:

class Student
{
    private:
        double math, english;

    public:
        double getAverage() const;
        void setMath(double);
        void setEnglish(double);
        double getMath() const;
        double getEnglish() const;
};

double Student::getAverage() const
{
    return (getMath() + getEnglish())/2;
}

void Student::setMath(double m)
{
    math = m;
}
void Student::setEnglish(double e)
{
    english = e;
}
double Student::getMath() const
{
    return math;
}
double Student::getEnglish() const
{
    return english;
}

We will now declare a pointer named 'ptr' of type Student:

  • Student *ptrStudent

Now let's actually declare an object of the Student class:

  • Student s1;

We can associate the pointer with the object as follows:

  • ptrStudent = &s1

(don't forget &, remember that pointers store memory addresses, and by placing & before a variable, it gives the address of that variable)


Working with Pointers with Classes and Objects: ->

As we are already studying pointers, we saw that they can work wonders.
For example, in the previous example, for the pointer to access the elements of an object, we must use the operator ->

Let's define two grades for a student:
  • ptrStudent-> setMath (8);
  • ptrStudent-> setEnglish (10);

And to know the average, using a pointer, just do:
  • ptrStudent-> getAverage()

Our complete code is:
#include <iostream>
using namespace std;

class Student
{
    private:
        double math, english;

    public:
        double getAverage() const;
        void setMath(double);
        void setEnglish(double);
        double getMath() const;
        double getEnglish() const;
};

double Student::getAverage() const
{
    return (getMath() + getEnglish())/2;
}

void Student::setMath(double m)
{
    math = m;
}
void Student::setEnglish(double e)
{
    english = e;
}
double Student::getMath() const
{
    return math;
}
double Student::getEnglish() const
{
    return english;
}

int main()
{
    Student *ptrStudent;
    Student s1;

    ptrStudent = &s1;
    
    ptrStudent->setMath(8);
    ptrStudent->setEnglish(10);

    cout << ptrStudent->getAverage()<<endl;

    return 0;
}
Note that we used only one student, we could have created a thousand students and make the pointer point to each one of these students, and the code would always be the same, for example:
Student s1, s2, s3, s4;

Do: ptrStudent = &s1
Then: ptrStudent = &s2
Then: ptrStudent = &s3
Then ptrStudent = &s4

And ready, then just use usr
ptrStudent-> setMath();
ptrStudent-> setEnglish();
ptrStudent-> getAverage()

This avoids writing code for nothing.

Allocating space for objects: new operator

There is a new way to instantiate objects, it is from the new operator.
First you declare your pointer:
  • MyClass *ptr;
At this point, do we have a pointer to point to an object? What object is that? Let's create it now, as follows:
  • ptr = new MyClass

Deleting objects - delete

When we create an object, via normal instantiation, it will always exist and occupy a space in memory. One of the advantages of making dynamic memory allocation, through new, is that we can destroy this pointer, destroying this object and freeing space in memory, for this, just use the delete operator:
  • delete ptr;

And ready, we have a free space on the machine. This may seem silly now, but when you create super complex systems that require maximum efficiency and the least possible use of memory (such as programming a watch or microwave, which has little memory), it is a very important technique.

See the code below, just with a pointer, we fill the grade of two students and calculate their average, and all this just as an object / pointer of the Student class. See how the code looks:

#include <iostream>
using namespace std;

class Student
{
    private:
        double math, english;

    public:
        double getAverage() const;
        void setMath(double);
        void setEnglish(double);
        double getMath() const;
        double getEnglish() const;
};

double Student::getAverage() const
{
    return (getMath() + getEnglish())/2;
}

void Student::setMath(double m)
{
    math = m;
}
void Student::setEnglish(double e)
{
    english = e;
}
double Student::getMath() const
{
    return math;
}
double Student::getEnglish() const
{
    return english;
}

int main()
{
    Student *ptrStudent;
    ptrStudent = new Student;

    ptrStudent->setMath(8);
    ptrStudent->setEnglish(10);
    cout << "Studant average 1: " <<ptrStudent->getAverage()<<endl;

    ptrStudent->setMath(6);
    ptrStudent->setEnglish(7);
    cout << "Studant average 2: " <<ptrStudent->getAverage()<<endl;

    delete ptrStudent;

    return 0;
}

Dynamic Memory Allocation and Operators new and delete

In this tutorial, we will learn a new way to declare variables, using the new operator and pointers, in C++.

Dynamic Memory Allocation in C++

Let's assume that you are going to make software for the multinational you work for.
It has 1000 employees.

Then, you will declare, among several things, a thousand variables of type int to store the age of each person, a thousand variables of type double to store the salary of each person, a thousand strings to store the name of each person, etc., etc.

However, his company has grown, and now has 2,000 employees.
And now, go back to the code and change everything?

So far, in our C++ course, we have declared fixed values, exact values ​​of memory blocks, but this is not always interesting in the world of computing. Often you don't know how many variables you will need to use, so you don't know how many variables you will need to declare.

For example, if you are going to create a new social network, how much memory space will you need to store user data? Hey, you don't know ... after all, it may have a thousand or a million users, who knows.

What you have to do is then allocate memory dynamically. That is, it will allocate as needed.

Pointers and the Operator new

Let's say you want to allocate space for a variable of type double.
You will ask, via C++ code, for the computer to find a free memory block on the machine that you can allocate for yourself.

It then goes there to search your system, finds a block and returns the starting address of that block, the address of the first byte. Now, if it returns you an address, what do you need to keep that kind of information?

Yes, our beloved type, the pointer.
This allocation request is made with the new operator.

Here is a code example that we allocate a block of memory for a variable of type double:
#include <iostream>
using namespace std;

int main()
{
    double *ptr;
    ptr = new double;
    *ptr = 3.14;

    cout << *ptr << endl;

    return 0;
}
When we type 'new double', the machine returns a memory address, the first of the block reserved for the double, and stores that address in the 'ptr' pointer, which obviously must be of the double type as well (that is, it points to the address of an 8-byte block, space required for a double).

See, we haven't defined any variables purely of type double. We only have a pointer that points to a block that has a value of type double.

We can even change this stored value using *ptr:
#include <iostream>
using namespace std;

int main()
{
    double *ptr;
    ptr = new double;
    cin >> *ptr;

    cout << *ptr << endl;

    return 0;
}
Everything we could do with a variable of type double, we can do with this pointer.

We can also allocate entire arrays, with the new operator, see:
#include <iostream>
using namespace std;

int main()
{
    int *ptr;
    ptr = new int[10];

    for(int aux=0 ; aux<10 ; aux++){
        ptr[aux] = aux+1;
    }

    for(int aux=0 ; aux<10 ; aux++){
        cout << ptr[aux] << endl;
    }

    return 0;
}
In the code above, we allocate an array of 10 integers, and fill in the numbers from 1 to 10.
That is, in the first example, ptr 'became' a double. In this example above, ptr 'became' an array of integers.

These pointers are flexible, aren't they?

Free memory spaces - Operator delete

Another advantage of using memory allocation dynamically, is the possibility of also freeing that memory that has been allocated, just by using the delete operator.

In the first example, we deallocate the memory block like this:
#include <iostream>
using namespace std;

int main()
{
    double *ptr;
    ptr = new double;
    cin >> *ptr;

    cout << *ptr << endl;

    delete ptr;

    return 0;
}
In case it is an array, we place the pair of square brackets [] before the pointer name:
#include <iostream>
using namespace std;

int main()
{
    int *ptr;
    ptr = new int[10];

    for(int aux=0 ; aux<10 ; aux++){
        ptr[aux] = aux+1;
    }

    for(int aux=0 ; aux<10 ; aux++){
        cout << ptr[aux] << endl;
    }

    delete [] ptr;

    return 0;
}
This may seem strange nowadays, if you imagine your PC with several and several Terabytes, there is no need to free up allocated memory during the execution of a program.

However, this knowledge and practice is essential in critical systems that consume a lot of memory, such as in a very high level game or in a super efficient operating system, you cannot leave everything allocated forever, you have to release it gradually.

In systems with little memory space (like your digital clock, the panel of a refrigerator or the timer of your microwave), it is vital to deallocate and free up memory, as this resource is very scarce.

We will study the new operator in more detail when we study Classes and Objects, in Object Oriented Programming.