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
Sem resumo de edição
Sem resumo de edição
Linha 3: Linha 3:


{{indentar}}
{{indentar}}

{{reciclagem}}


== Herança ==
== Herança ==

Revisão das 23h31min de 22 de janeiro de 2010

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

Herança

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 : 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:

 // Demonstrate inheritance.
 #include <iostream>
 using namespace std;
 class road_vehicle // Define a base class for vehicles.
    {
        int wheels;
        int passengers;
    public:
        void set_wheels(int num) { wheels = num; }
        int get_wheels() { return wheels; }
        void set_pass(int num) { passengers = num; }
        int get_pass() { return passengers; }
    };
 class truck : public road_vehicle // Define a truck.
    {
          int cargo;
    public:
           void set_cargo(int size) { cargo = size; }
           int get_cargo() { return cargo; }
           void show();
    };
 enum type {car, van, wagon};
 class automobile : public road_vehicle // Define an automoble.
    {
        enum type car_type;
    public:
        void set_type(type t) { car_type = t; }
        enum type get_type() { return car_type; }
        void show();
    };
 void truck::show()
        {
        cout << "wheels: " << get_wheels() << "\n";
        cout << "passengers: " << get_pass() << "\n";
        cout << "cargo capacity in liters: " << cargo << "\n";
        }
 void automobile::show()
        {
        cout << "wheels: " << get_wheels() << "\n";
        cout << "passengers: " << get_pass() << "\n";
        cout << "type: ";
        switch(get_type()) 
                {
                case van: cout << "van\n";
                          break;
                case car: cout << "car\n";
                          break;
                case wagon: cout << "wagon\n";
                }
        }
 int main()
 {
    truck t1, t2;
    automobile c;
    t1.set_wheels(18);
    t1.set_pass(2);
    t1.set_cargo(3200);
    t2.set_wheels(6);
    t2.set_pass(3);
    t2.set_cargo(1200);
    t1.show();
    cout << "\n";
    t2.show();
    cout << "\n";
    c.set_wheels(4);
    c.set_pass(6);
    c.set_type(van);
    c.show();
    system ("pause");
    return 0;
 }

Na implementação acima temos a classe base road_vehicle e duas classes derivadas“:” truck e automobile.

Repare ainda um pormenor: tanto a classe truck quanto a automobile têm como método membro o método show(), mas uma não interfere com a outra. Isto ilustra um outro aspecto do polimorfismo.

base class Access Control.

Quando uma classe herda outra, os membros da classe base ficam membros da classe derivada. o acesso dos membros da classe base à classe derivada é determinado pelo especificador de acesso: public, private e protected. Por defaut temos o private, ou seja como temos a opção de não explicitar o especificador de acesso, sabes que eles são private por defaut.

Assim ficamos com as possíveis combinações

  • base class inheret as Public:
    • Public membrer da base classe:
      • É como tivéssemos a fazer copy dos public members da classe base e os colocássemos como public na classe derivada
    • Private membrer da base classe:
      • não são passados
    • Protected membrer da base classe:
      • se tivermos protected members na classe eles são como copiados para a classe derivada como protected.
  • base class inheret as Private:
    • Public membrer da base classe:

É como tivéssemos a fazer copy dos public members da classe base e os colocássemos como **private na classe derivada

    • Private membrer da base classe:
      • não são passados
    • Protected membrer da base classe:
      • è como estivéssemos a copiar os protected members da classe base e os colocássemos como private na classe derivada.


  • base class inheret as Protected:
    • Public membrer da base classe:
      • É como tivéssemos a fazer copy dos public members da classe base e os colocássemos como protected na classe derivada
    • Private membrer da base classe:
      • não são passados
    • Protected membrer da base classe:
      • Assim é como estivéssemos a copiar os protected members da classe base e os colocássemos como protected 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); // access member of base
   ob.show(); // access member of base
   ob.showk(); // uses member of derived class
   system ("pause");
   return 0;
}


#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); // Error, can't access set()
   ob.show(); // Error, can't access show()
   ob.showk(); // uses member of derived class
   system ("pause");
   return 0;
}

conseguimos aceder á função set() e show() porque é heradada como public Agora já não porque está como private



