Mandar um cafézinho para o programador:


Me ajude a transformar café em código!
Mostrando postagens com marcador Programação Orientada a Objetos. Mostrar todas as postagens
Mostrando postagens com marcador Programação Orientada a Objetos. Mostrar todas as postagens

Associação, Composição e Agregação em C++

 Neste tutorial de nosso curso de C++, vamos aprender como diferentes Classes e Objetos se relacionam uns com os outros, e estudar melhor essa relação.

Classes e Objetos na vida real

Até o momento, em nosso curso, usamos classes de maneira bem isolada, criando e usando apenas uma classe. Mas o mundo real, isso raramente ocorre. Vivemos em um mundo de objetos.

Muitas vezes, só faz sentindo existir um objeto, se existirem outros. Por exemplo, podemos ter a classe Humano e a classe Alimento. Os objetos do tipo Humano é você, eu, nossos amigos, familiares...os objetos do tipo Alimento, é o arroz, feijão, carne...tem sentido objetos da classe Humano existirem sem os objetos da classe Alimento? Claro que não.

E mesmo que um objeto não dependa de outro pra existir, ele pode depender de outros pra 'funcionar'. Por exemplo, objetos das classes Motor, Câmbio, Freio etc, trabalham juntos, para fazer um objeto o tipo Carro funcionar.

Ou seja, no mundo real, os objetos não são isolados. Nos seus projetos, você vai sempre criar várias classes, instanciar vários objetos, fazer um criar outro, um enviar informação, outro receber um dado de outro objeto, o método de um objeto usar vários outros objetos, e por ai vai.

Associação, Composição e Agregação

Quando um objeto usa um método ou serviço de outro objeto, dizemos que há uma associação aí. Sim, é algo bem amplo e genérico mesmo. Vamos especificar mais os tipos de relação, em composição e agregação.

Em ambos casos, um objeto possui outro objeto. Porém, na composição, um objeto só existe se o outro existir.

Por exemplo, o objeto do tipo Carro só existe se tiver um objeto da classe Motor. Não existe carro sem motor.

Um objeto do tipo Humano, só existe se tiver objetos como Coração, Pulmão...não existe uma pessoa sem esses órgãos (pelo menos por enquanto, vai que algum programa projeta e cria órgãos no futuro - claro, iria usar C++).

Já na agregação, um objeto possui outro, mas poderia existir sem ele. Por exemplo, todo carro tem um dono. Ou seja, tem objetos do tipo Humano que possuem objetos do tipo Carro. Mas um ser humano não precisa, obrigatoriamente, de um carro para existir.

Você precisa de um coração, uma cabeça....obrigatoriamente. De um carro, não. A relação de um objeto do tipo Carro e de um objeto do tipo Humano, é agregação.

Associação, Composição e Agregação em C++

Na composição, quando um objeto-pai é destruído, os objetos-filhos também serão. Um não existe sem o outro. Na agregação, não. Ou seja, associação é algo bem genérico, depois vem agregação (um pode existir sem o outro) e por fim a composição, a relação mais específica, onde um objeto só existe se outro existir.

Mas vamos deixar essas conversas genéricas de lado e ver na prática algumas relações entre objetos.

Composição em C++

Vamos começar pelo mais específico. Pra facilitar, chamamos as classes Humano e Carro de classes pais. As classes Motor, Coração, Câmbio, Pulmão etc, são as classes filhas, membros ou componentes. Ou chamamos de Todo e Partes, ou Objeto e Membro.

Para se caracterizar como composição, a parte faz parte do todo. Cada parte pertence somente a um objeto (ou seja, o coração pertence somente a um humano, e o motor do carro pertence somente a um carro).

A classe que representa o Todo, é que vai gerenciar as partes, os componentes. Esses membros não sabem da existência do 'Todo'.

Vamos criar a classe Engine, no construtor avisamos que estamos ligando o motor e na função destruidor avisamos que estamos desligando o motor. 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 << "Ligando o motor..."<<endl;
}

Engine::~Engine()
{
    cout << "Desligando o motor..."<<endl;
}

Agora vamos criar a classe Car, que vai fazer quase o mesmo da Engine, vai dizer 'ligando o carro' na construtor e 'desligando o carro' no destruidor. Porém, ele vai ter um membro, um ponteiro pro tipo Engine, a variável 'myEng'.

Na classe, apenas declaramos esse ponteiro, Engine.h:

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

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

#endif // CAR_H

Mas já na implementação, nós instanciamos esse ponteiro, fazendo que o método construtor do objeto 'myEng' seja invocado. Na função destruidor da Car, deletamos o ponteiro do tipo Engine, invocando automaticamente a função destructor, Car.pp:

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

Car::Car()
{
    cout << "Vamos ligar o carro."<<endl;
    myEng = new Engine();
}

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

Nossa main.cpp:

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

int main()
{
    Car myCar;

    return 0;
}

O resultado é o seguinte:

Vamos ligar o carro.
Ligando o motor...
Desligando o motor...
Carro desligado.

O objeto 'myEng' SÓ EXISTE por causa do objeto 'myCar'. Sem o 'myCar', sem 'myEng'. 'myEng' É DO 'myCar'. Um objeto pertence a outro.

Quem 'controla' o 'myEng' também é o 'myCar'. Ele que cria o objeto, instancia...e morreu o objeto da classe Car, morre também o objeto da classe Engine. O objeto 'myEng' pertence somente ao objeto 'myCar', a nenhum outro mais. E ele também não sabe da existência de nenhum outro objeto e não sabe de nada da classe Car.

Essas coisas que caracterizam bem a composição.

Agregação em C++

Vamos agora criar a classe Human, para criar pessoas. Elas que vão dirigir o carro. Para criar um objeto dessa classe, precisamos passar uma string com o nome da pessoa e um objeto do tipo Car, veja como fica a Human.h:

#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

Agora a implementação Human.cpp:

#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.liga();
}

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

Fizemos algumas alterações na Car.h:

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

class Car
{
    public:
        void liga();
        void desliga();
        Engine *myEng;
};

#endif // CAR_H

E na implementação Car.cpp:

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

void Car::liga()
{
    cout << " vai ligar o carro."<<endl;
    myEng = new Engine();
}

void Car::desliga()
{
    cout <<" desligou o carro."<<endl;
    delete myEng;
}

Ou seja, agora temos as funções liga() e desliga(), nos objetos do tipo Car (antes a gente ligava e desligava na construtor e destruidor).

Veja como ficou nossa main.cpp:

#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;
}

Ou seja, criamos um objeto da classe Car e dois da classe Human.
Vamos ver algumas coisas interessantes. O resultado se rodarmos o código acima é:

Neil Peart vai ligar o carro.
Motor ligado.
Neil Peart desligou o carro.
Motor desligado.

Bruce Dickinson vai ligar o carro.
Motor ligado.
Bruce Dickinson desligou o carro.
Motor desligado.

Primeiro, o mesmo objeto 'car' é usado tanto no objeto 'h1' quanto no 'h2'.
Segundo, são os objetos h1 e h2 que acionam as funções ligar() e desligar() do carro, ou seja, eles que manuseiam o objeto 'car'.
Por fim, o objeto 'car' não deixa de existir quando o objeto 'h1' deixou de existir. Ele continuou lá vivo, tanto que foi usado pelo objeto 'h2' logo em seguida. Ou seja, embora haja uma relação entre eles (uns objetos usam outros), eles são de certa forma independentes, diferente da relação entre Car e Engine, onde os motores SÓ EXISTEM enquanto existem os carros, e só é usado um motor para cada carro.

Essas são as características da agregação.

Entre Car e Engine, é composição.
Entre Human e Car, é agregação.

Associação em C++

Deixa eu te contar o maior segredo da linguagem C++: um sistema grande, complexo, como uma estação espacial ou Microsoft Excel, são nada mais nada menos que vários objetos pequenos, desempenhando papéis específicos.

Ou seja, um sistema complexo nada mais é que vários objetos pequenos, trabalhando juntos, como uma associação.

Assim, durante sua carreira como programador C++, procure criar sempre Classes e Objetos específicos, bem definidos, com funções certas e de preferências os mais simples e diretos possíveis.

Repetindo: um grande projeto ou sistema nada mais é que um mooooonte de objetos trabalhando juntos, em associação, seja por composição ou agregação.

Organização é a chave do sucesso para se trabalhar com orientação a objetos!

