Ponteiro em C: Polimorfismo

funções X macros compilação condicional Diagnóstico
Este post faz parte da série Ponteiro em C

Muito se discute a respeito das facilidades criadas com a utilização de linguagens orientadas a objetos (OO), principalmente quando a comparação é realizada com a linguagem C. De fato, a linguagem C não possui as características de uma linguagem OO, contudo alguns desses aspectos podem ser criados em C. É claro que isto exige empenho por parte do programador. Nesse contexto, este artigo tem o objetivo de apresentar uma forma de implementar os principais conceitos de OO: Herança, Encapsulamento e Polimorfismo. O aprofundamento dos tópicos não será apresentado neste artigo, pois o objetivo é demonstrar a utilização dos ponteiros e suas aplicações!

Aspectos de Orientação a Objeto

Como apresentado na introdução, Herança, Encapsulamento e Polimorfismo são conceitos básicos de OO. O encapsulamento já foi discutido no artigo anterior em que os tipos de dados abstratos (TDA) eram criados de forma que sua implementação não era conhecida. Diante disso, um TDA é considerado como uma classe em OO, pois tem como função representar uma estrutura geral e as suas operações. Esse aspecto permite modularizar um código, isto é, organizá-lo em unidades funcionais distintas. Neste ponto, dois tópicos são essenciais: Herança e Polimorfismo.

  • Herança – Uma característica importante da POO é permitir a criação de novas classes a partir de uma classe já existente. Essa característica pode ser entendida como uma especialização, isto é, uma forma de ampliar a funcionalidade de uma classe;
  • Polimorfismo – A palavra polimorfismo significa ‘de muitas formas’. Na POO isso significa que a partir de um mesmo nome, que representa um objeto ou método, podemos ter várias formas, isto é, comportamentos diferentes.

Quando a herança é utilizada temos uma relação hierárquica. A classe que foi herdada é chamada de classe base, já a outra classe é chamada de derivada. Nesse contexto, a classe derivada herda de uma classe base todos os métodos, campos, propriedades e eventos da classe base. Dependendo da forma como isso foi especificado, é possível definir alguns elementos que podem ser alterados pela classe derivada! Considere o exemplo clássico [2] de formas geométricas mostrado na Figura 1.

Generalização e Especialização - Polimorfismo
Figura 1: Generalização e Especialização

A classe Shape define um conjunto de atributos e um método chamado Draw. Note que na declaração do método Draw aparece a keyword ‘virtual’. Essa keyword especifica que o método pode ser substituído em uma classe derivada. Na implementação mostrada abaixo optou-se pela linguagem C#.

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

Abaixo são mostradas as classes derivadas da classe Shape. Observe que nas classes Circle, Rectangle e Triangle o método Draw aparece com a keyword ‘override’, indicando a substituição do método da classe base pelo método da classe derivada. Além disso, todas essas classes podem acessar os atributos de Shape: X, Y, Height e Width.

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}

class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}

class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

O potencial deste tipo de implementação pode ser observado no código abaixo. Considere que existe uma estrutura de dados do tipo Shape, ou seja, da classe base. Essa estrutura de dados é populada com objetos do tipo Rectangle, Triangle e Circle. Esse procedimento é possível, pois essas classes também são do tipo Shape. No processo iterativo ‘foreach’ essa estrutura de dados é percorrida e, para cada iteração, o método Draw definido em Shape é invocado.

shapes.Add(new Rectangle());
shapes.Add(new Triangle());
shapes.Add(new Circle());

foreach (Shape s in shapes)
{
    s.Draw();
}

À primeira vista, o método Draw em Shape será executado, contudo esses métodos foram estendidos. Portanto, ao chamar o método Draw a partir de Shape, o método será executado na classe derivada! Assim, cada classe derivada apresentou um comportamento diferente, porém a chamada pôde ser realizada a partir de um nome comum.

Polimorfismo e Funções Virtuais em C

A forma mais simples de criar um mecanismo de herança e polimorfismo em C é a partir das tabelas de funções virtuais, também chamada de VTable. Uma VTable consiste de um conjunto de ponteiros para funções que são comuns nas classes derivadas e que são agrupados em uma struct. Considere, por exemplo, a classe Shape e o método virtual Draw na linguagem C.

typedef struct tShape Shape; //Define a estrutura Shape
typedef struct tShape_VTable Shape_VTable; //Define a tabela de funções virtuais

struct tShape_VTable
{
    void (*Draw)(Shape * const me); //Função virtual Draw que dever ser substituída
};

struct tShape
{
    //atributos
    int X;
    int Y;
    int Height;
    int Width;

    //tabela de funções virtuais – Ponteiro para o tipo Shape_VTable
    Shape_VTable * VTable;
};

Diante disso, as classes derivadas tem o dever de atribuir aos ponteiros de funções conforme suas implementações. A definição de Rectangle, Triangle e Circle são mostradas abaixo.

typedef struct tRectangle Rectangle;
typedef struct tTriangle Triangle;
typedef struct tCircle Circle;

struct tRectangle
{
    Shape base;
    
