AULA 6 - Programação II - Graduação
CONSTRUTORES
Em uma linguagem de programação qualquer, quando uma variável de um tipo existente, como um inteiro, é declarada, o compilador cria espaço para aquela variável.
Além disso, a linguagem C permite que uma variável seja declarada e inicializada simultaneamente, evitando problemas de utilização de variáveis não inicializadas.
Porém, quando uma variável de um tipo existente sai fora de escopo, o compilador faz com que o espaço para aquela variável seja liberado. Na verdade, o compilador "constrói" e "destrói" a variável.
A linguagem C++ é concebida para fazer tipos definidos pelo usuário (classes). Para permitir uma inicialização direta dos objetos, o compilador necessita de uma função para chamar quando a variável é criada, isto é, o compilador chama um construtor.
Os construtores são muito úteis em tarefas ligadas à inicialização de classes. Nelas pode-se encontrar inicialização direta de membros, alocação dinâmica de memória, recuperação de dados em arquivos, etc.
Atributos não podem ser explicitamente inicializados na declaração da classe – necessidade de métodos de inicialização dos valores dos atributos!
Para automatizar este processo ⇒ Construtores.
Construtores – Funções-membro que têm o mesmo nome da classe e são executadas automaticamente e uma única vez! – quando um objeto é criado.
Sintaxe
O construtor é uma função com o mesmo nome da classe e que não pode retornar nenhum valor. Exceto em casos não usuais, o construtor assume que o espaço foi alocado para todos os atributos na estrutura do objeto quando ele é chamado.
class Nome_Classe
{ ...
Nome_Classe(Lista_Argumentos_inicializ);
...
};
Exemplo:
class Data
{ private: int dia, mes, ano;
public:
int bissexto(); ...
Data(int d, int m, int a); //protótipo do Construtor
...
};
Definição do Construtor
Nome_Classe :: Nome_Classe(Lista_Argumentos_inicializ)
{ ...
CORPO DO CONSTRUTOR
...
}
Exemplo:
Data::Data(int d, int m, int a) // não há tipo de retorno!!
{ ValidaData(d,m,a);
//agora este método também pode ser private!!
}
Execução do Construtor
...
void main() // ou outra função, p.e., Button1Click()
{
Data x(14,6,1992); //cria objeto e passa valores p/ Construtor
x.ImprimeData();
x.ImprimeSigno();
x.ImprimeBissexto();
}
Outros exemplos:
#include <iostream> using namespace std; /* Classe que representa retangulos */ class retangulo { /* Variaveis privadas para representar largura, comprimento, area e perimetro do retangulo */ float larg, comp, area, perim; public: /* Contrutor da classe 'retangulo', chamado somente na declaracao de um objeto do tipo 'retangulo'. Qualquer chamada dessa funcao no resto do codigo ira gerar um erro de compilacao (nao sera gerado um arquivo executavel) */ retangulo(float L, float C); /* Funcao para escrever os valores de 'larg', 'comp', 'area' e 'perim' na tela */ void escreve_tela(); }; /* Definicao do construtor da classe 'retangulo' Construtores devem ter o mesmo nome da classe, e nao retornam nenhum valor */ retangulo::retangulo(float L, float C) { /* 'larg' recebe o valor de 'L', que eh a primeira variavel de entrada */ larg = L; /* 'comp' recebe o valor de 'C', que eh a segunda variavel de entrada */ comp = C; /* Formula da area do retangulo */ area = larg * comp; /* Formula do perimetro do retangulo */ perim = 2 * (larg + comp); } /* Definicao da funcao que escreve os valores de 'larg', 'comp', 'area' e 'perim' na tela */ void retangulo::escreve_tela() { cout << "---------------------------------" << endl; cout << "Largura = " << larg << endl; cout << "Comprimento = " << comp << endl; cout << "Area = " << area << endl; cout << "Perimetro = " << perim << endl; cout << "---------------------------------" << endl; } int main() { /* Criacao de um objeto da classe 'retangulo' Os valores entre parenteses serao assinalados para as variaveis 'L' e 'C' no construtor da classe */ retangulo r(2.0, 3.4); /*Chamada da funcao que escreve os valores de 'larg', 'comp', 'area' e 'perim' na tela */ r.escreve_tela(); /* A chamada abaixo esta errada. Nao podemos chamar o construtor da classe em nenhuma outra parte do codigo que nao seja na declaracao de variaveis e objetos. Caso voce "des-comente" a linha abaixo, o codigo nao ira compilar*/ //r.retangulo(10.0, 3.4); /* Fim do codigo */ return 0; }
#include <iostream> using namespace std; /* Classe que representa retangulos */ class retangulo { /* Variaveis privadas para representar largura, comprimento, area e perimetro do retangulo */ float larg, comp, area, perim; public: /* Contrutor da classe 'retangulo', chamado somente na declaracao de um objeto do tipo 'retangulo'. Qualquer chamada dessa funcao no resto do codigo ira gerar um erro de compilacao (nao sera gerado um arquivo executavel) */ retangulo(float L, float C); /* Outro contrutor da classe 'retangulo', chamado somente na declaracao de um objeto do tipo 'retangulo'. Qualquer chamada dessa funcao no resto do codigo ira gerar um erro de compilacao (nao sera gerado um arquivo executavel) */ retangulo(); /* Funcao para definir os valores de 'larg', 'comp', 'area' e 'perim', de acordo com os valores de entrada 'L' (para a largura) e 'C' (para o comprimento do retangulo)*/ void define_ret(float L, float C); /* Funcao para escrever os valores de 'larg', 'comp', 'area' e 'perim' na tela */ void escreve_tela(); }; /* Definicao do construtor da classe 'retangulo' Construtores devem ter o mesmo nome da classe, e nao retornam nenhum valor */ retangulo::retangulo(float L, float C) { /* 'larg' recebe o valor de 'L', que eh a primeira variavel de entrada */ larg = L; /* 'comp' recebe o valor de 'C', que eh a segunda variavel de entrada */ comp = C; /* Formula da area do retangulo */ area = larg * comp; /* Formula do perimetro do retangulo */ perim = 2 * (larg + comp); } /* Definicao de outro construtor da classe 'retangulo' Este construtor ser· chamado quando um objeto da classe 'retangulo' for declarado sem nenhum valor de entrada, Nesse caso, vamos definir 'larg', 'comp', 'area' e 'perim' iguais a zero. Construtores devem ter o mesmo nome da classe, e nao retornam nenhum valor */ retangulo::retangulo() { larg = comp = area = perim = 0; } /* Definicao da funcao que define os valores de 'larg', 'comp', 'area' e 'perim', de acordo com os valores de entrada 'L' (para a largura) e 'C' (para o comprimento do retangulo)*/ void retangulo::define_ret(float L, float C) { /* 'larg' recebe o valor de 'L', que eh a primeira variavel de entrada */ larg = L; /* 'comp' recebe o valor de 'C', que eh a segunda variavel de entrada */ comp = C; /* Formula da area do retangulo */ area = larg * comp; /* Formula do perimetro do retangulo */ perim = 2 * (larg + comp); } /* Definicao da funcao que escreve os valores de 'larg', 'comp', 'area' e 'perim' na tela */ void retangulo::escreve_tela() { cout << "---------------------------------" << endl; cout << "Largura = " << larg << endl; cout << "Comprimento = " << comp << endl; cout << "Area = " << area << endl; cout << "Perimetro = " << perim << endl; cout << "---------------------------------" << endl; } int main() { /* Criacao de um objeto da classe 'retangulo' Os valores entre parenteses serao assinalados para as variaveis 'L' e 'C' no primeiro construtor da classe */ retangulo r1(2.0, 3.4); /* Criacao de outro objeto da classe 'retangulo' Como nao ha nenhum valor de entrada, sera chamado o segundo construtor, 'retangulo()' */ retangulo r2; /*Chamada da funcao que escreve os valores de 'larg', 'comp', 'area' e 'perim' na tela. Como 'r2' foi declarado pelo segundo construtor, todas as suas variaveis devem estar iguais a zero. */ cout << "---------------------------------" << endl; cout << "r2 no inicio do codigo:\n"; r2.escreve_tela(); /*Chamada da funcao que define os valores de 'larg', 'comp', 'area' e 'perim' para 'r2'. */ r2.define_ret(10, 20); /*Chamada da funcao que escreve os valores de 'larg', 'comp', 'area' e 'perim' na tela. Agora, 'r2' tera valores novos definidos para as suas variaveis */ cout << "r2 atualizado:\n"; r2.escreve_tela(); /*Chamada da funcao que escreve para 'r1' os valores de 'larg', 'comp', 'area' e 'perim' na tela */ cout << "r1:\n"; r1.escreve_tela(); /* Fim do codigo */ return 0; }
Exercício
Expanda a classe DiadoAno para que esta tenha um construtor. Dica: neste caso, não será mais necessário nenhum método público de leitura de dados
Sugestão de solução: Classe DiadoAno com Construtor
SOBRECARGA DE CONSTRUTORES
É possível, e recomendável, criar-se dois ou mais códigos distintos ao mesmo Construtor - garantir mais de uma forma de inicialização de parâmetros ao usuário, por exemplo.
Para tanto ⇒ incluir os diferentes protótipos na declaração da classe e em seguida, definir os diferentes códigos.
Exemplo:
class Data
{ ...
Data(int d, int m, int a); //Construtor
Data( ); //Construtor sobrecarregado
...
};
//Definição dos construtores:
Data::Data(int d, int m, int a)
{ ValidaData(d, m, a);
}
//
Data::Data( )
{ int d, m, a;
cout << “\n Dia: “; cin >> d;
cout << “\n Mês: “; cin >> m;
cout << “\n Ano: “; cin >> a;
ValidaData(d, m, a);
}
Execução da classe:
void main( ) // ou outra função
{
Data x(14, 6, 1992), z; //cria dois objetos: x e z
x.ImprimeData( );
x.ImprimeBissexto( );
z.ImprimeData( );
z.ImprimeBissexto( );
}
Exercício 2
Expanda agora a classe DiadoAno para que esta tenha um construtor padrão, com os 4 argumentos, e um sobrecarregado, para nenhum argumento.
Sugestão de solução: Classe DiadoAno com Construtor Sobrecarregado
OBSERVAÇÕES SOBRE CONSTRUTORES
1. Um construtor pode dar DIRETAMENTE valores default aos atributos, na declaração da classe
Ex:
class Horario
{ private:
int hora, minuto, segundo;
public:
Horario( ) //Construtor
{ hora=minuto=segundo= 0; } //inic. direta de atribs.
void SetaHora(int, int, int);
void ImprimeMilitar( );
void ImprimePadrao( );
};
2. Em C/C++ os construtores não podem ser chamados explicitamente pelo programador
Contra-ex:
int main( )
{ Horario h;
...
Horario( ); //ERRO!!!
...
}
3. É permitido definir-se um construtor de corpo vazio - na prática: impede que outro construtor seja executado automaticamente para o objeto. Ex:
...
Horario( ) { }
...
4. Construtores com argumentos default - outra alternativa para a proibição de se inicializar atributos em uma classe
Construtores default podem ser invocados em parâmetros (na verdade já definidos). Porém só poderá haver um construtor default por classe! Ex:
class Horario
{ public:
Horario(int = 0, int = 0, int = 0); //Construtor default
...
};
Corpo do construtor default:
Horario::Horario(int h, int m, int s)
{ SetaHora(h, m, s); }
Execução do construtor default:
void main()
{ Horario h1, h2(2), h3(21,34), h4(12,25,42);
...
}
Contra-Ex:
class Horario
{ public:
Horario(int = 0, int = 0, int = 0);
Horario ( ); //ERRO !!!
...
};
DESTRUTORES
Quando um objeto sai fora do seu escopo, um destrutor pode ser chamado.
Embora a liberação de memória seja uma aplicação bastante freqüente para funções destrutoras, esta não é sua única utilidade. Uma segunda utilidade para funções destrutoras pode ser a finalização de dispositivos ou subsistemas que tenham sido ativados como classes.
Por exemplo, ao terminar a impressão pode-se finalizar a impressora desativando eventuais modos de impressão ajustados pelo sistema. Outra utilidade para a função destrutora, às vezes associada ao gerenciamento dinâmico de memória, está na prática de gravar os dados ao se encerrar o escopo dos mesmos (ou antes de liberá-los da memória).
Se o programador não proporcionar construtores (pode haver mais de um) e um destrutor (só pode haver um) para uma classe, o compilador assume as ações mais simples. Em outras palavras, caso não seja definido um construtor e um destrutor para a classe, o compilador cria um construtor e um destrutor default, sem código e sem parâmetros, que são chamados, respectivamente, a cada declaração de instância, e cada vez que a instância sai fora do escopo.
O nome do destrutor é o da classe com um til (~) anexado no início. O destrutor nunca pega nenhum argumento; ele só é chamado pelo compilador e não pode ser chamado explicitamente pelo programador.
Como geralmente o desejado é fazer vários tipos de inicialização num objeto, o "destrutor padrão" (fazendo nada) é muitas vezes suficiente e não é necessário definir um destrutor.
Em resumo:
- São métodos que liberam a memória alocada para o objeto.
- Também utilizam o mesmo nome da classe, mas precedido por um ‘~’ (til).
- Tal como o construtor, o destrutor não pode ter valor de retorno e nem pode ser chamado explicitamente.
- Ao contrário dos construtores, Destrutores não aceitam argumentos e nem podem ser sobrecarregados.
Exemplo:
class CriaEDestroi
{ public:
CriaEDestroi (int);
~CriaEDestroi( );
private:
int numero;
};
//
CriaEDestroi::CriaEDestroi(int valor)
{ numero = valor;
cout << “Construtor do objeto ” << numero; }
//
CriaEDestroi::~CriaEDestroi( ) //sem tipo e sem argumentos
{ cout << “ Destrutor do objeto “ << numero << endl; }
Entretanto, se um objeto inicializa algum hardware ou muda algum valor global, pode ser preciso desfazer o efeito do objeto quando este é
destruído. Para isso precisa-se de um destrutor.
Exemplo de Execução:
CriaEDestroi primeiro(1); // cria um objeto global
//
int main( )
{ cout << “(global criado antes de main) “ << endl;
CriaEDestroi segundo(2); //cria objeto local
cout << “(local em main) “ << endl;
cria( );
CriaEDestroi terceiro(3); //outro objeto local
cout << “(outro local em main)” << endl;
return 0;
};
//
void cria (void)
{ CriaEDestroi quarto(4);
cout << “(local em cria)” << endl; }
#include <iostream> using namespace std; /* Classe generica */ class generica { /* String para identificar cada objeto */ string titulo; public: /* Declaracao do construtor para a classe 'generica' */ generica(string s); /* Declaracao do destrutor para a classe 'generica' */ ~generica(); }; /* Definicao do construtor da classe 'retangulo' Construtores devem ter o mesmo nome da classe, e nao retornam nenhum valor */ generica::generica(string s) { titulo = s; cout << "------------------------------------------------\n"; cout << "Construcao do objeto --- " << titulo << endl; cout << "------------------------------------------------\n"; cout << endl; } /* Definicao do destrutor da classe 'retangulo' Destrutores devem ter o mesmo nome da classe, antecedidos de um '~', e nao retornam nenhum valor */ generica::~generica() { cout << "------------------------------------------------\n"; cout << "Destruicao do objeto --- " << titulo << endl; cout << "------------------------------------------------\n"; cout << endl; } int main() { /* Criacao de tres objetos da classe 'generica'. Cada objeto tera uma string diferente para sua variavel 'titulo' */ generica OBJ1("PRIMEIRO OBJ"), OBJ2("SEGUNDO OBJ"), OBJ3("TERCEIRO OBJ"); cout << "\n------------------------------------------------\n"; cout << "Instrucoes no meio do codigo de main()\n"; cout << "------------------------------------------------\n\n"; /* Fim do codigo da funcao 'main()'*/ return 0; /* Apos o 'return' acima, os destrutores dos objetos 'OBJ1', 'OBJ2' e 'OBJ3' serao chamados*/ }
/* Aplicacao pratica de destrutores - desalocar memoria dinamica */ #include <iostream> using namespace std; /* Classe que representa vetores inteiros N-dimensionais */ class vetor { public: /* Tamanho do vetor */ int N; /* Ponteiro que indicara as N posicoes do vetor de inteiros */ int *vet; /* Construtor da classe*/ vetor(int tamanho); /* Destrutor da classe */ ~vetor(); /* Funcao para escrever o vetor na tela */ void escreve_tela(); }; /* Construtores da classe 'vetor' */ vetor::vetor(int tamanho) { N = tamanho; vet = new int [N]; } /* Destrutor da classe 'vetor'. Com ele, nao precisamos nos preocupar em desalocar a memoria de 'vet' */ vetor::~vetor() { N = 0; delete [] vet; cout << "-------------------------------------------------\n"; cout << "Destruicao de um vetor - desalocando memoria\n"; cout << "-------------------------------------------------\n"; } /* Funcao para escrever na tela o vetor 'vet' da classe 'vetor' */ void vetor::escreve_tela() { int j; for(j=0;j<N;j++) cout << vet[j] << " "; cout << endl; } int main() { int j; int tam = 10; /* Criacao de dois objetos do tipo 'vetor'. Ambos terao 'tam' posicoes. */ vetor A(tam), B(tam); /* Definicao dos valores dos vetores. Valores escolhidos somente para ilustracao.*/ for(j=0; j< tam; j++) { A.vet[j] = 2*j; B.vet[j] = (tam-j)*3; } /* Visualizar na tela os valores dos vetores */ cout << "\n------------------------------------------------\nA = "; A.escreve_tela(); cout << "------------------------------------------------\n\n\n"; cout << "------------------------------------------------\nB = "; B.escreve_tela(); cout << "------------------------------------------------\n\n"; /* Fim do codigo da funcao 'main()'*/ return 0; /* Apos o 'return' acima, os destrutores dos objetos 'A' e 'B' serao chamados*/ }
Exercícios
- Crie a classe Retangulo, o qual contém os atributos privados x1, y1, x2 e y2 (coordenadas do ponto superior esquerdo e do ponto inferior direito, respectivamente), os métodos privados Calcula_Area, Calcula_Perimetro e o método público Desenha.
Teste, para esta classe, os seguintes métodos construtores:
- Construtor simples, com argumentos; Solucao 1 Exercicio Retangulo
- Construtor sobrecarregado, sem argumentos; Solucao 2 Exercicio Retangulo
- Construtor default. Solucao 3 Exercicio Retangulo
DICA: você pode utilizar os métodos ShowMessage(), para mandar mensagens ao usuário e a propriedade Canvas (com os métodos MoveTo(x,y) e LineTo(x,y) do formulário ou de um quadro de imagem, para traçar o retângulo.


