Você está na página 1de 9

9.

Tipos Abstratos de Dados


R. Cerqueira, W. Celes e J.L. Rangel Neste captulo, discutiremos uma importante tcnica de programao baseada na definio de Tipos Abstratos de Dados (TAD). Veremos tambm como a linguagem C pode nos ajudar na implementao de um TAD, atravs de alguns de seus mecanismos bsicos de modularizao (diviso de um programa em vrios arquivos fontes).

9.1. Mdulos e Compilao em Separado


Como foi visto no captulo 1, um programa em C pode ser dividido em vrios arquivos fontes (arquivos com extenso .c ). Quando temos um arquivo com funes que representam apenas parte da implementao de um programa completo, denominamos esse arquivo de mdulo. Assim, a implementao de um programa pode ser composta por um ou mais mdulos. No caso de um programa composto por vrios mdulos, cada um desses mdulos deve ser compilado separadamente, gerando um arquivo objeto (geralmente um arquivo com extenso .o ou .obj) para cada mdulo. Aps a compilao de todos os mdulos, uma outra ferramenta, denominada ligador, usada para juntar todos os arquivos objeto em um nico arquivo executvel. Para programas pequenos, o uso de vrios mdulos pode no se justificar. Mas para programas de mdio e grande porte, a sua diviso em vrios mdulos uma tcnica fundamental, pois facilita a diviso de uma tarefa maior e mais complexa em tarefas menores e, provavelmente, mais fceis de implementar e de testar. Alm disso, um mdulo com funes C pode ser utilizado para compor vrios programas, e assim poupar muito tempo de programao. Para ilustrar o uso de mdulos em C, considere que temos um arquivo str.c que contm apenas a implementao das funes de manipulao de strings comprimento, copia e concatena vistas no captulo 6. Considere tambm que temos um arquivo prog1.c com o seguinte cdigo:
#include <stdio.h> int comprimento (char* str); void copia (char* dest, char* orig); void concatena (char* dest, char* orig); int main (void) { char str[101], str1[51], str2[51]; printf("Entre com uma seqncia de caracteres: "); scanf(" %50[^\n]", str1); printf("Entre com outra seqncia de caracteres: "); scanf(" %50[^\n]", str2); copia(str, str1); concatena(str, str2); printf("Comprimento da concatenao: %d\n",comprimento(str)); return 0; }

A partir desses dois arquivos fontes, podemos gerar um programa executvel compilando cada um dos arquivos separadamente e depois ligando-os em um nico
Estruturas de Dados PUC-Rio 9-1

arquivo executvel. Por exemplo, com o compilador Gnu C (gcc) utilizaramos a seguinte seqncia de comandos para gerar o arquivo executvel prog1.exe:
> gcc c str.c > gcc c prog1.c > gcc o prog1.exe str.o prog1.o

O mesmo arquivo str.c pode ser usado para compor outros programas que queiram utilizar suas funes. Para que as funes implementadas em str.c possam ser usadas por um outro mdulo C, este precisa conhecer os cabealhos das funes oferecidas por str.c . No exemplo anterior, isso foi resolvido pela repetio dos cabealhos das funes no incio do arquivo prog1.c. Entretanto, para mdulos que ofeream vrias funes ou que queiram usar funes de muitos outros mdulos, essa repetio manual pode ficar muito trabalhosa e sensvel a erros. Para contornar esse problema, todo mdulo de funes C costuma ter associado a ele um arquivo que contm apenas os cabealhos das funes oferecidas pelo mdulo e, eventualmente, os tipos de dados que ele exporte (typedefs, structs, etc). Esse arquivo de cabealhos segue o mesmo nome do mdulo ao qual est associado, s que com a extenso .h. Assim, poderamos definir um arquivo str.h para o mdulo do exemplo anterior, com o seguinte contedo:
/* Funes oferecidas pelo modulo str.c */ /* Funo comprimento ** Retorna o nmero de caracteres da string passada como parmetro */ int comprimento (char* str); /* Funo copia ** Copia os caracteres da string orig (origem) para dest (destino) */ void copia (char* dest, char* orig); /* Funo concatena ** Concatena a string orig (origem) na string dest (destino) */ void concatena (char* dest, char* orig);

