Agnor's HQ

Aula 15 - Classes - Parte II

Conceitos-Chave:

- construtor, método de uma classe que é executado quando o objecto é criado
- destrutor, método de uma classe que é executado quando o objecto é destruído
- operator, serve para atribuir operadores a classes
- this, um apontador que aponta para o objecto em que está contido

Construtores

Frequentemente um objecto necessita de inicializar algumas variáveis ou alojar memória dinâmica aquando a sua inicialização. Para tal uma classe pode possuir uma função para o efeito, o construtor, que será chamado automaticamente, aquando a iniciação da classe. Por exemplo, imaginemos uma classe Rectangulo:

class Rectangulo
{
public:

    //funções básicas de alterar valores e retorno
    void mudarX(int a) { x = a; }
    void mudarY(int a) { y = a; }
    void mudarAltura(int a) { altura = a; }
    void mudarLargura(int a) { largura = a; }

    void mudarValores(int x_, int y_, int alt, int larg);

    int obterX()    { return x; }
    int obterY()    { return y; }
    int obterAltura() { return altura; }
    int obterLargura() { return largura; }

    //funções de cálculo
    int area() { return largura * altura; }
    int perimetro() { return (largura * 2) + (altura * 2); }

private:

    int x, y;   //coordenadas do canto superior esquerdo do rectangulo
    int altura, largura;    //altura e largura do rectangulo
};

void Rectangulo::mudarValores(int x_, int y_, int alt, int larg)
{
    mudarX(x_);
    mudarY(y_);
    mudarAltura(alt);
    mudarLargura(larg);
}

Exemplo 15.1 - Classe Rectangulo

A classe é perfeitamente funcional, mas será que não necessita de um construtor? Reparem que para fazermos alguma operação (como saber a area, etc.) temos que ter as variáveis definidas. Um construtor assegura que o programador não irá cometer esse erro básico. Criar um construtor é bastante fácil:

Nome_da_Classe();

Se a classe se chamar Rectangulo o construtor deverá ser:

Rectangulo();

Exceptuando o facto de um construtor não retornar nenhum tipo de dados, é tratado como uma função normal. No exemplo acima, da classe Rectangulo, um construtor poderia ser:

class Rectangulo
{
public:

    Rectangulo(int x_, int y_, int alt, int larg);

    //funções básicas de alterar valores e retorno
    void mudarX(int a) { x = a; }
    void mudarY(int a) { y = a; }
    void mudarAltura(int a) { altura = a; }
    void mudarLargura(int a) { largura = a; }

    void mudarValores(int x_, int y_, int alt, int larg);

    int obterX()    { return x; }
    int obterY()    { return y; }
    int obterAltura() { return altura; }
    int obterLargura() { return largura; }

    //funções de cálculo
    int area() { return largura * altura; }
    int perimetro() { return (largura * 2) + (altura * 2); }

private:

    int x, y;   //coordenadas do canto superior esquerdo do rectangulo
    int altura, largura;    //altura e largura do rectangulo
};

Rectangulo::Rectangulo(int x_, int y_, int alt, int larg)
{
    mudarValores(x_, y_, alt, larg);
}

void Rectangulo::mudarValores(int x_, int y_, int alt, int larg)
{
    mudarX(x_);
    mudarY(y_);
    mudarAltura(alt);
    mudarLargura(larg);
}
Exemplo 15.2 - Classe Rectangulo (versão com construtores)

Destrutores

Os destrutores fazem precisamente o contrário dos construtores. Quando a classe é destruída (depende do seu tempo de vida) irá executar o destrutor. Os destrutores são óptimos para desalojar memória dinâmica (e assim evitar muitos esquecimentos).

Tal como os construtores, os destrutores não retornam nenhum valor e são definidos pelo nome da classe, precedido por um til (~):

~Nome_da_Classe();

Vou só deixar um exemplo fácil:

#include <iostream>
using namespace std;

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

Teste::Teste()
{
    cout << "Iniciou o objecto" << endl;
}

Teste::~Teste()
{
    cout << "O objecto foi destruido" << endl;
}

int main()
{
    Teste *t = new Teste;   //também podemos iniciar objectos dinâmicamente

    cin.get();
    cout << "Clique ENTER para destruir o objecto." << endl;

    delete t;

    cin.get();
    return 0;
}
Exemplo 15.3 - Testes com Destrutores

