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 391: Linha 391:
* podemos conseguir isto através da palavra “using” e esta é a maneira recomendada, mas vamos deixar isto para o capitulo dos namespace
* 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.
* 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 ==


== Virtual base class ==
== Virtual base class ==

Revisão das 20h43min de 23 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 : 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.

Controle de acesso à classe base

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

Heranças múltiplas

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

Construtores e destrutores

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 construtores da classe base

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

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

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