    //outros membros da estrutura....
};

struct tTriangle
{
    Shape base;
    
    //outros membros da estrutura....
};

struct tCircle
{
    Shape base;
    
    //outros membros da estrutura....
};

Note que na definição dessas estruturas a base Shape é sempre o primeiro membro. Organizando desse modo fica fácil observar a herança e que o endereço base é o endereço da estrutura shape. Dito de outra maneira, essas estruturas apresentam a relação de extensão. A Figura 2 ilustra essa situação.

Disposição das estruturas na memória - Polimorfismo
Figura 2: Disposição das estruturas na memória

Cabe ressaltar que esse tipo de construção é possível somente porque a descrição da estrutura Shape é conhecida, caso contrário seria necessário utilizar um ponteiro para estrutura Shape. Um breve exemplo, sem considerar os módulos e construtores para cada classe derivada, ilustrando a utilização das estruturas é mostrado abaixo.

void Circle_Draw(Shape const * const me);
void Rectangle_Draw(Shape const * const me);
void Triangle_Draw(Shape const * const me);

int main()
{
    Circle circle;
    Triangle triangle;
    Rectangle rectangle;

    //tabelas de funções virtuais para cada tipo de estrutura
    Shape_VTable VTable_circle;
    Shape_VTable VTable_triangle;
    Shape_VTable VTable_rectangle;

    //inicialização das tabelas de acordo com sua função de desenho
    VTable_circle.Draw = Circle_Draw;
    VTable_rectangle.Draw = Rectangle_Draw;
    VTable_triangle.Draw = Triangle_Draw;

    //inicialização das estruturas com a tabela de funções virtuais
    circle.base.VTable = &VTable_circle;
    triangle.base.VTable = &VTable_rectangle;
    rectangle.base.VTable = &VTable_triangle;

    //Vetor com ponteiros para dados do tipo Shape, ou seja, o tipo base
    Shape * shapes[3] = {
        (Shape *)&circle,
        (Shape *)&triangle,
        (Shape *)&rectangle
    };

    int i;

    for(i = 0; i < 3; i++)
    {
        //chamada da função virtual
        //shapes[i]->               É um ponteiro para uma das estruturas
        //shapes[i]->VTable->       Indica o acesso ao membro VTable que também é um ponteiro
        //shapes[i]->VTable->Draw   Acessa o ponteiro de função Draw definido em VTable
        shapes[i]->VTable->Draw((Shape const * const)&shapes[i]);
    }

    return 0;
}

void Circle_Draw(Shape const * const me)
{
    printf("Circle!\r\n");
}

void Rectangle_Draw(Shape const * const me)
{
    printf("Rectangle!\r\n");
}

void Triangle_Draw(Shape const * const me)
{
    printf("Triangle!\r\n");
}

Conclusão

Esse artigo apresentou, de maneira introdutória, alguns conceitos de orientação a objetos que podem ser aplicados em projetos utilizando linguagem C. Com base nesse e no artigo anterior você, leitor, poderá explorar novas formas de estruturar um projeto. Os conceitos envolvidos e demonstrados são muito importantes para reutilização de código. Outro ponto que pode ser observado é em relação à extensão e manutenção do projeto, pois a herança e o polimorfismo podem tornar o design flexível. Durante o projeto, vale a pena considerar os seguintes tópicos:

  • Programe para uma interface;
  • Encapsule o que varia;
  • Para reduzir o acoplamento das estruturas dê prioridade à composição em relação à herança.

Para saber mais

Embora o objetivo seja a implementação das técnicas de OO em C, é extremamente importante conhecer as características de orientação a objetos. Vale a pena conferir o artigo do Felipe Lavratti sobre polimorfismo em que o tópico é demonstrado com mais detalhes de modularização.

Referências

[1] – Livro: C, completo e total – 3ª edição revista e atualizada. Herbert Schildt.

[2] – https://msdn.microsoft.com/pt-br/library/ms173152.aspx

Ponteiro em C

Ponteiro em C: Tipo de Dado Abstrato
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
4 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Cesar Junior
Cesar Junior
20/01/2016 08:11

Parabéns pelo artigo! Muito bom.

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Cesar Junior
20/01/2016 13:51

Obrigado, Cesar.

Rafael Gebert
Rafael Gebert
19/01/2016 13:44

Excelente artigo Fernando! Nota 10!
Apenas gostaria de acrescentar aos demais leitores que o Shape * shapes[3] não precisa ser de tamanho fixo. Se implementar em forma de lista encadeada (ao invés de array) é possível alocar quantos objetos quiserem até acabar a memória. A base da minha gfx é exatame isso.
Quem sabe fica de sugestão para mais um artigo de ponteiros, hein Fernando?

Fernando Deluno Garcia
Fernando Deluno Garcia
Reply to  Rafael Gebert
20/01/2016 13:51

Olá, Rafael.

Um artigo sobre listas encadeadas? Pode ser uma série sobre estrutura de dados. É um assunto interessante!

Obrigado pelo retorno!

Home » Software » Linguagem C » Ponteiro em C: Polimorfismo

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: