Programar em C++/Herança: diferenças entre revisões

Origem: Wikilivros, livros abertos por um mundo aberto.
[edição não verificada][edição não verificada]
Conteúdo apagado Conteúdo adicionado
Linha 474: Linha 474:
derivada(int x, int y, int z): base1(y), base2(z)
derivada(int x, int y, int z): base1(y), base2(z)
{ j = x; cout << "Construindo derivada\n"; }
{ j = x; cout << "Construindo derivada\n"; }
~derived() { cout << "Destruindo derivada\n"; }
~derivada() { cout << "Destruindo derivada\n"; }
void mostrar() { cout << i << " " << j << " " << k << "\n"; }
void mostrar() { cout << i << " " << j << " " << k << "\n"; }
};
};

Revisão das 13h01min de 28 de janeiro de 2010

<< Classes Encapsulamento >>
Esta página precisa ser reciclada (discuta).
Ao melhorá-la, você estará ajudando o Wikilivros.

Conceito

Herança é um dos pontos chave de programação orientada a objetos (POO). Ela fornece meios de promover a extensibilidade do código, a reutilização e uma maior coerência lógica no modelo de implementação. Estas características nos possibilitam diversas vantagens, principalmente quando o mantemos bibliotecas para uso futoro de determinados recursos que usamos com muita frequência.

Uma classe de objetos "veiculo", por exemplo, contém todas as características inerentes aos veículos, como: combustível, autonomia, velocidade máxima, etc. Agora podemos dizer que "carro" é uma classe que têm as características básicas da classe "veículo" mais as suas características particulares. Analisando esse fato, podemos concluir que poderíamos apenas definir em "carro" suas características e usar "veículo" de alguma forma que pudéssemos lidar com as características básicas. Este meio chama-se herança.

Agora podemos definir outros tipos de veículos como: moto, caminhão, trator, helicóptero, etc, sem ter que reescrever a parte que está na classe "veículo". Para isso define-se a classe "veículo" com suas características e depois cria-se classes específicas para cada veículo em particular, declarando-se o parentesco neste instante.

Outro exemplo: Imagine que já exista uma classe que defina o comportamento de um dado objeto da vida real, por exemplo, animal. Uma vez que eu sei que o leão é um animal, o que se deve fazer é aproveitar a classe animal e fazer com que a classe leão derive (herde) da classe animal as características e comportamentos que a mesma deve apresentar, que são próprios dos indivíduos classificados como animais.

Ou seja, herança acontece quando duas classes são próximas, têm características mútuas mas não são iguais e existe uma especificação de uma delas. Portanto, em vez de escrever todo o código novamente é possível poupar algum tempo e dizer que uma classe herda da outra e depois basta escrever o código para a especificação dos pontos necessários da classe derivada (classe que herdou).

Sintaxe

Para declarar uma classe derivada de outra já existente, procedemos de forma a declarar o parentesco e o grau de visibilidade (acesso) que a classe derivada terá dos membros de sua classe base. Para isso seguimos o seguinte código sintático:

 class classe_derivada : [<acesso>] classe_base {
  //corpo da classe derivada
 }

Repare que temos o operador ":" ( dois pontos ) como elo de ligação entre as duas classes. Este operador promove o "parentesco" entre as duas classes quando é usado na declaração de uma classe derivada.

O termo [<acesso>] é opcional, mas se estiver presente deve ser public, private ou protected. Ele define o grau de visibilidade dos membros da classe base quando a classe derivada precisar acessá-los.

Exemplo de implementação:

 // Demonstra herança.
 #include <iostream>
 using namespace std;
 class veiculo_rodoviario // Define uma classe base veículos.
    {
        int rodas;
        int passageiros;
    public:
        void set_rodas(int num) { rodas = num; }
        int get_rodas() { return rodas; }
        void set_pass(int num) { passageiros = num; }
        int get_pass() { return passageiros; }
    };
 class caminhao : public veiculo_rodoviario // Define um caminhao.
    {
          int carga;
    public:
           void set_carga(int size) { carga = size; }
           int get_carga() { return carga; }
           void mostrar();
    };
 enum tipo {carro, van, vagao};
 class automovel : public veiculo_rodoviario // Define an automoble.
    {
        enum tipo car_tipo;
    public:
        void set_tipo(tipo t) { car_tipo = t; }
        enum tipo get_tipo() { return car_tipo; }
        void mostrar();
    };
 void caminhao::mostrar()
        {
        cout << "rodas: " << get_rodas() << "\n";
        cout << "passageiros: " << get_pass() << "\n";
        cout << "carga (capacidade em litros): " << carga << "\n";
        }
 void automoveis::mostrar()
        {
        cout << "rodas: " << get_rodas() << "\n";
        cout << "passageiros: " << get_pass() << "\n";
        cout << "tipo: ";
        switch(get_tipo()) 
                {
                case van: cout << "van\n";
                          break;
                case car: cout << "carro\n";
                          break;
                case vagao: cout << "vagao\n";
                }
        }
 int main()
 {
    caminhao t1, t2;
    automovel c;
    t1.set_rodas(18);
    t1.set_pass(2);
    t1.set_carga(3200);
    t2.set_rodas(6);
    t2.set_pass(3);
    t2.set_carga(1200);
    t1.mostrar();
    cout << "\n";
    t2.mostrar();
    cout << "\n";
    c.set_rodas(4);
    c.set_pass(6);
    c.set_tipo(van);
    c.mostrar();
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }

Na implementação acima temos a classe base veiculo_rodoviario e duas classes derivadas “:” caminhao e automovel. Podemos notar que as características comuns a todos os tipos de veículos, rodas e passageiros, estão na classe base, enquanto as características exclusivas de cada tipo de veículo estão nas classes derivadas. Desta forma podemos definir procedimentos especializados para cada classe, fazendo com que todas as eventuais modificações feitas ao longo da implementação na classe base sejam estendidas a todos os objetos criados a partir das classes derivadas no programa.


Repare ainda um pormenor: tanto a classe "caminhao" quanto a automovel têm como função membro o método show(), mas uma não interfere com a outra. Isto ilustra um outro aspecto da orientação a objeto, o polimorfismo. Este será exposto em mais detalhe nos capítulos subsequentes.

Controle de acesso à classe base

Quando uma classe herda outra, os membros da classe base são incorporados como membros da classe derivada. Devido à separação das classes e do controle de acesso às variáveis em cada classe, devemos pensar como as restrições de acesso são gerenciadas em classes diferentes, principalmente o acesso a membros da classe base a partir das classes derivadas.

O acesso dos membros da classe base à classe derivada é determinado pelo especificador de acesso: public, private e protected. Por "defaut" (padrão) temos o private, ou seja, como temos a opção de não explicitar o especificador de acesso, se este não estiver presente o compilador usará "private" durante a interpretação do código.

Assim ficamos com as possíveis combinações


  • Classe base herdada como public:


    • Membros públicos (public) da classe base:
      • É como se copiássemos os membros da classe base e os colocássemos como "public" na classe derivada. No final, eles permanecem como públicos.

    • Membros privados (private) da classe base:
      • Os membros estão presentes na classe, porém ocultos como privados. Desta forma as informações estão presentes, mas só podem ser acessadas através de funções publicas ou protegidas da classe base.

    • Membros protegidos (protected) da classe base:
      • Se tivermos membros protegidos (protected) na classe derivada, eles se comportam como se tivessem sido copiados para a classe derivada como protegidos (protected).



  • Classe base herdada como private:


    • Membros públicos (public) da classe base:
      • Os membros se comportam como se tivessem sido copiados como privados (private) na classe derivada.

    • Membros privados (private) da classe base:
      • Os membros estão presentes na classe, porém ocultos como privados. Desta forma as informações estão presentes, mas não poderão ser acessadas, a não ser por funções da classe base que se utilizem delas.

    • Membros protegidos (protected) da classe base:
      • Os membros se comportam como se tivessem sido copiados como privados (private) na classe derivada.



  • Classe base herdada como Protected:


    • Membros públicos (public) da clase base:
      • Se comportam como se tivéssemos copiado-os como protegidos (protected) na classe derivada

    • Membros privados (private) da classe base:
      • Os membros estão presentes na classe, porém ocultos como privados. Desta forma as informações estão presentes, mas não poderão ser acessadas, a não ser por funções da classe base que se utilizem delas.

    • Membros protegidos (protected) da classe base:
      • Se comportam como se estivéssemos copiado-os como protegidos (protected) na classe derivada.

