Showing posts with label Pointers. Show all posts
Showing posts with label Pointers. 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.

Comparison of Pointers and Pointer to Constants

In this tutorial, we will learn how to compare pointers as well as how to use constant variables with them.

Comparison between pointers in C++

In the same way that we can compare any two variables (such as int and double), we can also compare pointers.

And using the same operators:>,>=, <, <=, == and !=

We say, for example, that one pointer is greater than another, when, for example, its memory address in an array points to a variable whose index is greater than another.

For example:
int *ptr1 = array[0];
int *ptr2 = array[1];

So, the comparison: ptr2 > ptr1 will result in a true result.
ptr1 == ptr2 will result in a false result, as they point to different memory addresses.

That is, pointers are variables that store memory addresses.
So, when comparing two pointers, we are comparing two memory addresses, and not the values they point to, ok?

Pointers and C++ const

Whenever we use the keyword const, we are telling the compiler that the value stored in that variable should not be changed.

Often, we want to pass a variable as information for somewhere in code, but we don't want it to be modified in any way, in these cases, it is important to use the keyword const.

So far, we have used non-constant pointers that point to non-constant variables, that is, we can change up to the value of the variable via a pointer that points to it.

You cannot, for example, pass a pointer, which is not constant, to a function that expects a constant variable as an argument. Also, look:
#include <iostream>
using namespace std;

int main()
{
    const double pi = 3.14;
    const double *ptr = &pi;

    cout << *ptr << endl;

    return 0;
}
To point to a constant variable (pi), we had to define a constant pointer (const double *).
Try to remove the 'const' from the pointer declaration, and see what happens.

However, we can have a constant pointer that points to a variable that is not constant:
#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14;
    double * const ptr = &pi;

    cout << *ptr << endl;

    return 0;
}
The difference is that this pointer will ALWAYS point to the same memory address. You can even change the value it points to. But he will never change the address it points to. Constant type pointers must be initialized when they are declared.

Finally, we can have a constant pointer, which points to a constant variable:
#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14;
    const double *const ptr = &pi;

    cout << *ptr << endl;

    return 0;
}
Note that, in this case, we cannot change the address of the pointer (we cannot do ptr =&another_variable) nor can we change the value stored where the pointer points (*ptr = 21.12)

These cases of constant and pointers, as well as the comparison between pointers, are used a lot in the study of strings, for example.

Pointers, Arrays and Arithmetic

Continuing the study of pointers, we will see their important relationship with arrays, as well as learn how to manipulate pointers arithmetically, with mathematical operations.

Arrays and Pointers, Pointers and Arrays

Just for curiosity, let's declare an array of integers, named 'numbers', initialize and then simply print the value '*numbers':
#include <iostream>
using namespace std;

int main()
{
    int numbers[]={1, 2, 3, 2112};

    cout << *numbers << endl;

    return 0;
}
Look how interesting the result:
Curso de C++ online grátis, com apostila para download

That is: the name of the array works like a pointer.
And where does it point? For the first element of the array.

So, whenever we have an array named: arr
If we use the name of the array variable, it will behave like an array that points to: arr[0]

And to point to the other members of the array?
Remember the following rule:
  • arr[index] = *(arr + index)
Thus:
arr[0] can be referenced by *(arr + 0)
arr[1] can be referenced by *(arr + 1)
arr[2] can be referenced by *(arr + 2)
...
arr[n] can be referenced by *(arr + n)

We can make a pointer named 'ptr' point to the first element of an array in the following ways:
  1. int *ptr = numbers;
  2. int *ptr = &numbers[0];

Let's print an entire array, using just one pointer:
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = numbers;

    for(int aux=0 ; aux<6 ; aux++)
        cout << *(ptr+aux) << endl;

    return 0;
}

Pointer Arithmetic

In the previous C++ code example, you saw that we did an add operation with pointers several times: ptr + aux, where aux is an integer variable from 0 to 5, to go through the array.

We could have done the same program with the ++ operator, see:
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = numbers;

    for(int aux=0 ; aux<6 ; aux++){
        cout << *ptr << endl;
        ptr++;
    }

    return 0;
}
That is, each time we add a unit to the pointer (++), it points to the next element in the array. We can do the reverse, point to the last element of the array and go on decrementing:
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = &numbers[5];

    for(int aux=5 ; aux>=0 ; aux--){
        cout << *ptr << endl;
        ptr--;
    }

    return 0;
}
We could also use the operators -= or +=

