Ponteiro em C: Tipo de Dado Abstrato

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

Olá, caro leitor! Ao longo da série foram abordados diversos tópicos sobre os ponteiros em C. Os tópicos abordados, ainda que de maneira introdutória, tiveram por objetivo definir e demonstrar o uso dos ponteiros. Diante disso, os assuntos apresentados relacionavam-se especificamente para utilização dos ponteiros como um recurso para armazenar um endereço e acessar uma região da memória de forma indireta. Agora, veremos que os ponteiros também podem ser utilizados para modelar uma aplicação, fazendo uso de TDA (Tipo de Dado Abstrato).

A Linguagem C

A característica especial de uma linguagem estruturada é a compartimentalização do código e dos dados, isto é, a capacidade de seccionar e encapsular partes do programa. Diante disso, o principal componente em C que possui essa característica é a função. Com a função é possível modularizar um programa, permitindo codificar, separadamente, uma determinada tarefa.

O Design Estruturado

O projeto de um programa em C segue uma abordagem Top-Down. Essa abordagem é caracterizada por estruturar o projeto a partir da rotina de nível mais alto até as rotinas de nível mais baixo. Um esboço de uma determinada parte do programa começa com uma descrição geral e caminha em direção da particularização. Vale a pena conferir o artigo do Felipe Lavratti sobre abordagem Top-Down.

tipo de dado abstrato: decomposição
Figura 1: Decomposição funcional de uma aplicação.

Diante disso, o design estruturado corresponde a uma decomposição funcional de uma aplicação num conjunto de módulos bem definidos. Esses módulos cooperam para desempenhar a funcionalidade definida pela aplicação, conforme ilustrado na Figura 1.

Programação Estruturada: Um método de escrever um programa que utiliza (1) analise Top-Down para solução de problemas, (2) modularização para estruturar e organizar o programa, e (3) estruturação do código em módulos individuais [4].

Neste contexto, a modularização inclui não somente a abstração dos algoritmos (abordagem procedimental), mas também o conceito de abstração dos dados, denominado Tipo de Dado Abstrato – TDA (ADT – do inglês, Abstract Data Type). Assim, a abstração tem como propósito separar a definição da implementação.

Tipo de Dado Abstrato

O conceito de abstração de dados, proposto por Barbara Liskov em 1974, tem a mesma ideia da abstração criada por funções. Diante disso, o objetivo é definir um tipo de dado que possa ser independente de qualquer implementação específica e separado do utilizador da aplicação. Dito de outra maneira, um tipo de dado abstrato é caracterizado por definir um conjunto de valores e de operações para manipular esses valores. Essa definição é ilustrada na Figura 2.

tipo de dado abstrato: Definição
Figura 2: Definição do TDA.

Assim como na construção de funções, a criação de um tipo abstrato começa com uma descrição geral e caminha em direção da particularização. A descrição geral é sua especificação, isto é, a definição do que o tipo abstrato representa. Já a particularização é sua implementação, isto é, a criação de um módulo que deve atender a especificação. Se esse módulo é encapsulado de forma que nenhum outro dependa de sua implementação, então esse módulo está de acordo com o princípio de information hiding definido por David Parnas em 1970. Embora a linguagem C não dê suporte para criação de tipo abstrato, é possível cria-lo com algumas limitações.

Módulos

Um módulo na linguagem C pode ser criado utilizando arquivos de cabeçalho e de implementação. Cabe ressaltar que um módulo não é um tipo abstrato de dados, pois pode ser utilizado para outros fins. Abaixo segue a definição de cada tipo de arquivo:

Arquivo de cabeçalho: Os arquivos ‘.h’ são utilizados para especificar assinatura de funções, definições de constantes, tipos de dados criados pelo usuário etc. De modo geral, os arquivos de cabeçalho tem como função definir a interface de um módulo.

Arquivo de implementação: Os arquivos ‘.c’ implementam as funções definidas na interface. De modo geral, esses arquivos são compostos por diversas funções e estruturas de dados utilizadas internamente.

Assim, o arquivo de cabeçalho é utilizado para especificação do tipo de dado abstrato, isto é, das operações que podem ser realizadas e as estruturas de dados definidas. Já o arquivo de implementação concretiza as operações definidas na interface. Desse modo, uma aplicação pode fazer uso de um determinado módulo a partir da inclusão do seu arquivo de interface.

Estruturas Opacas

Após a apresentação dos conceitos e da visão geral sobre modularização, faremos o uso de estruturas opacas para criar a abstração de dados. Lembrando que o conceito envolvido na abstração é separar especificação de implementação, então uma estrutura é opaca se apenas o seu nome é conhecido pela aplicação. Isso é possível se declararmos uma estrutura em um arquivo de interface e sua descrição interna em um arquivo de implementação. Considere o exemplo abaixo.

#ifndef EVENT_H
#define EVENT_H

typedef struct tEvent Event;

struct tEvent
{
    int id;
    int timeout;
};

#endif // EVENT_H