Em suma, estas regras podem ser sintetizadas em uma regra muito simples: Prevalece o atributo mais restritivo. Para isto basta-nos listar os atributos por ordem de restrições decrescente:

  1. private
  2. protected
  3. public

Assim, temos todas as combinações definidas quando colocamos um atributo combinado com o outro, bastando para isto escolher sempre o mais restritivo na combinação. Por exemplo: Quando temos uma variável pública na base e a herança é privada, a combinação resulta em uma variável privada na classe derivada.

Aqui está um exemplo muito simples:

 #include <iostream>

 using namespace std;

 class base 
 {
      int i, j;
 public:
       void set(int a, int b) { i = a; j = b; }
       void show() { cout << i << " " << j << "\n"; }
 };

 class derived : public base 
 {
      int k;
 public:
       derived(int x) { k = x; }
       void showk() { cout << k << "\n"; }
 };

 int main()
 {
    derived ob(3);
    ob.set(1, 2); // acesso a membro da base
    ob.show(); // acesso a membro da base
    ob.showk(); // uso de membro da classe derivada
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }

Conseguimos acessar as funções set() e show() porque são heradadas como publicas.

Agora modifiquemos o atributo de acesso na declaração da herança da classe base:

 #include <iostream>

 using namespace std;

 class base 
 {
      int i, j;
 public:
       void set(int a, int b) { i = a; j = b; }
       void show() { cout << i << " " << j << "\n"; }
 };

 class derived : private base 
 {
      int k;
 public:
       derived(int x) { k = x; }
       void showk() { cout << k << "\n"; }
 };

 int main()
 {
    derived ob(3);
    ob.set(1, 2); // Erro, não é possível acessar set()
    ob.show(); // Erro, não é possível acessar show()
    ob.showk(); // uso de membro da classe derivada
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }

Agora já não podemos acessar as funções porque estão privadas.

Heranças múltiplas

Podemos ter a situação em que uma classe derivada possa herdar membros de várias classes base. Esta característica é uma distinção entre C++ e outras linguagens orientadas a objeto. Este recurso dá mais poder de modelagem ao programador, mas vale a pena lembrar que mais poder exige mais cautela no uso.

 // Um exemplo de múltiplas classes base.
 #include <iostream>

 using namespace std;

 class base1 
    {
    protected:
              int x;
    public:
           void showx() { cout << x << "\n"; }
    };

 class base2 
    {
    protected:
              int y;
    public:
           void showy() { cout << y << "\n"; }
    };

 class derived: public base1, public base2 // Inherit multiple base classes.
    {
    public:
           void set(int i, int j) { x = i; y = j; }
    };

 int main()
 {
    derived ob;
    ob.set(10, 20); // Disponível pela classe "derived"
    ob.showx(); // Pela classe base1
    ob.showy(); // Pela classe base2
#ifndef WIN32
    system ("pause");
#endif
    return 0;
 }

Repare que utilizamos o operador vírgula para dizer ao compilador que a classe derivada herda mais de uma classe. Com efeito, temos uma lista de classes separadas por vírgulas depois do operador ":" (dois pontos).

Quando queremos que a classe derivada herde uma classe como pública e outra como privada ou protegida basta preceder a classe com o seu especificador de acesso. Da mesma forma, a omissão do especificador leva o compilador a usar o padrão que é privado (private).

Construtores e destrutores

Temos uma série de classes que mantém relações de parentesco conforme mostramos nas seções anteriores. Em termos genéricos, classes que herdam características de uma base precisam de regras claras quando forem criadas e destruídas. Precisamos definir a sequência em que os construtores e destrutores serão chamados, uma vez que cada classe tem pelo menos um construtor e um destrutor.

Agora temos a questão: Quando é que os construtores são chamados quando eles são herdados?

  • Quando um objeto da classe derivada é instanciado, o construtor da classe base é chamado primeiro seguido do construtor das classes derivadas, em sequência da base até a última classe derivada.
  • Quando o objeto da classe derivada é destruído, o seu destrutor é chamado primeiro seguido dos destrutores das outras classes derivadas logo abaixo, em sequência até a base.

