Data Structure: struct - What is it, what is it for and how to use it?

In this tutorial, we will begin the study of data structures, in C++.

Data Abstraction

One of the most important concepts in Computer Science is that of data abstraction. Also called ADT, abstract data type.

Abstraction, in computing, refers to a model of something, a prototype, a generalization of something.

For example, Human is an abstraction. You don't know a Human, you actually know specific people, like your father, your mother, your friends. Humans is a 'mold', to represent an object that has a head, brain, heart, lung, etc.

The same goes for Carro. You don't come to the dealership and say 'I want a car'.
You say: I want a Corolla, a Civic, a Honda Fit, etc.

Car is a generalization of a thing that has all, engine, gear etc.

Well, the same happens in our dear and beloved programming, it is super common in a project to create our abstractions, to represent 'things' of the system that we are going to implement.

Data Structures: structs

So far, we have used very specific data types, such as int, double and float (to work with numbers) or char, to work with characters, for example.

Through arrays, we can create and work with any number of these types of data in a very easy and powerful way.

However, there is a problem. The data types of an array are always the same. You can't have an array that has an integer inside representing an employee's age, a string storing the name and a float to store your salary.

Well, that's what structs are for: packaging other types of data, to create your own type of data. Through structs, we can create new types of data.

For example, let's create the data type Car, inside it there is an integer to store the number of doors, a float to store the engine power and a string to store the model name.

Estrutura de Dados em C++

If before we did:
  • int number;
  • float price;
Now let's do:

  • Employee Jose;

That is, we are declaring a variable named 'Jose', which is of type 'Employee'. Yes, type 'Employee', as well as data type int, float, char, etc.

It is a type of data that you created, a necessary abstraction in your project, and this is done through structs! It can contain as many variables as you want and of any type, it can have arrays, pointers, matrix, etc. All of this 'packaged', in a data structure that you have named.

In the next tutorial, we will learn how to create, access and use a struct, in C ++!

Association, Composition and Aggregation in C++

In this tutorial from our C++ course, we will learn how different Classes and Objects relate to each other, and study this relationship further.

Classes and Objects in Real Life

So far, in our course, we have used classes in a very isolated way, creating and using only one class. But in the real world, it rarely happens. We live in a world of objects.

Often, it only makes sense that there is an object, if there are others. For example, we can have the Human class and the Food class. Human-type objects are you, me, our friends, family ... Food-type objects, rice, beans, meat ... does it make sense for Human-class objects to exist without Food-class objects? Of course not.

And even if one object does not depend on another to exist, it can depend on others to 'work'. For example, objects from the Engine, Gear, Brake, etc. classes work together to make a Car type object work.

That is, in the real world, objects are not isolated. In your projects, you will always create several classes, instantiate several objects, make one create another, send information, another receive data from another object, the method of an object using several other objects, and so on.

Association, Composition and Aggregation

When an object uses a method or service from another object, we say that there is an association there. Yes, it is very broad and generic. We will specify more the types of relationship, in composition and aggregation.

In both cases, an object has another object. However, in composition, an object exists only if the other exists.

For example, the object of type Car only exists if it has an object of the Engine class. There is no car without an engine.

A Human type object, only exists if it has objects like Heart, Lung ... there is no person without these organs (at least for now, some program will design and create organs in the future - of course, it would use C++).

In aggregation, an object has another, but it could exist without it. For example, every car has an owner. That is, it has Human-type objects that have Car-type objects. But a human being does not necessarily need a car to exist.

You need a heart, a head ... mandatorily. Not a car. The relationship of a Car-type object and a Human-type object is aggregation.

Association, Composition and Aggregation in C++

In composition, when a parent object is destroyed, the child objects will also be destroyed. One does not exist without the other. In aggregation, no. In other words, association is something very generic, then there is aggregation (one can exist without the other) and finally the composition, the most specific relationship, where an object only exists if another exists.

But let's put these generic conversations aside and see in practice some relationships between objects.

Composition in C++

Let's start with the most specific. To make it easier, we call the Human and Car classes parent classes. The classes Engine, Heart, Gear, Lung etc., are the child classes, members or components. Or we call it All and Parts, or Object and Member.