Herdar múltiplas classes base

Podemos ter a situação em que uma classe possa herdar de várias classes base.

// An example of multiple base classes.
#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); // provided by derived
   ob.showx(); // from base1
   ob.showy(); // from base2
   system ("pause");
   return 0;
}

repare que utilizamos o operador comma (virgula) para dizer que herdamos várias classes.

Pergunta: e quando queremos herdar uma classe como public e outra como private ou protected. Basta preceder a classe com o seu especificador de acesso


Constructors e destructors

Agora temos a questão quando é que os constructors são chamados quando eles são herdados?

  • quando um objecto da classe derivada é chamado, o constructor da base class é chamado primeiro seguido do constructor da classe derivada.
  • quando o objecto da classe derivada é destruído, o seu destructor é chamado primeiro seguido do destructor da base class

vamos ver isto. caso em que termos herança sequencial A-B-C

#include <iostream>
using namespace std;
class base 
   {
   public:
          base() { cout << "Constructing base\n"; }
          ~base() { cout << "Destructing base\n"; }
   };
class derived1 : public base 
   {
   public:
          derived1() { cout << "Constructing derived1\n"; }
          ~derived1() { cout << "Destructing derived1\n"; }
   };
class derived2: public derived1 
   {
   public:
          derived2() { cout << "Constructing derived2\n"; }
          ~derived2() { cout << "Destructing derived2\n"; }
   };
int main()
{
   derived2 ob; // construct and destruct ob
   system ("pause");
   return 0;
}

este exemplo está muito giro!!

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

#include <iostream>
using namespace std;
class base1 
        {
        public:
              base1() { cout << "Constructing base1\n"; }
              ~base1() { cout << "Destructing base1\n"; }
       };
class base2 
       {
       public:
              base2() { cout << "Constructing base2\n"; }
              ~base2() { cout << "Destructing base2\n"; }
       };
class derived: public base2,public base1
       {
       public:
              derived() { cout << "Constructing derived\n"; }
              ~derived() { cout << "Destructing derived\n"; }
       }; 
int main()
{
   derived ob;// construct and destruct ob
   system ("pause");
   return 0;
}

repare que aqui é a ordem da esquerda para a direita que os constructors são executados na



Passando parâmetros para a base class constructors

a sintax é:

derived-constructor(arg-list) : base1(arg-list), base2(arg-list), ...baseN(arg-list);
{
body of derived constructor
}


este exemplo está complexo, atenção!

#include <iostream>
using namespace std;
class base 
       {
       protected:
                 int i;
       public:
              base(int x) { i = x; cout << "Constructing base\n"; }
              ~base() { cout << "Destructing base\n"; }
       };
class derived: public base 
       {
             int j;
       public:
              derived(int x, int y): base(y) { j = x; cout << "Constructing derived\n"; }// derived uses x; y is passed along to base.
              ~derived() { cout << "Destructing derived\n"; }
              void show() { cout << i << " " << j << "\n"; }
       };
int main()
{
   derived ob(3, 4);
   ob.show(); // displays 4 3
   system ("pause");
   return 0;
}

aqui o derived constructor é declarado com 2 argumentos (x e y). no entanto a função derived() usa apenas um.


aqui vai mais um exemplo.

#include <iostream>
using namespace std;
class base1 
   {
   protected:
             int i;
   public:
          base1(int x) { i = x; cout << "Constructing base1\n"; }
          ~base1() { cout << "Destructing base1\n"; }
   };
class base2 
   {
   protected:
             int k;
   public:
          base2(int x) { k = x; cout << "Constructing base2\n"; }
          ~base2() { cout << "Destructing base2\n"; }
   };
class derived: public base1, public base2 
   {
        int j;
   public:
          derived(int x, int y, int z): base1(y), base2(z)
                      { j = x; cout << "Constructing derived\n"; }
          ~derived() { cout << "Destructing derived\n"; }
          void show() { cout << i << " " << j << " " << k << "\n"; }
   };
int main()
{
   derived ob(3, 4, 5);
   ob.show(); // displays 4 3 5
   system ("pause");
   return 0;
}



Granting Access

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.



Virtual base class

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