Utilizei memória dinâmica porque podemos destruí-la quando quisermos, embora se utilizasse um bloco, teria o mesmo efeito.

Apontadores para classes

Podemos usar apontadores para classes (ou estruturas) da mesma maneira que utilizamos para uma variável.

Class *teste;

//...

(*teste).x = 3;
(*teste).y = 1;

No entanto, para simplificar mais o código, foi criado um método novo, especificamente para classes ou estruturas:

Class *teste;

//...

teste->x = 3;
teste->y = 1;

Definindo operadores

Vejamos o seguinte pedaço de código:

int x, y, z;

//...


z = x + y;

Não há nada de estranhar neste código: é bastante simples e comum. Vejam então o seguinte:

Classe x, y, z;

//...


z = x + y;

O código acima daria erro a compilar, mas é totalmente legítimo querermos utilizar uma soma se recorrer a métodos mais complexos. Teremos então que utilizar a instrução operator na classe:

#include <iostream>
using namespace std;

class Ponto
{
public:

    int x, y;   //as variaveis são públicas para poupar trabalho

    Ponto operator + (Ponto classe)   //reparem: operator +
    {
        Ponto temp;         //vamos retornar uma classe temporária...
        temp.x = x + classe.x;
        temp.y = y + classe.y;
        return temp;
    }
};

int main()
{
    Ponto a, b, c;

    a.x = 10;
    a.y = 5;

    b.x = 10;
    b.y = 15;

    c = a + b;  //se tudo correr bem, c.x é 20 e c.y também é 20

    cout << "C.x = " << c.x << " e C.y = " << c.y << endl;

    cin.get();
    return 0;
}
Exemplo 15.4 - Classe Ponto (operator +)

Neste exemplo utilizamos a função operator junto com o operador +, que trata da adição (operator +).

Há vários operadores que se podem usar em conjunto com a instrução operator. Entre eles temos: +, -, *, /, [], new, delete, %, etc, e usariamo-los da mesma forma, por exemplo:

Ponto operator - () ou Ponto operator new () ou Ponto operator [] ()

Apontador this

O apontador this é um apontador muito especial, que representa o objecto em si. Vejam o exemplo seguinte:

#include <iostream>
using namespace std;

class Ponto
{
public:

    int x, y;   //as variaveis são públicas para poupar trabalho

    Ponto& operator = (const Ponto classe)  //função para igualar objectos
    {
        x = classe.x;
        y = classe.y;
        return *this;   //ao fazermos return *this estaremos a retornar
                        //o próprio objecto, logo não precisamos de criar
                        //um objecto temporário
    }
};

int main()
{
    Ponto a, b;

    a.x = 10;
    a.y = 5;

    b = a;

    cout << "A:\n\nX = " << a.x << "\nY = " << a.y << endl;
    cout << "\nB:\n\nX = " << b.x << "\nY = " << b.y << endl;

    cin.get();
    return 0;
}
Exemplo 15.5 - Classe Ponto (operator =)

A única confusão que poderão sentir é na expressão Ponto& operator....

Só precisam de saber que, quando precisarem de retornar o apontador this, terão que colocar o & à frente do tipo da função (ou seja, o nome da classe).

Há ainda outro uso (muito útil) para o apontador this. Nos exemplos acima temos, muitas vezes, utilizado alguns nomes de variáveis (x_ por exemplo) para evitar conflitos com os nomes de variáveis que estão dentro da classe. Podemos resolver este problema através do apontador this:

class Rectangulo
{
public:

    Rectangulo(int x, int y, int alt, int larg);

    //funções básicas de alterar valores e retorno
    void mudarX(int x) { this->x = x; }     //notem! o x do objecto será igual
                                            //ao x da função
    void mudarY(int y) { this->y = y; }
    void mudarAltura(int altura) { this->altura = altura; }
    void mudarLargura(int largura) { this->largura = largura; }

    void mudarValores(int x, int y, int alt, int larg);

    int obterX()    { return x; }
    int obterY()    { return y; }
    int obterAltura() { return altura; }
    int obterLargura() { return largura; }

    //funções de cálculo
    int area() { return largura * altura; }
    int perimetro() { return (largura * 2) + (altura * 2); }

private:

    int x, y;   //coordenadas do canto superior esquerdo do rectangulo
    int altura, largura;    //altura e largura do rectangulo
};

Rectangulo::Rectangulo(int x, int y, int alt, int larg)
{
    mudarValores(x, y, alt, larg);
}

void Rectangulo::mudarValores(int x, int y, int alt, int larg)
{
    mudarX(x);
    mudarY(y);
    mudarAltura(alt);
    mudarLargura(larg);
}
Exemplo 15.6 - Classe Rectangulo (usando o apontador this)

Não se esqueçam que os atributos de uma função (o que está entre parentises () ) são locais e apenas existem nessa função. Podemos então fazer this->x = x; que significa a variável x do objecto vai ser igual à variável x (que foi atribuida à função).

Headers

As classes são normalmente colocadas num tipo especial de ficheiros, os headers (*.h), para uma mais fácil reutilização das mesmas. Vou pegar no exemplo da classe Rectângulo e colocá-la em dois ficheiros separados: rectangulo.h (o header, que contém a declaração da classe) e o rectangulo.cpp(que contém o código com as definições dos métodos da classe):

rectangulo.h:

#ifndef _RECTANGULO_H_
#define _RECTANGULO_H_

class Rectangulo
{
public:

    Rectangulo(int x, int y, int alt, int larg);

    //funções básicas de alterar valores e retorno
    void mudarX(int x) { this->x = x; }     //notem! o x do objecto será igual
                                            //ao x da função
    void mudarY(int y) { this->y = y; }
    void mudarAltura(int altura) { this->altura = altura; }
    void mudarLargura(int largura) { this->largura = largura; }

    void mudarValores(int x, int y, int alt, int larg);

    int obterX()    { return x; }
    int obterY()    { return y; }
    int obterAltura() { return altura; }
    int obterLargura() { return largura; }

    //funções de cálculo
    int area() { return largura * altura; }
    int perimetro() { return (largura * 2) + (altura * 2); }

private:

    int x, y;   //coordenadas do canto superior esquerdo do rectangulo
    int altura, largura;    //altura e largura do rectangulo
};

#endif

Exemplo 15.7 - rectangulo.h

rectangulo.cpp

#include "rectangulo.h"

Rectangulo::Rectangulo(int x, int y, int alt, int larg)
{
    mudarValores(x, y, alt, larg);
}

void Rectangulo::mudarValores(int x, int y, int alt, int larg)
{
    mudarX(x);
    mudarY(y);
    mudarAltura(alt);
    mudarLargura(larg);
}

Exemplo 15.7 - rectangulo.cpp

Só há 3 linhas que merecem explicação:

#ifndef _RECTANGULO_H_
#define _RECTANGULO_H_
#endif

Ao fazermos #include "rectangulo.h" estaremos a chamar o conteúdo de rectangulo.h. Ora, no exemplo que dei estamos obrigados a chamá-la pelo menos 2 vezes. Para evitar conflitos de código somos obrigados a incluir estas instruções (do pré-processador, que será dado em aulas futuras). Sigam o seguinte raciocínio:

If Not Defined (ifndef ou Se não está definido) _NOME_QUALQUER_
Define (definir) _NOME_QUALQUER_
...classe...
End If (pára a condição)

Ao dar o nome da classe temos de ter em conta que nunca iremos utilizar o mesmo nome numa variável (ou no pré-processador), por isso normalmente dá-se o nome: NOME_DA_CLASSE_H ou _NOME_DA_CLASSE_H_

Para acabar, um exemplo de como utilizar os dois ficheiros (rectangulo.h e rectangulo.cpp) num programa:

main.cpp:

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

int main()
{
    Rectangulo rect(10, 5, 20, 10);

    cout << "Area antes: " << rect.area() << endl;

    rect.mudarAltura(5);

    cout << "Area depois: " << rect.area() << endl;

    cin.get();
    return 0;
}

Exemplo 15.7 - main.cpp

Final da aula 15 de C++:

Próxima aula -> C++ 16 - Relações entre Classes

Fazer o download do código fonte dos exemplos da aula WinZip
Fazer o download da aula em PDF (Brevemente)

Ir para o topo da página

Aulas de C++

Anúncios