To be characterized as a composition, a member is part of the whole. Each part belongs to only one object (that is, the heart belongs only to a human, and the car's engine belongs to only one car).

The class that represents the Whole, will manage the parts, the components. These members are unaware of the existence of the 'Whole'.

Let's create the Engine class, in the constructor we warn that we are starting the engine and in the destructor function we warn that we are turning the engine off. Engine.h:

#ifndef ENGINE_H
#define ENGINE_H

class Engine
{
    public:
        Engine();
        ~Engine();
};

#endif // ENGINE_H

Engine.cpp

#include "Engine.h"
#include <iostream>
using namespace std;

Engine::Engine()
{
    cout << "Starting the engine..."<<endl;
}

Engine::~Engine()
{
    cout << "Turning off the engine..."<<endl;
}

Now we are going to create the Car class, which will do almost the same as the Engine, it will say 'starting the car' in the constructor and 'turning the car off' in the destructor. However, it will have a member, a pointer for the Engine type, the variable 'myEng'.

In the class, we just declare this pointer, Engine.h:

#ifndef CAR_H
#define CAR_H
#include "Engine.h"

class Car
{
    public:
        Car();
        ~Car();
        Engine *myEng;
};

#endif // CAR_H

But in the implementation, we instantiate this pointer, causing the constructor method of the 'myEng' object to be invoked. In Car's destructor function, we delete the Engine pointer, automatically invoking the destructor function, Car.pp:

#include "Car.h"
#include <iostream>
using namespace std;

Car::Car()
{
    cout << "Let's start the car."<<endl;
    myEng = new Engine();
}

Car::~Car()
{
    delete myEng;
    cout << "Car off."<<endl;
}

Our main.cpp:

#include <iostream>
#include "Car.h"
using namespace std;

int main()
{
    Car myCar;

    return 0;
}

The result is as follows:

Let's start the car.
Starting the engine...
Turning off the engine...
Car off.

The 'myEng' object EXISTS ONLY because of the 'myCar' object. Without 'myCar', there is not 'myEng'. 'myEng' belongs to 'myCar'. One object belongs to another.

Whoever 'controls' myEng is also myCar. He who creates the object, instantiates ... and the Car class object dies, the Engine class object also dies. The 'myEng' object belongs only to the 'myCar' object, to none other. And he doesn't know about any other object either, and he doesn't know anything about the Car class.

Those things that characterize the composition well.

Aggregation in C++

We are now going to create the Human class, to create people. They will drive the car. To create an object of this class, we need to pass a string with the person's name and an object of type Car, see how Human.h looks:

#ifndef HUMAN_H
#define HUMAN_H
#include <string>
#include "Car.h"

class Human
{
    public:
        std::string name{};
        Car myCar;
        Human(const std::string& str, Car car);
        ~Human();

};

#endif // HUMAN_H

Now the Human.cpp implementation:

#include "Human.h"
#include <iostream>
#include <string>
using namespace std;

Human::Human(const std::string& str, Car car)
{
    name = str;
    myCar = car;
    cout<<name;
    myCar.turnOn();
}

Human::~Human()
{
    cout<<name;
    myCar.turnOff();
}

We made some changes to Car.h:

#ifndef CAR_H
#define CAR_H
#include "Engine.h"

class Car
{
    public:
        void turnOn();
        void turnOff();
        Engine *myEng;
};

#endif // CAR_H

And in the Car.cpp implementation:

#include "Car.h"
#include <iostream>
using namespace std;

void Car::liga()
{
    cout << " will turn on the car."<<endl;
    myEng = new Engine();
}

void Car::desliga()
{
    cout <<" turned off the car."<<endl;
    delete myEng;
}
 

That is, now we have the functions turnOn() and turnOff(), on objects of the Car type (before we used to turn on and off the constructor and destructor).

See how our main.cpp looks like:

#include <iostream>
#include "Car.h"
#include "Human.h"

using namespace std;

int main()
{
    Car car;
    Human *h1 = new Human("Neil Peart", car);
    delete h1;

    cout << endl;

    Human *h2 = new Human("Bruce Dickinson", car);
    delete h2;

    return 0;
}

That is, we created an object of the Car class and two of the Human class.
Let's see some interesting things. The result if we run the code above is:

Neil Peart will start the car.
Engine running.
Neil Peart turned off the car.
Engine off.

Bruce Dickinson will start the car.
Engine running.
Bruce Dickinson turned off the car.
Engine off.

First, the same 'car' object is used for both 'h1' and 'h2' objects.

Second, it is the h1 and h2 objects that trigger the car's turnOn() and turnOff() functions, that is, they handle the 'car' object.

Finally, the 'car' object does not cease to exist when the 'h1' object ceases to exist. It remained there alive, so much so that it was used by the object 'h2' shortly thereafter. That is, although there is a relationship between them (some objects use others), they are somewhat independent, unlike the relationship between Car and Engine, where engines ONLY EXIST as long as there are cars, and only one engine is used for each car .

These are the characteristics of aggregation.
Between Car and Engine, it's composition.
Between Human and Car, it's aggregation.

Association in C++

Let me tell you the biggest secret of the C++ language: a large, complex system, like a space station or Microsoft Excel, is nothing less than several small objects, playing specific roles.

In other words, a complex system is nothing more than several small objects, working together, as an association.

Thus, during your career as a C++ programmer, always try to create specific, well-defined Classes and Objects, with the right functions and as simple and direct as possible.

To repeat: a large project or system is nothing more than a looot of objects working together, in association, whether by composition or aggregation.

Organization is the key to success in working with object-oriented programming!

C++ Operators Overloading

In this tutorial of our C++ course, we will learn how to use a very important tool in the C++ language, operator overloading.


The role of an operator

What the '+' operator does in the command:
2 + 2?

Easy. He adds, after all, is the addition operator.
Wrong. He is not the addition operator.
So what is it?

Depends on the use!

For example, with strings:
"Progressive" + "C++"

It will result in the string: "Progressive C++"

That is, the function of the '+' operator was concatenation, that is, to unite, to join two strings into one. You see, it is concatenating, there is no sense in 'adding text'. You add numbers, and with numbers, the '+' function is a mathematical addition.

That is, an operator can act in different ways, depending on how it is being used. It's called overloading.


What is Operator overloading for?

Let's suppose that you are the programmer of a company that manages several bands, among them you have the band 'IronMaiden', object of the class 'Band'. Your system also has the class "Member", where your objects are the members of each band.

Overloading operators in C++

Let's assume that candidate "BruceDickinson" will be hired and member "BlazeBayley" will leave the band.
With operator overloading, we can use the '+' operator in the expression:
  • IronMaiden = IronMaiden + BruceDickinson

As well as using the '-' in the command:
  • IronMaiden = IronMaiden - BlazeBayley

Look how curious: we are adding an object of type Band with an object of type Member. But, doesn't it make sense? One will enter and another will leave.

Another example, imagine that you are the programmer responsible for a large company.

When we hire, we are adding someone, so we can do an overloading to add an object of the Employee class with the object of the Company class, then there in your code, when this happens, there will be one more person on the payroll, one more person in one sector, etc.

And when we fire, we are subtracting someone, we can use the '-' operator to subtract an Employee object from the Company object, so in the code, in these cases, we will have to pay labor rights, another employee will have to keep the functions that the dismissed used to did, etc.

That is, we did an overload of operators '+' and '-', to have different meanings when we do operations with these objects. With the overloading operators, you do what you want with the objects, the operators will do what you define.

There are only 4 operators that cannot be overloaded:
  1.  .
  2.  .*
  3.  ::
  4.  ?:
The rest can, and should, overload, if it makes sense to your project. Let's learn how to do this in practice?

How to overload operators in C++

Let's create the Point class, to represent a point on the Cartesian plane. It receives two integers (coordinates) and has a function that shows these coordinates, see the Point class:

class Point
{
    private:
        int x, y;
    public:
        Point(int a, int b);
        void getPoint();
};

However, in vector algebra, a common operation is the sum of vectors, which is basically adding the coordinates of two points.
For example: (1,2) + (2,3) = (1 + 2, 2 + 3) = (3,5)

That is, the sum of two points is nothing more than the sum of the coordinates, separately.

So come on, implement this with overloading. The overloading header is the same as a function. In our case, it is:
  • Point operator+(Point p);
That is, it must always return the data type of the class, which is Point. Then we write "operator+" to overload the + operator, and finally, we put the type of data that will be added, in this case, it is another object of type Point.

Our code is:

#include <iostream>
using namespace std;

class Point
{
    public:
        int x, y;
        Point(int a, int b);
        void getPoint();
        Point operator+(Point p);
};

Point Point::operator+(Point p)
{
    x=x+p.x;
    y=y+p.y;

    return Point(x,y);
}

Point::Point(int a, int b)
{
    x=a;
    y=b;
}

void Point::getPoint()
{
    cout<<"("<<x<<","<<y<<")"<<endl;
}

int main()
{

    Point p1(1,2), p2(2,3);
    Point p3 = p1 + p2;
    p3.getPoint();

    return 0;
}

One way to understand this operation is to switch:
p3 = p1 + p2;

Per:
p3 = p1.operator+(p2);

It is as if the class had a function called operator+(), whose parameter is another object of type Point.

Operator Overloading Example in C++

We will now overload the operator '='
I want the following: when I equate a Point object with an integer, the coordinates must be equal to that integer.

That is, if I do:
p = 1, the coordinates of the object p must be (1,1)

See the code:

#include <iostream>
using namespace std;

class Point
{
    public:
        int x, y;
        Point(int a, int b);
        Point();
        void getPoint();
        Point operator+(Point p);
        void operator=(int n);
};
void Point::operator=(int n)
{
    x=n;
    y=n;
}
Point Point::operator+(Point p)
{
    x=x+p.x;
    y=y+p.y;

    return Point(x,y);
}

Point::Point()
{
    x=0;
    y=0;
}
Point::Point(int a, int b)
{
    x=a;
    y=b;
}

void Point::getPoint()
{
    cout<<"("<<x<<","<<y<<")"<<endl;
}

int main()
{

    Point p1(1,2), p2(2,3);
    Point p3 = p1 + p2;
    p3.getPoint();

    Point p4;
    p4=1;
    p4.getPoint();

    return 0;
}

Look what curious, we assign the object 'p4', which is a Point type, with an integer. It sounds crazy, it's like assign a car with an apple. But C++ allows this 'madness', you just need to make sense of it, and it does so through the overload of operators.

You know something crazy, compare if one object is bigger than another!
?

Relational Operator Overloading

A vector, in the Cartesian plane, is defined by two numbers (x, y)
Its size, called a module, is: d = sqrt (x² + y²)
That is, square root of: x² + y²

Let's create the myVector class to represent a vector, and compare if a vector v1 is greater than a vector v2, the code stays, that is, if v1> v2

However, v1 and v2 are objects of the myVector class, not numbers, so we cannot make this comparison directly, so we will have to overload the operator '>', for it to do another type of operation:

#include <iostream>
#include <cmath>
using namespace std;

class myVector
{
    public:
        int x, y;
        myVector(int a, int b);
        bool operator>(myVector v);
};

bool myVector::operator>(myVector v)
{
    float d1, d2;
    d1=sqrt(x*x + y*y);
    d2=sqrt(v.x*v.x + v.y*v.y);

    if(d1>d2)
        return true;
    else
        return false;
}

myVector::myVector(int a, int b)
{
    x=a;
    y=b;
}

int main()
{
    myVector v1(6,8), v2(3,4);

    if(v1 > v2)
        cout<<"The vector ("<<v1.x<<","<<v1.y<<") is bigger than ("<<v2.x<<","<<v2.y<<")"<<endl;
    else
        cout<<"The vector ("<<v1.x<<","<<v1.y<<") is less or equal than ("<<v2.x<<","<<v2.y<<")"<<endl;


    return 0;
}

In the case of the '>' operator, it always returns true or false, so the overload returns a bool.

Operator Overloading Exercise

If you have already graduated from high school, you know what complex numbers are. These are numbers like:
x = a + bi

Where 'a' is the real part and 'b' the imaginary part, as it is accompanied by the imaginary number 'i'.

Create a class that represents a complex number. It must perform the sum and product operation of complex numbers.

Sum:
x = a + bi
y = c + di
sum = x + y = (a + c) + (b + d) i

Product:
product = x * y = (ac − bd) + (ad + bc) i

Post your overloadings in the comments.

Instance Members and Static Members

In this tutorial of our C ++ course, we will clarify a little more about the variables of the objects as well as learn what static members are and how to use them in practice.


Instance members (of objects)

Static members in C++
Recalling our study of functions a little, we saw there that, commonly, the variables declared within a function are said to be local and temporary. Because they only exist within the function and are deleted from memory when the function ends. In classes, something similar occurs.
When we have a 'Car' class and the 'engine' attribute, we do:

  • BMW.engine = 4;
  • corolla.engine = 2;

Although the name of the variable is the same, each object has its own variable, where one does not change the other. That is, 'copies' of these variables are created.

Thus, the variables are specific to each instance, even though they have the same name and the objects originate from the same Class.


Static members of a Class

However, sometimes we do not want each instance to have its own attribute, it may be necessary to have an attribute that is exactly the same (same memory location), for all objects.

And it is possible to do this, for both variables and functions.

For example, suppose you are the programmer who will take care of a large company's system, and each time a person is hired, an Employee type object must be created. And each time an employee is fired, an object is deleted. How do you keep track of how many employees there are?

Simple, just create a static variable, which is incremented every time an object is instantiated and is decremented every time an object is deleted.


Static variable

To create a static variable, just use the static keyword before declaring the variable.

Let's create the class "Car.h":

#ifndef CAR_H
#define CAR_H


class Car
{
    public:
        Car();
        ~Car();
        int getTotal();

    private:
        static int total;
};

#endif // CAR_H

See that we declare the entire variable 'total' as static. Let's see the implementation, 'Car.cpp':

#include "Car.h"

int Car::total = 0;

Car::Car()
{
    total++;
}

Car::~Car()
{
    total--;
}

int Car::getTotal()
{
    return total;
}

The first thing we did was to initialize the total variable with 0, after all, initially, no object was instantiated. The constructor function simply increases the total value, that is, each time we instantiate an object, this variable will be increased.

And in the destructor function, we decrease this variable.

And since it is static, this value will represent the number of objects created from the Car class. See how our main.cpp looks like:

#include <iostream>
#include "Car.h"
using namespace std;

int main()
{
    Car *ptr1, *ptr2, *ptr3;
    ptr1 = new Car();
    ptr2 = new Car();
    ptr3 = new Car();

    cout<<"Number of instantiated objects: "<< (*ptr1).getTotal() <<endl;

    delete ptr3;
    cout<<"Number of instantiated objects: "<< (*ptr1).getTotal() <<endl;
return 0; }

First, we created three Car-type objects. Next, we do the dynamic memory allocation.

We print the result after these 3 allocations. We delete the third pointer (this causes the destructor ~ Car() function to be triggered), then print the number of instances again.

And the result is:

"Number of instantiated objects: 3"
"Number of instantiated objects: 2"

Note that we use the function of the first object, '*ptr1', which we created, this proves that the variable 'total' is the same, for all objects.


Static function

Just as static variables exist, we can also create static functions, for a class. However, they have a very specific characteristic: they can only act on static variables.

We will create the zero() function, which will reset the 'total' variable. Our "Car.h" class:

#ifndef CAR_H
#define CAR_H

class Car
{
    public:
        Car();
        ~Car();
        int getTotal();
        static void zero();

    private:
        static int total;
};

#endif // CAR_H

Our implementation "Car.cpp"

#include "Car.h"
int Car::total=0;

Car::Car()
{
    total++;
}

Car::~Car()
{
    total--;
}

int Car::getTotal()
{
    return total;
}

void Car::zero()
{
    total=0;
}

Finally, our main:

#include <iostream>
#include "Car.h"
using namespace std;

int main()
{

    Car *ptr1, *ptr2, *ptr3;
    ptr1 = new Car();
    ptr2 = new Car();
    ptr3 = new Car();

    cout<<"Number of instantiated objects: "<< (*ptr1).getTotal() <<endl;
delete ptr3;
    cout<<"Number of instantiated objects: "<< (*ptr1).getTotal() <<endl;
Car::zero();
    cout<<"Number of instantiated objects: "<< (*ptr1).getTotal() <<endl;
return 0; }

And the result is:

"Number of instantiated objects: 3"
"Number of instantiated objects: 2"
"Number of instantiated objects: 0"

An interesting thing about static members is that they exist even before any instance of the class is created. So, without creating any objects, you can already access 'total' and 'zero()'.

For example, you can ask the user for a value and use that value to change a static variable of a class, before even starting to instantiate objects from it, and start working with objects from a value previously determined by the user.

Bank Account System with Object-Oriented Programming in C++

In this tutorial, we will use all our knowledge of Object-Oriented Programming in C++, and create a real example of application, a bank account.

How to make a Bank Account System
Exercise: Bank Account

Create a system in which a user bank account is created, where he can check his balance, deposit and withdraw money, use object-oriented knowledge.

Your system should start by displaying a menu with these options, and allow the user to perform these operations as many times as he wants, and decide when to close the system.


The class: Client.h

The customer's balance will be stored in the cash variable. It is initialized with a value of 0, in the constructor function, that is, the customer obviously starts with 0 of money.

We have the menu() function, which will display the options menu, that is, the screen of the ATM of the banking system. It invokes the action() function, which will call each specific operation.

The first operation is checkBalance(), which will check the balance, the account balance.

Then there is the depositCash() function, which is used to add money to your account.

Finally, the withdraw() function, to withdraw money.

Our class looks like this:

#ifndef CLIENT_H
#define CLIENT_H


class Client
{
    public:
        Client();
        void menu();
        void action(int);
        void checkBalance();
        void depositCash();
        void withdraw();

    private:
        double cash;
};

#endif // CLIENT_H

Source: Client.cpp

Everything will start where you should start, with the Client() constructor function.

It simply clears your money, as it is as if your account was created the moment you instantiated the object.

The menu() function will be displaying a menu of options for the user, and asking him to enter a value. This value is stored in the op variable and sent to the action() function, this is done within a do while loop, which will only end if you type 0. In other words, you only exit the menu, if you type 0 to exit.

The action() will take an action, evaluating the op value received from the menu(). This is done in a switch conditional test.

If it is 0, it says that the system is shutting down.

If it is 1, invoke the checkBalance() function, which will simply display the cash variable. Nothing more.

At depositCash (), we ask the user for a value. But we have to test whether this value is positive, because obviously there is no way to deposit a negative value. This test is done on an IF ELSE. If the value is positive, we simply add it to the cash and display the user's balance.

In the withdraw() function, we ask for the amount the user wants to withdraw. Before removing this value from the cash variable, we need to assess whether it is less than cash. After all, you can only withdraw something below, or at most equal to your balance. This test is done with an IF ELSE as well.

Here's how the code looks:

#include "Client.h"
#include <iostream>
using namespace std;

Client::Client()
{
    cash = 0;
    menu();
}

void Client::menu()
{
    int op=0;

    do{
        cout<<"0. Exit"<<endl;
        cout<<"1. Check balance"<<endl;
        cout<<"2. Deposit money"<<endl;
        cout<<"3. Withdraw money"<<endl;
        cin >> op;

        action(op);

    }while(op);
}

void Client::action(int op)
{
    switch(op)
    {
        case 0:
                cout << "Shutting down system."<<endl;
                break;
        case 1:
                checkBalance();
                break;

        case 2:
                depositCash();
                break;

        case 3:
                withdraw();
                break;

        default:
                cout << "Invalid option"<<endl;
    }

}

void Client::checkBalance()
{
    cout<<"\nYour balance is: $" << cash <<endl;
}

void Client::depositCash()
{
    double val;

    cout<< "Value to deposit: ";
    cin >> val;

    if(val>0){
        cash += val;
        checkBalance();
    }
    else
        cout<<"Invalid value, try again"<<endl;

}

void Client::withdraw()
{
    double val;

    cout<< "Value to withdraw: ";
    cin >> val;

    if(val<=cash){
        cash -= val;
        checkBalance();
    }
    else
        cout<<"Insufficient funds"<<endl;
}

main.cpp

Our main will simply instantiate an object. Automatically it activates the constructor function, and everything starts to happen:

#include "Client.h"

int main()
{
    Client c;
    return 0;
}

See how our main() is simple, clean and to the point.


Hacker challenge

There is a way to hack this system. To add money to your account in a 'hacker' way. Can you find out how it is? Type in the comments.