Exercícios de Orientação a Objetos em C++

 01. Crie uma classe que vai simular um Array de números inteiros, mas um Array sofisticado. A classe deve ter no máximo 100 elementos e preencher os seguintes requisitos:

  • Inserir um elemento ao final do 'array'
  • Inserir um elemento em qualquer local do 'array'
  • Informar o tamanho do 'array' (ou seja, quantos elementos foram inseridos)
  • Informar o valor de qualquer elemento do 'array'
  • Informar a média dos valores do array
  • Informar o maior e o menor valor inserido no array

Sistema de Conta Bancária com Orientação a Objetos em C++

 Neste tutorial, vamos usar todo nosso conhecimento de Programação Orientada a Objetos, e criar um exemplo real de aplicação, uma conta bancária.

Como fazer um sistema de conta bancária
Exercício: Conta Bancária

Crie um sistema em que uma conta bancária de usuário é criada, onde ele pode checar seu saldo, depositar e sacar dinheiro, use os conhecimentos de orientação a objetos.

Seu sistema deve iniciar exibindo um menu com essas opções, e permita que o usuário faça essas operações quantas vezes ele quiser, e decida quando fechar o sistema.


A classe: Client.h

O saldo do cliente vai ficar armazenado na variável cash. Ela é inicializada com valor 0, na função construtor, ou seja, o cliente obviamente começa com 0 de dinheiro.

Temos a função menu(), que vai ficar exibindo o menu de opções, ou seja, a tela do caixa eletrônico do sistema bancário. Ela invoca a função action(), que vai chamar cada operação específica.

A primeira operação é a checkBalance(), que vai checar o balanço, o saldo da conta. 

Depois tem a função depositCash(), que serve pra adicionar dinheiro na sua conta.

Por fim, a função withdraw(), pra sacar dinheiro.

Nossa classe fica assim:

#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

Código-fonte: Client.cpp

Tudo vai começar por onde deve começar, pela função construtor Client().
Ela simplesmente zera seu dinheiro, pois é como se sua conta fosse criada no momento que instanciou o objeto.

A função menu() vai ficar exibindo um menu de opções pro usuário, e pedindo pra ele digitar algum valor. Esse valor é armazenado na variável op e enviado para a função action(), isso é feito dentro de um laço do while, que só terminar se você digitar 0. Ou seja, só sai do menu, se digitar 0 pra sair.

A action() vai tomar uma ação, avaliando o valor op recebido da menu(). Isso é feito num teste condicional switch.
Se for 0, diz que o sistema está encerrando.

Se for 1, invoca a função checkBalance(), que simplesmente vai exibir a variável cash. Nada demais.

Na depositCash(), pedimos um valor ao usuário. Mas temos que testar se esse valor é positivo, pois obviamente não tem como depositar valor negativo. Esse teste é feito num IF ELSE. Se o valor for positivo, simplesmente adicionamos ele ao cash e exibimos o balanço do usuário.

Na função withdraw(), perguntamos o valor que o usuário deseja sacar. Antes de retirar esse valor da variável cash, precisamos avaliar se ele é menor que cash. Afinal, você só pode sacar algo abaixo, ou no máximo igual ao seu saldo. Esse teste é feito com um IF ELSE também.

Veja como fica o código:

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

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

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

    do{
        cout<<"0. Sair"<<endl;
        cout<<"1. Checar saldo"<<endl;
        cout<<"2. Depositar dinheiro"<<endl;
        cout<<"3. Sacar dinheiro"<<endl;
        cin >> op;

        action(op);

    }while(op);
}

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

        case 2:
                depositCash();
                break;

        case 3:
                withdraw();
                break;

        default:
                cout << "Opção inválida"<<endl;
    }

}

void Client::checkBalance()
{
    cout<<"\nSeu saldo atual é: R$" << cash <<endl;
}

void Client::depositCash()
{
    double val;

    cout<< "Valor que deseja depositar: ";
    cin >> val;

    if(val>0){
        cash += val;
        checkBalance();
    }
    else
        cout<<"Valor inválido! Tente novamente"<<endl;

}