Observe que colocamos vrios comentrios no arquivo str.h. Isso uma prtica muito comum, e tem como finalidade documentar as funes oferecidas por um mdulo. Esses comentrios devem esclarecer qual o comportamento esperado das funes exportadas por um mdulo, facilitando o seu uso por outros programadores (ou pelo mesmo programador algum tempo depois da criao do mdulo). Agora, ao invs de repetir manualmente os cabealhos dessas funes, todo mdulo que quiser usar as funes de str.c precisa apenas incluir o arquivo str.h. No exemplo anterior, o mdulo prog1.c poderia ser simplificado da seguinte forma:
#include <stdio.h> #include "str.h" int main (void) { char str[101], str1[51], str2[51]; printf("Entre com uma seqncia de caracteres: "); scanf(" %50[^\n]", str1); printf("Entre com outra seqncia de caracteres: "); scanf(" %50[^\n]", str2);
Estruturas de Dados PUC-Rio 9-2

copia(str, str1); concatena(str, str2); printf("Comprimento da concatenao: %d\n",comprimento(str)); return 0; }

Note que os arquivos de cabealhos das funes da biblioteca padro do C (que acompanham seu compilador) so includos da forma #include <arquivo.h>, enquanto que os arquivos de cabealhos dos seus mdulos so geralmente includos da forma #include "arquivo.h". O uso dos delimitadores < > e " " indica para o compilador onde ele deve procurar esses arquivos de cabealhos durante a compilao.

9.2. Tipo Abstrato de Dados


Geralmente, um mdulo agrupa vrios tipos e funes com funcionalidades relacionadas, caracterizando assim uma finalidade bem definida. Por exemplo, na seo anterior vimos um mdulo com funes para manipulao de cadeias de caracteres. Nos casos em que um mdulo define um novo tipo de dado e o conjunto de operaes para manipular dados desse tipo, falamos que o mdulo representa um tipo abstrato de dados (TAD). Nesse contexto, abstrato significa esquecida a forma de implementao, ou seja, um TAD descrito pela finalidade do tipo e de suas operaes, e no pela forma como est implementado. Podemos, por exemplo, criar um TAD para representar matrizes alocadas dinamicamente. Para isso, criamos um tipo matriz e uma srie de funes que o manipulam. Podemos pensar, por exemplo, em funes que acessem e manipulem os valores dos elementos da matriz. Criando um tipo abstrato, podemos esconder a estratgia de implementao. Quem usa o tipo abstrato precisa apenas conhecer a funcionalidade que ele implementa, no a forma como ele implementado. Isto facilita a manuteno e o re-uso de cdigos. O uso de mdulos e TADs so tcnicas de programao muito importantes. Nos prximos captulos, vamos procurar dividir nossos exemplos e programas em mdulos e usar tipos abstratos de dados sempre que isso for possvel. Antes disso, vamos ver alguns exemplos completos de TADs. Exemplo 1: TAD Ponto Como nosso primeiro exemplo de TAD, vamos considerar a criao de um tipo de dado para representar um ponto no R2. Para isso, devemos definir um tipo abstrato, que denominaremos de Ponto, e o conjunto de funes que operam sobre esse tipo. Neste exemplo, vamos considerar as seguintes operaes: cria: operao que cria um ponto com coordenadas x e y; libera: operao que libera a memria alocada por um ponto; acessa: operao que devolve as coordenadas de um ponto; atribui: operao que atribui novos valores s coordenadas de um ponto; distancia: operao que calcula a distncia entre dois pontos. A interface desse mdulo pode ser dada pelo cdigo a seguir:

Estruturas de Dados PUC-Rio

9-3

Arquivo ponto.h:
/* TAD: Ponto (x,y) */ /* Tipo exportado */ typedef struct ponto Ponto; /* Funes exportadas */ /* Funo cria ** Aloca e retorna um ponto com coordenadas (x,y) */ Ponto* cria (float x, float y); /* Funo libera ** Libera a memria de um ponto previamente criado. */ void libera (Ponto* p); /* Funo acessa ** Devolve os valores das coordenadas de um ponto */ void acessa (Ponto* p, float* x, float* y); /* Funo atribui ** Atribui novos valores s coordenadas de um ponto */ void atribui (Ponto* p, float x, float y); /* Funo distancia ** Retorna a distncia entre dois pontos */ float distancia (Ponto* p1, Ponto* p2);