Vamos testar e ver como isto funciona.

No caso em que termos herança sequencial A-B-C, teremos:

 #include <iostream>

 using namespace std;

 class base 
    {
    public:
           base() { cout << "Construindo base" << endl; }
           ~base() { cout << "Destruindo base" << endl; }
    };

 class derivada1 : public base 
    {
    public:
           derivada1() { cout << "Construindo derivada1" << endl; }
           ~derivada1() { cout << "Destruindo derivada1" << endl; }
    };

 class derivada2: public derivada1 
    {
    public:
           derived2() { cout << "Construindo derivada2\n"; }
           ~derived2() { cout << "Destruindo derivada2\n"; }
    };

 int main()
 {
    derivada2 ob; // constrói e destrói o objeto ob
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }


Caso de múltipla herança A - B e C

 #include <iostream>

 using namespace std;

 class base1 
         {
         public:
               base1() { cout << "Construindo base1\n"; }
               ~base1() { cout << "Destruindo base1\n"; }
        };

 class base2 
        {
        public:
               base2() { cout << "Construindo base2\n"; }
               ~base2() { cout << "Destruindo base2\n"; }
        };

 class derived: public base2,public base1
        {
        public:
               derived() { cout << "Construindo derivada\n"; }
               ~derived() { cout << "Destruindo derivada\n"; }
        }; 

 int main()
 {
    derivada ob;// construindo e destruindo o objeto.
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }

Passando parâmetros para construtores da classe base

Agora imaginemos que temos um conjunto de bases para uma classe que queiramos derivar, então podemos ter um construtor em cada base que precise de parâmetros para que possa ser invocado pela nossa classe. Como poderemos passar os parâmetros, uma vez que os mesmos só podem ser passados durante a inicialização da classe?

Para que possamos passar os parâmetros para as classes bases durante a inicialização do objeto da classe derivada temos o recurso de passagem de parâmetros pelo construtor. Basicamente, ele funciona como se passássemos valores para variáveis membro. Chamamos cada construtor na lista de passagem de valores, a sintax para declarar o corpo do construtor é a seguinte:

class Classe_derivada : public Base1, public Base2, ..., public BaseN
{ // Membros...
  public:
     Classe_derivada(lista_de_argumentos);
  // Outras funções...
};


 Classe_derivada::Classe_derivada(lista_de_argumentos) : Base1(lista_de_argumentos), Base2(lista_de_argumentos), ...BaseN(lista_de_argumentos);
 {
  //Corpo do construtor da classe derivada
 }

Este exemplo é um pouco mais complexo, atenção!

 #include <iostream>

 using namespace std;

 class base 
        {
        protected:
                  int i;
        public:
               base(int x) { i = x; cout << "Construindo base\n"; }
               ~base() { cout << "Destruindo base\n"; }
        };

 class derivada: public base 
        {
              int j;
        public:
               derivada(int x, int y): base(y) { j = x; cout << "Construindo derivada\n"; }// derivada usa x; y é passada em lista para a base.
               ~derivada() { cout << "Destruindo derivada\n"; }
               void mostrar() { cout << i << " " << j << "\n"; }
        };

 int main()
 {
    derivada ob(3, 4);
    ob.mostrar(); // mostra 4 3
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }

No exemplo, a o construtor da classe derivada é declarado com 2 argumentos (x e y). no entanto a função derivada() usa apenas um para inicializar a variável interna da classe, o segundo argumento é usado para passar o valor de inicialização para a classe base.

Vejamos mais um exemplo:

 #include <iostream>

 using namespace std;

 class base1 
    {
    protected:
              int i;
    public:
           base1(int x) { i = x; cout << "Construindo base1\n"; }
           ~base1() { cout << "Destruindo base1\n"; }
    };

 class base2 
    {
    protected:
              int k;
    public:
           base2(int x) { k = x; cout << "Construindo base2\n"; }
           ~base2() { cout << "Destruindo base2\n"; }
    };

 class derivada: public base1, public base2 
    {
         int j;
    public:
           derivada(int x, int y, int z): base1(y), base2(z)
                       { j = x; cout << "Construindo derivada\n"; }
           ~derivada() { cout << "Destruindo derivada\n"; }
           void mostrar() { cout << i << " " << j << " " << k << "\n"; }
    };

 int main()
 {
    derivada ob(3, 4, 5);
    ob.mostrar(); // mostra 4 3 5
#ifdef WIN32
    system ("pause");
#endif
    return 0;
 }

