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);
}
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);
}
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;
}
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;
}
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;
}
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);
}
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
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);
}
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;
}
Próxima aula -> C++ 16 - Relações entre Classes
Fazer o download do código fonte dos exemplos da aula
Fazer o download da aula em PDF (Brevemente)
Ir para o topo da página