void Client::withdraw()
{
    double val;

    cout<< "Valor que deseja sacar: ";
    cin >> val;

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

main.cpp

Nossa main vai simplesmente instanciar um objeto. Automaticamente ele aciona a função construtor, e tudo começa a acontecer:

#include "Client.h"

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

Veja como nossa main() é simples, enxuta e direta ao ponto.


Desafio Hacker

Tem uma maneira de burlar esse sistema. De adicionar dinheiro na sua conta de uma maneira 'hacker'. Você consegue descobrir como é? Digite nos comentários.

Array de Objetos em C++

 Neste tutorial de nosso livro de C++, vamos aprender a criar um array de objetos, bem como manipulá-los.

Criando um array com Objetos em C++

No tutorial passado, criamos uma classe que calculava médias de duas ou três notas, de estudantes. Vamos fazer algo parecido neste tutorial.

Vamos criar a classe Student, e inicialmente ela recebe duas notas. A função construtora seta corretamente as variáveis, e tem uma função chamada getAverage() que retorna a média das duas notas:

#include <iostream>
using namespace std;
class Student
{
    private:
        double grade1, grade2;

    public:
        Student();
        Student(double, double);
        double getAverage();
};

Student::Student()
{
    grade1 = 0;
    grade2 = 0;
}
double Student::getAverage() { return (grade1+grade2)/2; } int main() { Student NeilPeart(10, 8); cout<<NeilPeart.getAverage()<<endl; return 0; }

Note que criamos apenas um objeto, o aluno Neil Peart. Mas que escola tem apenas um aluno? Tem milhares, mas vamos com calma. Vamos trabalhar agora com 5 alunos. Poderíamos fazer:

  • Student alu1, alu2, alu3, alu4, alu5;

Mas, e se fosse um projeto real, com mil alunos? Ia fazer até o alu1000?
Claro que não, né, mané!

Você estudou pelo C++ Progressivo, é uma pessoa esperta e vai logo se lembrar dos nossos amados e queridos arrays, para se trabalhar com muitas variáveis ao mesmo tempo.

Para declarar 5 inteiros, fazemos:

int num[5];

Para declarar 5 chars, fazemos:
char letter[5];

Então adivinha só o que fazemos pra criar 5 objetos da classe Student? Isso mesmo:

  • Student alu[5];
Array de Objetos em C++

Rode o código o código abaixo:

#include <iostream>
using namespace std;

class Student
{
    private:
        double grade1, grade2;

    public:
        Student();
        Student(double, double);
        double getAverage();
};

Student::Student()
{
    grade1 = 0;
    grade2 = 0;
}

Student::Student(double g1, double g2)
{
    grade1 = g1;
    grade2 = g2;
}

double Student::getAverage()
{
    return (grade1+grade2)/2;
}


int main()
{
    Student alu[5];
    cout<<alu[0].getAverage()<<endl;

    return 0;
}

O resultado vai ser 0, pois não inicializamos os objetos do array.

Como inicializar Objetos de um Array

Poderíamos inicializar os objetos um por um, depois de declarar:

    Student alu[5];
    alu[0] = Student(10,9);
    alu[1] = Student(10,8);
    alu[2] = Student(10,7);
    alu[3] = Student(10,6);
    alu[4] = Student(10,5);

Ou poderíamos inicializar logo tudo na hora de instanciar os objetos:
    Student alu[5] = { Student(10,9),
                       Student(10,8),
                       Student(10,7),
                       Student(10,6),
                       Student(10,5) };
Ou até diretamente:
    Student alu[5] = { {10,9},
                       {10,8},
                       {10,7},
                       {10,6},
                       {10,5} };
Se você tiver outras funções construtoras (que recebam uma nota ou 3, por exemplo), também pode colocar diretamente no código, o C++ vai saber chamar a construtor correta. E se você não fornecer nenhuma informação de algum elemento do array, o C++ vai invocar o construtor padrão (é sempre bom ter um, na classe).

Como acessar membros do Array de Objetos

Ok, já declaramos e inicializamos vários objetos de uma vez só, agora precisa aprender a acessar seus membros.

Quando tínhamos um objeto de nome 'name', acessávamos seus membros usando ponto:
  • name.var
  • name.func()

Aqui é a mesma coisa, mas a variável é um elemento de array:
  • arr[0].grade
  • arr[1].getAverage();
Ou seja, mesma coisa, só usar o array bonitinho e o pontinho, seguido do membro da classe/objeto.
O programa a seguir cria 5 objetos, já inicializa com a nota dos alunos e exibe suas médias com a função getAverage():
#include <iostream>
using namespace std;

class Student
{
    private:
        double grade1, grade2;

    public:
        Student();
        Student(double, double);
        double getAverage();
};

Student::Student()
{
    grade1 = 0;
    grade2 = 0;
}

Student::Student(double g1, double g2)
{
    grade1 = g1;
    grade2 = g2;
}

double Student::getAverage()
{
    return (grade1+grade2)/2;
}


int main()
{
    Student alu[5] = { {10,9},
                       {10,8},
                       {10,6},
                       {10,5} };


    cout<<alu[0].getAverage()<<endl;
    cout<<alu[1].getAverage()<<endl;
    cout<<alu[2].getAverage()<<endl;
    cout<<alu[3].getAverage()<<endl;
    cout<<alu[4].getAverage()<<endl;

    return 0;
}
Note que inicializamos apenas os quatro primeiros alunos, então o quinto foi inicializado pelo construtor padrão. Qual foi a média do quinto aluno (alu[4])?

Agora faça outro teste: crie uma classe, que apenas tem o construtor padrão, exibindo uma mensagem na tela. Crie um array de tamanho 10, desses objetos. O que acontece?

Sobrecarga (overload) de Função Construtora

Neste tutorial de nossa apostila C++ Progressivo, vamos aprender sobre overloading, ou sobrecarga, da função constructor.

Sobrecarga de funções construtoras

Várias funções construtoras

Você foi contratado por uma universidade para criar um sistema para ajudar os professores, principalmente para trabalhar com as notas dos alunos.

Como você fez o curso C++ Progressivo, vai criar um sistema fantástico, com diversas classes e objetos comendo solto.
Mas vamos com calma.

Vamos criar uma classe de matemática, chamada Math. Pra simplificar, ela vai fazer apenas uma operação: fazer a média de duas notas, de um aluno, só isso.

A função construtor vai dizer logo de cara a média do aluno.
Veja como fica nosso código:

#include <iostream>
using namespace std;

class Math
{
    public:
        Math(double, double);
};


Math::Math(double g1, double g2)
{
    cout<<"Media: "<<(g1+g2)/2<<endl;
}


int main()
{
    Math ze(10, 8);

    return 0;
}

Resultado deu 9 certinho, tudo bonito e legal, e o aluno Zé ficou com uma ótima média.

E falando de experiência de mercado, é MUUUUUUITO comum os clientes ficarem pedindo alterações, novas funcionalidades etc. Se acostume, é sempre pedidos e reclamações.

Nesse caso, a universidade agora quer que você calcule a média de 3 notas.

Hora, é muito simples, só adicionar mais um parâmetro na lista da função construtor, criar o objeto com as 3 notas e mandar bala, já era, nosso código fica assim:

#include <iostream>
using namespace std;

class Math
{
    public:
        Math(double, double, double);
};


Math::Math(double g1, double g2, double g3)
{
    cout<<"Media: "<<(g1+g2+g3)/3<<endl;
}


int main()
{
    Math ana(10, 9, 10);

    return 0;
}

Tudo certinho e a Ana com média quase perfeita.

Mas aí chega sua universidade e diz: olha, tem aluno que vai fazer duas cadeiras e alunos que vai fazer três cadeiras, ou seja, seu sistema tem que calcular a média em ambos casos.

E agora? Como o construtor vai adivinhar? Uma hora calcula a média dividindo por 2 e no outro exemplo ele dividiu por 3 pois tinha uma variável a mais.

Criar uma classe para cada caso? Claro que não, pois isso é complicação.
Programação foi feito pra tornar o mundo mais fácil! É aí que entra a sobrecarga (overloading) de construidor.


A sobrecarga de Construtores

O C++ tem uma carta na manga: ele te permite criar quantos construtores quiser. Isso mesmo, funções com mesmo nome.
Mas uma coisa tem que ser diferente: a lista de parâmetros.

Exemplo de lista de parâmetros diferentes:

  • Math(double, double)
  • Math(double, int)
  • Math(int, double)
  • Math(double, double, double, float, char, int)
  • ...

Então, no nosso exemplo das médias, basta criar duas funçoes constructors, uma que recebe duas notas e a outra que recebe três notas:

#include <iostream>
using namespace std;

class Math
{
    public:
        Math(double, double);
        Math(double, double, double);
};

Math::Math(double g1, double g2)
{
    cout<<"Media: "<<(g1+g2)/2<<endl;
}

Math::Math(double g1, double g2, double g3)
{
    cout<<"Media: "<<(g1+g2+g3)/3<<endl;
}


int main()
{
    Math ze(10, 8);
    Math ana(10, 9, 10);

    return 0;
}

Veja como o C++ é inteligente. Quando você criou o objeto Zé, só passou dois argumentos, e o objeto invocou a função construidor que só trabalha com dois números.

Quando criou o objeto ana, chamou a outra função construidor, a que trabalha com três valores.
Ou seja, o C++ chama o construtor correto!
Isso é a sobrecarga de construtor.

Agora se você tiver dois construtores com a lista de parâmetros iguais, aí não tem como o C++ adivinhar que função você está querendo invocar, ok?

Vamos aprender um pouco mais sobre sobrecarga quando estudarmos uma classe super especial, a string, que faz loucuras e maravilhas com overloading.

Função Destruidor (destructor) em C++

 Neste tutorial de nosso curso de C++, vamos conhecer e aprender a usar as funções destruidoras.

A função Destruidor (Destructor)

No tutorial passado, falamos sobre a Função Construtor , que existe em toda classe e é executada sempre que um objeto é instanciado.

Analogamente, existe a função destruidor, que é executada apenas quando o objeto é destruído.

Para criarmos uma função destructor, basta definirmos uma função com o mesmo nome da classe, com o símbolo de til ( ~ ) antes. Vamos ver um exemplo, criando uma classe com construtor e destruidor:

#include <iostream>
using namespace std;

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

Test::Test()
{
    cout << "Constructor" << endl;
}

Test::~Test()
{
    cout << "Destructor" << endl;
}

int main()
{
    Test t;

    return 0;
}

O resultado vai ser as duas palavras na tela. Uma ocorre ao criarmos o objeto e outra ocorre quando o programa se encerra, e o nesse caso o destructor também é invocado.
E assim também como os construtores, toda classe tem destruidor. Mesmo que você não defina um, vai existir um padrão em branco, mas vai existir sempre.

Também não aceitam nenhum argumento, nem lista de parâmetros e não retornam nenhuma informação.

Para que serve a Função Destruidor ?

Basicamente, para fazer coisas que devem ser feitas quando um objeto deixa de existir.
Um exemplo muito comum é liberar memória que foi alocada dentro de um objeto.

No momento, estamos usando objetos bobinhos, pequenos, mas o normal é serem gigantes, com classes com centenas de variáveis e dezenas de funções. E lá dentro, é comum fazer alocação dinâmica de memória, e quando esse objeto deixar de existir, é uma boa prática liberar toda essa memória (principalmente em sistemas mais críticos com pouca memória, como seu relógio ou o sistema digital da sua geladeira).

Vamos supor que você criou um sistema pro banco, e quer sempre fazer um teste: se alguém acessou a classe Bank, instanciando algum objeto. Para isso, você define uma variável de escopo global chamada "spy" com valor inicial 0.

Pra verificar se algum objeto foi criado, faça com que 'spy=1' no destructor, veja:

#include <iostream>
using namespace std;

int spy=0;

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


Bank::~Bank()
{
    spy = 1;
}

int main()
{
    Bank *b;
    b = new Bank;

    delete b;

    cout<<"Spy: " << spy << endl;
    return 0;
}

Vamos supor que seja uma classe gigante, com vários membros, várias coisas acontecendo...você criou um objeto, usou ele, fez tudo direitinho e tal. Então chega a hora de dar um delete nele, e nessa hora a função destructor vai ser executada.

O resultado vai ser 'Spy: 1' ali na main(), indicando que alguém mexeu nessa classe Bank. Ou seja, o destruidor, por sempre ser executado, vai executar a operação 'spy=1'.

Um outro exemplo função destructor, é se você criar um jogo e tiver um personagem (que é um objeto) e ele simplesmente morrer ou sair do jogo. Na sua função destruidor, você deleta o nome, tirar seus pontos, tira ele dos times, tira do ranking.

E etc etc, por ai vai pessoal, quase sempre tem coisas pra se fazer quando um objeto deixa de existir, ok?

Função Construtor em C++- Orientação a Objetos

 Neste tutorial, vamos aprender sobre um tipo especial de função, a função Construtora, que desempenha um papel muito importante na orientação a objetos.

A função Construtora do C++

Existe uma função muitíssimo importante e utilizada em orientação a objetos, é a função construtora.
E ela possui algumas características únicas e especiais.

A primeira delas é que seu nome é o mesmo nome da classe. Ou seja, se sua classe se chama "MyClass", a função construtora tem que se chamar "MyClass()", obrigatoriamente, ok?

Outra característica dela é que, assim que instanciamos um objeto, a função construtora é sempre automaticamente invocada!

Por fim, uma última característica, ela não retorna nenhum valor! Não escreva void, int, doublet etc, nada em seu cabeçalho e implementação.

Vamos ver um teste de uma função construtora funcionando? Vamos criar a classe "MyClass" que tem apenas um membro, a função "MyClass()", que simplesmente vai exibir uma mensagem, ela fica assim:

#include <iostream>
using namespace std;

class MyClass
{
    public:
        MyClass();
};

MyClass::MyClass()
{
    cout << "Curso C++ Progressivo"<<endl;
}

int main()
{
    MyClass c;

    return 0;
}

Prontinho! Apenas criamos o objeto 'c' e veja o que apareceu na tela:

Função construtora em C++


Função Construtora: Para que serve?

Ok, já vimos como fazer, como funciona e o resultado...mas pra que serve isso? Só pra mostrar umas mensagens bobinhas na tela?

Claro que não, jovem!

Como o próprio nome diz, ele serve para 'construir' coisas, no caso, ele constrói coisas automaticamente. Quando criamos um objeto, é interessante que ele já faça algumas operações internas de imediato, sem precisarmos sempre fazer tudo manualmente.

A este tipo de construtor, chamamos ele de default, pois ele não recebe nenhum argumento. Porém, a maneira mais comum e útil de usar uma função construtora é com uma lista de parâmetros, que são informações que vem de fora pra dentro do objeto, e vão desempenhar algum papel importante lá dentro.


Função Construtora: Como usar?

Por exemplo, vamos relembrar nossa classe  Rect, que criava retângulos. Lembra que sempre tínhamos que usar as funções setters para definir os valores de comprimento e largura. Que tal se o construtor fizesse isso automaticamente?

Veja como fica:

#include <iostream>
using namespace std;

class Rect
{
    private:
        double length, width;

    public:
        Rect(double, double);
        double getArea();
        double getPerimeter();
};

Rect::Rect(double l, double w)
{
    length = l;
    width = w;
}
double Rect::getArea()
{
    return length * width;
}
double Rect::getPerimeter()
{
    return 2*(length+width);
}

int main()
{
    double len, wid;

    cout<<"Comprimento: ";
    cin >> len;

    cout<<"Largura: ";
    cin >> wid;

    Rect r(len, wid);

    cout<<"Area: " << r.getArea() << endl;
    cout<<"Perimetro: " << r.getPerimeter() << endl;

    return 0;
}

Note que no cabeçalho do construtor, já especificamos que ele vai receber duas variáveis do tipo double.

Na implementação, chamamos essas variáveis de 'l' e 'w', que serão fornecidas mais adiante pelo usuário.. E o que nosso construtor faz? Vai setar os valores corretos de 'length' e 'width'.

Na main, pedimos os valores de comprimento e largura, e depois simplesmente instanciamos um objeto do tipo Rect, de nome 'r'. Note agora que nele enviamos dois argumentos, que são os dois argumentos que a função construtora vai usar!

Pronto, internamente, dentro do objeto, o construtor já fez tudo e as funções getArea() e getPerimeter() já podem ser usadas.

Um pouco mais sobre Função Construtora

Uma outra curiosidade sobre construtores é que eles SEMPRE existem! Mesmo que você não defina nenhum, o C++ vai lá e cria um, vazio, que não faz nada.

Quando você faz:

  • MyClass c;

Ele automaticamente vai invocar o construtor, mas você não vê nada ocorrendo, pois não definiu nenhum construtor.

Outro truque que você pode precisar e usar, é através de ponteiros, declarando um ponteiro de um objeto:

  • MyClass *ptrClass;

Ao fazer isso, o método construtor não será executado, pois não foi criado um objeto, apenas um ponteiro para um objeto.

Agora se fizer isso em seguida:

  • ptrClass = new MyClass;

Aí sim, o objeto foi instanciado e alocado na memória, e o construtor será sumariamente executado.


Ajude o C++ Progressivo

Que tal apoiar e fazer crescer o ensino da programação no Brasil ?

Ajudar nosso país a crescer e se desenvolver cada vez mais, tecnologicamente?

Clica abaixo pra saber mais!

Apoiar o Projeto Progressivo