Note que qualquer arquivo que incluir a interface Event.h terá acesso aos elementos da estrutura Event. Agora, considere a modificação mostrada abaixo.

#ifndef EVENT_H
#define EVENT_H

typedef struct tEvent * pEvent;

#endif // EVENT_H

#include "Event.h"

struct tEvent
{
    int id;
    int timeout;
};

Agora, o tipo definido na interface é um ponteiro para estrutura e a descrição da estrutura foi colocada no arquivo de implementação. Desse modo, somente as funções presentes no arquivo Event.c poderão manipular a estrutura. Portanto, a interface de fornecer os construtores e as operações do tipo de dado. No código abaixo são mostradas quatro funções para manipular o novo tipo de dados.

#ifndef EVENT_H
#define EVENT_H

/*pEvent - Ponteiro para struct tEvent*/
typedef struct tEvent * pEvent;

/*
Construtor para estrutura tEvent.
Retorna um ponteiro para o tipo tEvent.
*/
pEvent Event_Create(int id, int timeout);

/*
Destrutor estrutura tEvent.
Recebe o endereço do Ponteiro pEvent (indireção múltipla!!)
*/
void Event_Destroy(pEvent * event);

/*
Define um novo timeout para o evento.
Recebe o endereço da estrutura e o timeout desejado.
*/
void Event_SetTimeout(pEvent event, int timeout);

/*
Retorna o timeout definido para o evento.
Recebe o endereço da estrutura.
*/
int Event_GetTimeout(pEvent event);

#endif // EVENT_H

A implementação dessas funções é mostrada abaixo.

#include "Event.h"
#include <stdlib.h>

struct tEvent
{
    int id;
    int timeout;
};


pEvent Event_Create(int id, int timeout)
{
    pEvent pt;

    pt = (pEvent)malloc(sizeof(struct tEvent));

    if(pt != NULL)
    {
        /*inicializa os elementos da estrutura*/
        pt->id = id;
        pt->timeout = timeout;
    }

    return pt;
}

void Event_Destroy(pEvent * event)
{
    if(event != NULL)
    {
        free(*event); /*event é o ponteiro que possui o endereço da estrutura que será liberada*/

        /*a indireção multípla só foi utilizada para pode atribuir NULL ao ponteiro da aplicação*/
        *event = NULL;
    }
}

void Event_SetTimeout(pEvent event, int timeout)
{
    if(event != NULL)
    {
        event->timeout = timeout;
    }
}

int Event_GetTimeout(pEvent event)
{
    if(event != NULL)
    {
        return event->timeout;
    }

    return -1;
}

O teste do módulo é mostrado a seguir.

#include <stdio.h>
#include <stdlib.h>
#include "Event.h"

int main()
{

    pEvent Event1;

    Event1 = Event_Create(1, 100);

    if(Event1 != NULL)
    {
        int timeout = Event_GetTimeout(Event1);
        printf("Timeout: %i\r\n", timeout);

        Event_SetTimeout(Event1, 200);

        timeout = Event_GetTimeout(Event1);
        printf("Timeout: %i\r\n", timeout);
    }

    Event_Destroy(&Event1);

    return 0;
}

É importante lembrar que qualquer tentativa de acesso fora do arquivo Event.c causará um erro de compilação. Por exemplo:

Event1->timeout = 10;

O erro de compilação indica que a composição da estrutura não é conhecida. De fato, essa era a intenção, logo os dados só podem ser manipulados a partir das operações definidas na interface.

error: dereferencing pointer to incomplete type.

Conclusão

Neste artigo foi apresentado um método para criar um tipo de dado abstrato. Fica demonstrado pelos exemplos que o conceito principal de TDA é a separação entre especificação e implementação. Essa característica permite estruturar uma aplicação em um conjunto de módulos independentes, que cooperam para concretizar uma determina função. Cabe ressaltar que os módulos podem ser bem mais elaborados, por exemplo, adicionando códigos de status nas funções para detalhar as operações ou algum erro. A questão da alocação dinâmica também pode ser reavaliada, porém essas definições ficam como responsabilidade do desenvolvedor.

Para saber mais

Vale a pena conferir o artigo do Andre Prado sobre as estruturas opacas.

Referências

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

[2] Barbara Liskov – Data Abstraction and Hierarchy.

[3] David L. Parnas – Modularization by Information Hiding.

[4] John Dalbey – Structured Programming.

Figura 1 – https://www.tenouk.com/Module4.html

Fonte da imagem destacada: https://listamaze.com/top-10-programming-languages-for-job-security/

Ponteiro em C

Ponteiro em C: Alocação Dinâmica de Memória Ponteiro em C: Polimorfismo
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
1 Comentário
recentes
antigos mais votados
Inline Feedbacks
View all comments
Eder
Eder
04/02/2017 17:42

Really nice Fernando!

Home » Software » Engenharia de Software » Ponteiro em C: Tipo de Dado Abstrato

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste:
Nenhum resultado encontrado.