Note que a composio da estrutura Ponto (struct ponto) no exportada pelo mdulo. Dessa forma, os demais mdulos que usarem esse TAD no podero acessar diretamente os campos dessa estrutura. Os clientes desse TAD s tero acesso s informaes que possam ser obtidas atravs das funes exportadas pelo arquivo ponto.h. Agora, mostraremos uma implementao para esse tipo abstrato de dados. O arquivo de implementao do mdulo (arquivo ponto.c ) deve sempre incluir o arquivo de interface do mdulo. Isto necessrio por duas razes. Primeiro, podem existir definies na interface que so necessrias na implementao. No nosso caso, por exemplo, precisamos da definio do tipo Ponto. A segunda razo garantirmos que as funes implementadas correspondem s funes da interface. Como o prottipo das funes exportadas includo, o compilador verifica, por exemplo, se os parmetros das funes implementadas equivalem aos parmetros dos prottipos. Alm da prpria interface, precisamos naturalmente incluir as interfaces das funes que usamos da biblioteca padro.
#include #include #include #include <stdlib.h> <stdio.h> <math.h> "ponto.h" /* malloc, free, exit */ /* printf */ /* sqrt */

Estruturas de Dados PUC-Rio

9-4

Como s precisamos guardar as coordenadas de um ponto, podemos definir a estrutura ponto da seguinte forma:
struct ponto { float x; float y; };

A funo que cria um ponto dinamicamente deve alocar a estrutura que representa o ponto e inicializar os seus campos:
Ponto* cria (float x, float y) { Ponto* p = (Ponto*) malloc(sizeof(Ponto)); if (p == NULL) { printf("Memria insuficiente!\n"); exit(1); } p->x = x; p->y = y; return p; }

Para esse TAD, a funo que libera um ponto deve apenas liberar a estrutura que foi criada dinamicamente atravs da funo cria:
void libera (Ponto* p) { free(p); }

As funes para acessar e atribuir valores s coordenadas de um ponto so de fcil implementao, como pode ser visto a seguir.
void acessa (Ponto* p, float* x, float* y) { *x = p->x; *y = p->y; } void atribui (Ponto* p, float x, float y) { p->x = x; p->y = y; }

J a operao para calcular a distncia entre dois pontos pode ser implementada da seguinte forma:
float distancia (Ponto* p1, Ponto* p2) { float dx = p2->x p1->x; float dy = p2->y p1->y; return sqrt(dx*dx + dy*dy); }

Exerccio: Escreva um programa que faa uso do TAD ponto definido acima. Exerccio: Acrescente novas operaes ao TAD ponto, tais como soma e subtrao de pontos.

Estruturas de Dados PUC-Rio

9-5

Exerccio: Acrescente novas operaes ao TAD ponto, de tal forma que seja possvel obter uma representao do ponto em coordenadas polares. Exerccio: Implemente um novo TAD para representar pontos no R3. Exemplo 2: TAD Matriz Como foi discutido anteriormente, a implementao de um TAD fica escondida dentro de seu mdulo. Assim, podemos experimentar diferentes maneiras de implementar um mesmo TAD, sem que isso afete os seus clientes. Para ilustrar essa independncia de implementao, vamos considerar a criao de um tipo abstrato de dados para representar matrizes de valores reais alocadas dinamicamente, com dimenses m por n fornecidas em tempo de execuo. Para tanto, devemos definir um tipo abstrato, que denominaremos de Matriz , e o conjunto de funes que operam sobre esse tipo. Neste exemplo, vamos considerar as seguintes operaes: cria: operao que cria uma matriz de dimenso m por n; libera: operao que libera a memria alocada para a matriz; acessa: operao que acessa o elemento da linha i e da coluna j da matriz; atribui: operao que atribui o elemento da linha i e da coluna j da matriz; linhas: operao que devolve o nmero de linhas da matriz; colunas: operao que devolve o nmero de colunas da matriz. A interface do mdulo pode ser dada pelo cdigo abaixo: Arquivo matriz.h:
/* TAD: matriz m por n */ /* Tipo exportado */ typedef struct matriz Matriz; /* Funes exportadas */ /* Funo cria ** Aloca e retorna uma matriz de dimenso m por n */ Matriz* cria (int m, int n); /* Funo libera ** Libera a memria de uma matriz previamente criada. */ void libera (Matriz* mat); /* Funo acessa ** Retorna o valor do elemento da linha i e coluna j da matriz */ float acessa (Matriz* mat, int i, int j); /* Funo atribui ** Atribui o valor dado ao elemento da linha i e coluna j da matriz */ void atribui (Matriz* mat, int i, int j, float v); /* Funo linhas ** Retorna o nmero de linhas da matriz */ int linhas (Matriz* mat);
Estruturas de Dados PUC-Rio 9-6