Controlando o acesso

recordam-se que os private members da classe base nunca são acedidos quer fora do programa quer mesmo sendo herdados. são apenas pelos restantes members da classe!

podemos no entanto conceder o mesmo nível de acesso que tinham na base classe aos membros herdados.

  • podemos conseguir isto através da palavra “using” e esta é a maneira recomendada, mas vamos deixar isto para o capitulo dos namespace
  • ou então usar um método que já está em desuso, que eu nem vou abordar.

Superposição de funções

Ocultando funções da classe base

Acessando funções superpostas da classe base

Funções virtuais

Chamando múltiplas funções virtuais

Funcões virtuais e passagem por valor

Construtor de cópia virtual

Classe base virtual

consideremos o seguinte programa:

// This program contains an error and will not compile.
#include <iostream>
using namespace std;
class base
   {
   public:
          int i;
   };
class derived1 : public base // derived1 inherits base.
   {
   public:
          int j;
   };
class derived2 : public base // derived2 inherits base.
   {
   public:
          int k;
   };
class derived3 : public derived1, public derived2 /* derived3 inherits both derived1 and derived2. This means that there are two copies of base in derived3! */
   {
   public: 
           int sum;
   };
int main()
{
   derived3 ob;
   ob.i = 10; // this is ambiguous; which i???
   ob.j = 20
   ob.k = 30;
   ob.sum = ob.i + ob.j + ob.k;// i ambiguous here, too
   cout << ob.i << " ";// also ambiguous, which i?
   cout << ob.j << " " << ob.k << " ";
   cout << ob.sum;
   system ("pause");
   return 0;
}

as classes derived1 e derived 2 herdam a classe base a classe derived3 herda quer derived 1 e derived2. como resultado temos 2 cópias da classe base presentes no objecto da derived 3. por exemplo presente na linha ob.i=20; isto resulta numa ambiguidade e o programa não vai compilar

há duas maneiras para remediar a situação 1. aplicar o operador scope resolution manualmente

// This program uses explicit scope resolution to select i.
#include <iostream>
using namespace std;
class base 
   {
   public:
          int i;
   };
class derived1 : public base // derived1 inherits base.
   {
   public:
          int j;
   };
class derived2 : public base // derived2 inherits base.
   {
   public:
          int k;
   };
class derived3 : public derived1, public derived2 /* derived3 inherits both derived1 and derived2. This means that there are two copies of base in derived3! */
   {
   public:
          int sum;
   };
int main()
{
   derived3 ob;
   ob.derived1::i = 10; 	// scope resolved, use derived1's i
   ob.j = 20;
   ob.k = 30;
   ob.sum = ob.derived1::i + ob.j + ob.k;		// scope resolved
   cout << ob.derived1::i << " ";		// also resolved here
   cout << ob.j << " " << ob.k << " ";
   cout << ob.sum;
   system ("pause");
   return 0;
}



2. a segunda maneira é através do virtual base classes. quando temos 2 ou mais objectos que são derivados da mesma base class, podemos prevenir múltiplas cópias da base class declarando a base class como virtual quando ela é herdada exemplificando

// This program uses virtual base classes.
#include <iostream>
using namespace std;
class base 
       {
       public:
              int i;
       };
class derived1 : virtual public base // derived1 inherits base as virtual.
       {
       public:
              int j;
       };
class derived2 : virtual public base // derived2 inherits base as virtual.
       {
       public:
              int k;
       };
class derived3 : public derived1, public derived2 /* derived3 inherits both derived1 and derived2. This time, there is only one copy of base class. */
       { 
       public:
              int sum;
       };
int main()
{
   derived3 ob;
   ob.i = 10; // now unambiguous
   ob.j = 20;
   ob.k = 30;
   ob.sum = ob.i + ob.j + ob.k;// unambiguous
   cout << ob.i << " ";// unambiguous
   cout << ob.j << " " << ob.k << " ";
   cout << ob.sum;
   system ("pause");
   return 0;
}

repare que agora temos a palavra virtual antes da classe

Ver também