The logic is as follows.
When we point the pointer to the array, it will point to the memory address of the first element. Let's assume that it is the 1000 memory block.

When we do: ptr++
It does not increment the memory address by one, it does not go to 1.
It goes to: 1000 + sizeof(int)

As the array is of integers, each block of the array occupies 4 Kbytes, so ptr will point to address 1004. Then to block 1008, then 1012 ...

If the array were double, it would point to the addresses: 1000, 1008, 1016, 1024 ... every time we incremented, since the double variable occupies 8 Kbytes ( sizeof(double) ).

See how is the code of the program that prints only the elements of even index, of the array (0, 2 and 4):
#include <iostream>
using namespace std;

int main()
{
    int numbers[6]={1, 2, 3, 4, 5, 2112},
        *ptr = numbers;

    for(int aux=0 ; aux<3 ; aux++){
        cout << *ptr << endl;
        ptr += 2;
    }

    return 0;
}
This is the logic and mathematics of the pointers. That is why it is so important to define the type of pointer (int, char, double ...), since the pointers point to entire blocks of memory, the size of these blocks varies according to the type of data.

Pointers in Functions (in C++)

In this tutorial from our C++ e-book, we will learn how to work with pointers in functions.
First, let's review a little of what we learned in the Functions section.

Passage by Value and Reference Variable

Run the following program, which squares a number entered by the user.
It shows the number entered, it squared and then the original value, again:
#include <iostream>
using namespace std;

int square(int num)
{
    num = num*num;
    cout << "Squared: " << num <<endl;
}

int main()
{
    int num;
    cout <<"Type a number: ";
    cin >> num;

    cout <<"Number typed: " << num << endl;
    square(num);
    cout <<"num value: " << num << endl;

    return 0;
}

C++ e-book for download

See that we pass the variable 'num' to the function, and inside it is squared. We show this value within the function, and then show it again when the function call is finished.

It did not change the value stored in 'num'.

This is because when we pass a variable to a function, the function makes a copy of its value. It will not use the variable directly, but its value only, so the original variable is not changed.

To give access directly to the variable, we can use the reference variables.
Which is basically passing the name of the variable, with the symbol & in front, see how it looks:
#include <iostream>
using namespace std;

int square(int &num)
{
    num = num*num;
    cout << "Squared: " << num <<endl;
}

int main()
{
    int num;
    cout <<"Type a number: ";
    cin >> num;

    cout <<"Number typed: " << num << endl;
    square(num);
    cout <<"num value: " << num << endl;

    return 0;
}

Now notice that the value of 'num' directly has been changed inside the square() function:

How to use pointers in Functions

This happened because we used the reference variable &num instead of just num.

And now that you've studied pointers and memory addresses, you know that by using & we are dealing directly with memory addresses. That is, the function will now have access to the memory address of variable num and will change that block of memory directly.

How to Use Pointers in Functions in C++

Another way to change the value of a variable, passing it to a function, is using pointers. In fact, it is important to know how to use this technique, as it is very useful in functions that deal with Strings and in several C++ libraries, for example.

Let's create a function, called doub(), that will double the value it receives, using pointers:
#include <iostream>
using namespace std;

int doub(int*);

int main()
{
    int num;
    cout <<"Type a number: ";
    cin >> num;

    cout <<"Number typed: " << num << endl;
    doub(&num);
    cout <<"num value: " << num << endl;

    return 0;
}

int doub(int *ptr_num)
{
    (*ptr_num) = (*ptr_num) * 2;
    cout << "Double: " << *ptr_num <<endl;
}
Its parameter is: int*
That is, it expects an int pointer. If it expects a pointer, we have to pass as a ... memory address! So we did: doub(&num);
Remember: a pointer is a variable that stores a memory address!

Within the function, 'ptr_num' is a pointer. Pointer to the memory address of the 'num' variable in the main() function.

We want to double the amount it points to. Now, how do you access the value stored in the location to which the pointer points? Using asterisk: *ptr_num

So, to double the value of the variable 'num', using the pointer 'ptr_num' that points to it, just do:
*ptr_num = *ptr_num * 2;

In order not to get confused with the operators, you can do it as soon as it gets more organized:
(*ptr_num) = (*ptr_num) * 2;

The power of pointers

A great advantage of working with pointers is that they can deal in a more 'global' way, so to speak, with the variables they point to.

For example, note that we always ask the user for a number within the main() function, and from there we send that value or its address to the most diverse functions.

Using pointers, we can get user values inside a function, like getNum(), see:
#include <iostream>
using namespace std;

void getNum(int *);
int doub(int*);