/* Funo colunas ** Retorna o nmero de colunas da matriz */ int colunas (Matriz* mat);

A seguir, mostraremos a implementao deste tipo abstrato usando as duas estratgias apresentadas no captulo 8: matrizes dinmicas representadas por vetores simples e matrizes dinmicas representadas por vetores de ponteiros. A interface do mdulo independe da estratgia de implementao adotada, o que altamente desejvel, pois podemos mudar a implementao sem afetar as aplicaes que fazem uso do tipo abstrato. O arquivo matriz1.c apresenta a implementao atravs de vetor simples e o arquivo matriz2.c apresenta a implementao atravs de vetor de ponteiros. Arquivo matriz1.c:
#include <stdlib.h> #include <stdio.h> #include "matriz.h" struct matriz { int lin; int col; float* v; }; Matriz* cria (int m, int n) { Matriz* mat = (Matriz*) malloc(sizeof(Matriz)); if (mat == NULL) { printf("Memria insuficiente!\n"); exit(1); } mat->lin = m; mat->col = n; mat->v = (float*) malloc(m*n*sizeof(float)); return mat; } void libera (Matriz* mat){ free(mat->v); free(mat); } float acessa (Matriz* mat, int i, int j) { int k; /* ndice do elemento no vetor */ if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Acesso invlido!\n"); exit(1); } k = i*mat->col + j; return mat->v[k]; } void atribui (Matriz* mat, int i, int j, float v) { int k; /* ndice do elemento no vetor */ if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Atribuio invlida!\n"); exit(1); }
Estruturas de Dados PUC-Rio 9-7

/* malloc, free, exit */ /* printf */

k = i*mat->col + j; mat->v[k] = v; } int linhas (Matriz* mat) { return mat->lin; } int colunas (Matriz* mat) { return mat->col; }

Arquivo matriz2.c:
#include <stdlib.h> #include <stdio.h> #include "matriz.h" struct matriz { int lin; int col; float** v; }; Matriz* cria (int m, int n) { int i; Matriz* mat = (Matriz*) malloc(sizeof(Matriz)); if (mat == NULL) { printf("Memria insuficiente!\n"); exit(1); } mat->lin = m; mat->col = n; mat->v = (float**) malloc(m*sizeof(float*)); for (i=0; i<m; i++) mat->v[i] = (float*) malloc(n*sizeof(float)); return mat; } void libera (Matriz* mat) { int i; for (i=0; i<mat->lin; i++) free(mat->v[i]); free(mat->v); free(mat); } float acessa (Matriz* mat, int i, int j) { if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Acesso invlido!\n"); exit(1); } return mat->v[i][j]; } void atribui (Matriz* mat, int i, int j, float v) { if (i<0 || i>=mat->lin || j<0 || j>=mat->col) { printf("Atribuio invlida!\n"); exit(1); } mat->v[i][j] = v; } int linhas (Matriz* mat) { return mat->lin; } /* malloc, free, exit */ /* printf */

Estruturas de Dados PUC-Rio

9-8

int colunas (Matriz* mat) { return mat->col; }

Exerccio: Escreva um programa que faa uso do TAD matriz definido acima. Teste o seu programa com as duas implementaes vistas. Exerccio: Usando apenas as operaes definidas pelo TAD matriz, implemente uma funo que determine se uma matriz ou no quadrada simtrica. Exerccio: Usando apenas as operaes definidas pelo TAD matriz, implemente uma funo que, dada uma matriz, crie dinamicamente a matriz transposta correspondente. Exerccio: Defina um TAD para implementar matrizes quadradas simtricas, de acordo com a representao sugerida no captulo 8.

Estruturas de Dados PUC-Rio

9-9

Você também pode gostar