AULA 6 - Programação II - Graduação

De IFSC
Revisão de 11h32min de 17 de agosto de 2017 por imported>Fargoud (→‎OBSERVAÇÕES SOBRE CONSTRUTORES)
Ir para navegação Ir para pesquisar

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


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( );
}
Execconstrutores.png


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);
  ...
}
Execconstrutores2.png

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.

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.

#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*/
}