int main()
{
    int num;
    getNum(&num);
    cout <<"Number typed: " << num << endl;

    doub(&num);

    cout <<"num value: " << num << endl;

    return 0;
}

void getNum(int *ptr_num)
{
    cout <<"Type a number: ";
    cin >> *ptr_num;
}

int doub(int *ptr_num)
{
    (*ptr_num) = (*ptr_num) * 2;
    cout << "Double: " << *ptr_num <<endl;
}
We declare the variable 'num', and pass its address to the doub() variable, this address will be stored inside the ptr_num pointer.
Now, we want to change the value of ptr_num, so just use: *ptr_num

And ready, magically, the variable that was declared there in the main() function was changed inside the getNum() function.

Powerful, these pointers, right?

Pointers: How to declare, Initialize, and Use

Now that we've learned what pointers and memory addresses are, let's get down to business, creating and using pointers in C++.

How to declare pointers in C++: *

Just like any other type of variable, to use a pointer, we need to declare the pointer data type.

And it is done as follows:

  • type *name;

For example:

  • int *number;


This means that we create a pointer (as we put the asterisk in the declaration), which will point to a memory address that stores values of type int.

Be careful not to confuse: it is not because there is an int written that number is an integer. It's a pointer to an int, okay?

To avoid confusion, many programmers prefer to write:

  • float* value;


That is, it is not a float variable, it is a pointer to a float (it is float* and not float).
Run the code example below:
#include <iostream>
using namespace std;

int main()
{
    int *number;

    cout << number << endl;

    return 0;
}
The result is 0, which is the symbol of NULL or empty pointer, in C ++.
In fact, it is even a good practice to initialize with 0 or NULL, pointers.

Let's learn how to point the pointer to a more specific location.

How to Initialize Pointers in C++

Well, let's go.

First, let's create and initialize a variable of type double:

  • double pi = 3.14;


Now, let's declare a pointer that points to a variable of type double:

  • double *pointer;


To initialize a pointer we need to provide a memory address.
How about the memory address of the pi variable?
It is obtained like this: &pi

Then, declaring and initializing the pointer:

  • double *pointer = &pi;

There, our pointer points to a variable.
See the result of the code:

#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14;
    double *pointer = &pi;

    cout << "pi variable value   : " << pi << endl;
    cout << "pi variable address : " << pointer << endl;

    return 0;
}

C++ e-book full PDF download for free

Pointer content: *pointer

We say that the variable pi directly references the value 3.14
The pointer variable indirectly references the value 3.14, as it points to the location where it is stored.

If we use the pointer variable, we are working with a memory address.
To handle the value that is stored at that address, we use: *pointer
That is, an asterisk before the name of the pointer.

Look:
#include <iostream>
using namespace std;

int main()
{
    double pi = 3.14;
    double *pointer;
    pointer = &pi;

    cout << "pi variable value   : " << *pointer << endl;
    cout << "pi variable address : " << pointer << endl;

    return 0;
}
That is, whatever does this:
cout << pi;

Or that:
cout << * pointer;

It is the same, because: pointer = &pi
That is, it doesn't matter which you use, because the pointer points to the variable.

How to use pointers in C++

And what is the purpose of that?
Simple: we can also change the value of the variables, using their pointers only.

See the example below, initially the variable has a value of 1.
Then, using a pointer that points to it, we change it to value 2:
#include <iostream>
using namespace std;

int main()
{
    int number = 1;
    int *pointer;
    pointer = &number;

    cout <<"number initial value: " << number << endl;
    *pointer = 2;
    cout <<"new number value    : " << number << endl;

    return 0;
}
In the previous example, it does not matter work with 'number' directly or with *pointer.
The asterisk, in this case, is called the indirect operator.

In the example below, we make the same pointer point (at different times) to different variables and change their values:
#include <iostream>
using namespace std;

int main()
{
    int a=1, b=1, c=1;
    int *pointer;

    cout <<"a+b+c: " << (a+b+c) << endl;
    cout <<"Incrementing a, b e c"<<endl;

    pointer = &a;
    (*pointer)++;

    pointer = &b;
    (*pointer)++;

    pointer = &c;
    (*pointer)++;

    cout <<"a+b+c: " << (a+b+c) << endl;

    return 0;
}
You see, we changed the value of 3 variables, without acting directly on them, with just one pointer.

Note that if we do: *pointer++, it will give an error, since the ++ operator takes precedence over the asterisk, that is, ++ will act before in the variable 'pointer', than the indirect operator *.