Conhecer para que serve e saber implementar utilizando o tipo de dado abstrato permite a construção de programas modulares e manuteníveis.
Recordando o que é tipo de dados (primitivos)
Se você curtir esse artigo, não esqueça de dar uns cliques nos anúncios para ver o que os anunciantes tem para te oferecer. Vou ficar muito contente.
Quando a gente vai falar de tipo de dados, a gente tem que lembrar duas coisas importantes:
- O tipo de dado define um certo domínio de valores.
- Define as operações permitidas com esses valores.
Isso pode parecer um pouco confuso, mas fica muito mais fácil se explicar um pouquinho mais.
Tomemos por exemplo, a declaração de um inteiro. Em relação ao tipo de dado inteiro, podemos dizer que ele:
- Aceita somente valores inteiros
- As operações permitidas com o tipo de dado inteiro são: adição, soma, multiplicação, subtração, operações de bitwise e outras.
Agora se a gente pegar a declaração de um tipo de dado float, podemos dizer que:
- Esse tipo de dado aceita apenas valores de ponto flutuante.
- As operações permitidas são: : adição, soma, multiplicação, subtração e outras, porém operações de bitwise não são permitidas.
Dito isso, a gente confirma que a definição de um tipo de dado (data type) serve para definir o domínio de valor de um dado e as operações que são permitidas com esses valores.
Tipos de dados definidos por usuários:
Uma vez conversado brevemente sobre o que é um tipo de dado primitivo (int, float, double, char…), vamos pular para os tipos de dados definidos por usuários.
Quando a gente está falando de tipos de dados definidos por usuários, as coisas mudam um pouco e as operações e valores do tipo de dado definido pelo usuário não é mais especificado pela linguagem em si. Na verdade os valores definidos pelo usuário e operações permitidas nesses valores que foram definidas pelo usuário, são especificadas pelo usuário.
Isso é feito utilizando estruturas (struct), que permite que diferentes dados sejam combinados, conforme exemplo abaixo:
#include <stdio.h>
#include <stdlib.h>
int main()
{
struct caixa{
char tipo[10];
float tamanho;
float largura;
float altura;
}caixa;
strcpy (caixa.tipo, "madeira");
caixa.tamanho = 10.0;
caixa.largura = 12.5;
caixa.altura = 7;
printf(
"Tipo de caixa: %s.\n"
"Tamanho: %.2f\n"
"Largura: %.2f\n"
"Altura: %.2f\n",
caixa.tipo,
caixa.tamanho,
caixa.largura,
caixa.altura
);
}
No programa acima, “caixa” é um tipo de dado definido pelo usuário e a saída desse programa, pode ser vista abaixo:
Tudo bem até aí. Mas e onde é que entra o tipo de dado abstrato?
Tipo de dado abstrato é como o tipo de dado definido pelo usuário, o qual utilizando funções, define as operações que podem ser realizadas, sem especificar o que há dentro da função e como a operação é executada.
É como se fosse uma caixa preta. O usuário envia os parâmetros, chama as funções e a estrutura que usa o tipo de dado abstrato processa esses parâmetros, devolvendo o dado processado.
Exemplo de tipo de dado abstrato:
Se pegarmos o exemplo do programa que cria uma caixa, seria possível acrescentar algumas operações como:
Operação | Função |
cria_caixa() | Cria uma caixa |
abre() | Abre a caixa |
fecha() | Fecha a caixa |
status() | Informações sobre a caixa |
Essas seriam os elementos possíveis e as operações possíveis de se realizar na caixa, mas no caso do tipo de dado abstrato, o usuário apenas define quais são as funções possíveis, sem realizar de fato a implementação e não há a necessidade de conhecer como essas funções são implementadas.
Repetindo, o tipo de dado abstrato deve ser entendido como uma caixa preta que esconde do usuário os detalhes de sua estrutura interior e esconde do usuário como o tipo de dado foi projetado.
Dito isso, temos que entender que há várias maneiras de se implementar um tipo de dado abstrato. Nesse exemplo, o tipo de objeto abstrato caixa. Pode utilizar struct, pode usar listas ligadas ou até um array contendo as informações sobre a caixa e seu status.
Porque precisamos do tipo de dado abstrato?
O tipo de dado abstrato é uma ferramenta importante para a construção de um código modular e de fácil manutenção. Ao separar do programa, a implementação de um tipo de dado, fica muito mais fácil implementar novas funcionalidades, sem interferir no resto do código legado.
São 2 programas e 3 nomes:
- Programa cliente – que é o programa que utiliza a estrutura de dados (p. ex main.c).
- Implementação – que é o programa que implementa a estrutura de dados (p. ex tda.c).
- Interface – Lista as funções que a estrutura de dados pode realizar (p.ex tda.h).
Podemos dizer que o programa que utiliza uma estrutura de dados abstrata, chama-se de programa “cliente” e ele não conhece como a implementação do programa é realizada.
O programa que implementa a estrutura de dado abstrata é chamado de “implementação”.
A “implementação” possui uma parte chamada “interface”, que é a porta de comunicação entre o programa “cliente” e a “implementação”.
Tipo de dado abstrato fornece abstração, ocultando detalhes da implementação do código, simplificando o código e facilitando sua manutenção.
Indo por partes. Exemplo de implementação de um tipo de dado.
Segue um dos exemplos possíveis de como transformar a estrutura “caixa” abordada acima, em um tipo de dado que vou chamar de “tipo_caixa” que vai definir uma variável “caixa” do “tipo_caixa”. Conforme dito, são 3 arquivos: Programa cliente, interface e implementação.
Programa Cliente (arquivo main.c):
#include <stdio.h>
#include "tda.h"
int main() {
int tamanho_obj,id_caixa;
//Declarando uma caixa do "tipo_caixa"
tipo_caixa caixa;
//Chamando o conjunto de operacoes permitidas para o tipo_caixa.
configura_material_caixa(&caixa,"madeira");
configura_tamanho_caixa(&caixa,12);
configura_largura_caixa(&caixa,17.4);
configura_altura_caixa(&caixa,7.2);
configura_id_caixa(&caixa, 32);
tamanho_obj = obj_size(&caixa);
id_caixa = obj_getid(&caixa);
//Imprimindo individualmente cada um dos elementos da caixa
//Nesta implementacao é possivel acessar diretamente
//cada um dos elementos da estrutura
printf("Material da caixa: %s.\n"
"Tamanho: %.1fcm\n"
"Largura: %.1fcm\n"
"Altura: %.1fcm\n"
"id da caixa: %d\n"
"Tamanho do objeto: %d\n\n",
caixa.material,
caixa.tamanho,
caixa.largura,
caixa.altura,
id_caixa,
tamanho_obj
);
//Funcao para imprimir todos elementos da caixa
imprime_caixa(&caixa);
return 0;
}
Interface (arquivo tda.h):
#ifndef TDA_H_INCLUDED
#define TDA_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
//Interface.
//Aqui lista a estrutura e as operacoes que podem ser
//realizadas na estrutura, bem como os parametros necessários.
typedef struct caixa{
char material[20];
float tamanho;
float largura;
float altura;
int id;
}tipo_caixa;
//Conjunto de operacoes permitidas para o tipo_caixa.
size_t obj_size(struct caixa *);
void configura_material_caixa(struct caixa *,char[]);
void configura_id_caixa(struct caixa *, int);
void configura_tamanho_caixa(struct caixa *,float);
void configura_largura_caixa(struct caixa *,float);
void configura_altura_caixa(struct caixa *,float);
int obj_getid(struct caixa *);
void imprime_caixa(struct caixa *);
#endif // TDA_H_INCLUDED
Implementação (arquivo tda.c):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tda.h"
//Implementacao das operacoes permitidas para o tipo_caixa
void configura_material_caixa(struct caixa *o, char i[]){
strcpy (o->material, i);
}
void configura_id_caixa(struct caixa *o, int i) {
o->id = i;
}
void configura_tamanho_caixa(struct caixa *o,float i){
o->tamanho = i;
}
void configura_largura_caixa(struct caixa *o,float i){
o->largura =i;
}
void configura_altura_caixa(struct caixa *o,float i){
o->altura = i;
}
int obj_getid(struct caixa *o) {
return o->id;
}
size_t obj_size(struct caixa *o) {
return sizeof (*o);
}
void imprime_caixa(struct caixa *o){
printf("Material de caixa: %s.\n"
"Tamanho: %.1fcm\n"
"Largura: %.1fcm\n"
"Altura: %.1fcm\n"
"Id da caixa %d\n"
"Tamanhho do objeto %u\n",
o->material,
o->tamanho,
o->largura,
o->altura,
obj_getid(o),
(unsigned)obj_size(o)
);
}
Implementando um tipo de dado abstrato e ocultando sua estrutura
No caso a seguir, estou implementando novamente o tipo de dado “tipo_caixa” com os três arquivos: Programa Cliente, Interface e a Implementação.
A diferença é que a estrutura do mesmo “typedef struct Caixa” fica oculta dentro da implementação e a interação vai se dar apenas utilizando as funções que estiverem definidas na interface, que no caso é o arquivo “tda.h”. Analisando o código, não é mais possível acessar diretamente os atributos do tipo de dado “tipo_caixa”. Dá erro de compilação se tentar fazer isso.
Programa cliente (arquivo main.c)
#include <stdio.h>
#include "tda.h"
int main() {
int tamanho_obj,id_caixa;
//Declarando uma caixa do "tipo_caixa"
tipo_caixa caixa =Caixa_new();
//Chamando o conjunto de operacoes permitidas para o tipo_caixa.
configura_material_caixa(caixa,"madeira");
configura_tamanho_caixa(caixa,12);
configura_largura_caixa(caixa,17.4);
configura_altura_caixa(caixa,7.2);
configura_id_caixa(caixa, 32);
tamanho_obj = obj_size(caixa);
id_caixa = obj_getid(caixa);
//Funcao para imprimir todos elementos da caixa
imprime_caixa(caixa);
//Função para imprimir somente o id da caixa
imprime_id_caixa(caixa);
//Nesta implementacao NÃO É possivel acessar diretamente
//cada um dos elementos da estrutura.
//Se descomentar essa parte, vai dar erro de compilação.
// printf("Material da caixa: %s.\n"
// "Tamanho: %.1fcm\n"
// "Largura: %.1fcm\n"
// "Altura: %.1fcm\n"
// "id da caixa: %d\n"
// "Tamanho do objeto: %d\n\n",
// caixa.material,
// caixa.tamanho,
// caixa.largura,
// caixa.altura,
// id_caixa,
// tamanho_obj
// );
//Liberando memoria
destroi_caixa(caixa);
return 0;
}
Interface (arquivo tda.h)
#ifndef TDA_H_INCLUDED
#define TDA_H_INCLUDED
#include <stdio.h>
#include <stdlib.h>
//Interface.
//Aqui lista a estrutura e as operacoes que podem ser
//realizadas na estrutura, bem como os parametros necessários.
typedef struct Caixa *tipo_caixa;
//Conjunto de operacoes permitidas para o tipo_caixa.
extern size_t obj_size(struct Caixa *);
extern void configura_material_caixa(struct Caixa *,char[]);
extern void configura_id_caixa(struct Caixa *, int);
extern void configura_tamanho_caixa(struct Caixa *,float);
extern void configura_largura_caixa(struct Caixa *,float);
extern void configura_altura_caixa(struct Caixa *,float);
extern int obj_getid(struct Caixa *);
extern void imprime_caixa(struct Caixa *);
extern void imprime_id_caixa(struct Caixa *);
extern void destroi_caixa(struct Caixa *);
#endif // TDA_H_INCLUDED
Implementação (arquivo tda.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "tda.h"
typedef struct Caixa{
char material[20];
float tamanho;
float largura;
float altura;
int id;
};
tipo_caixa Caixa_new(void){
tipo_caixa caixa = malloc(sizeof (struct Caixa));
caixa->id = 1;
strcpy (caixa->material, "teste");
caixa->altura = 10.2;
caixa->largura = 2.3;
caixa->tamanho = 8.4;
return caixa;
}
void destroi_caixa(struct Caixa *o){
free(o);
}
//Implementacao das operacoes permitidas para o tipo_caixa
void configura_material_caixa(struct Caixa *o, char i[]){
strcpy (o->material, i);
}
void configura_id_caixa(struct Caixa *o, int i) {
o->id = i;
}
void configura_tamanho_caixa(struct Caixa *o,float i){
o->tamanho = i;
}
void configura_largura_caixa(struct Caixa *o,float i){
o->largura =i;
}
void configura_altura_caixa(struct Caixa *o,float i){
o->altura = i;
}
int obj_getid(struct Caixa *o) {
return o->id;
}
size_t obj_size(struct Caixa *o) {
return sizeof (*o);
}
void imprime_caixa(struct Caixa *o){
printf("Material de caixa: %s.\n"
"Tamanho: %.1fcm\n"
"Largura: %.1fcm\n"
"Altura: %.1fcm\n"
"Id da caixa %d\n"
"Tamanhho do objeto %u\n\n",
o->material,
o->tamanho,
o->largura,
o->altura,
obj_getid(o),
(unsigned)obj_size(o)
);
}
void imprime_id_caixa(struct Caixa *o){
printf("Imprimindo apenas o Id da caixa\n"
"Id da caixa %d\n", o->id);
}
Implementando conforme indicado acima, é possível criar bibliotecas e as vender. Fornece-se apenas o arquivo .h (header) com as funções permitidas e a implementação já compilada, ficando a cargo do usuário apenas a construção do programa cliente.
GitHub
https://github.com/renatopierri/algoritmos/tree/main/TipoDado
https://github.com/renatopierri/algoritmos/tree/main/TipoDadoUsuario
https://github.com/renatopierri/algoritmos/tree/main/TipoDadoAbstrato
Bibliografia:
TENENBAUM, Aaron M.; LANGSAM, Yedidyah; AUGESNSTEIN, Moshe J.. Estruturas de Dados Usando C. São Paulo: Pearson, 2013. 884 p. ISBN 13: 978-85-346-0348-5
UNIVERSITY, Princeton. Abstract Data Types. 2004. Disponível em: https://www.cs.princeton.edu/courses/archive/spring04/cos217/lectures/Adts.pdf. Acesso em: 19 dez. 2022.
DATA Types vs. Abstract Data Types. Música: Axol X Alex Skrindo – You [Ncs Release]. [S.I.]: Neso Academy, 2020. (8 min.), Youtube, son., color. Disponível em: https://youtu.be/ZniDyolzrBw. Acesso em: 18 dez. 2022.
Curtiu?
Vou ficar muito contente se você clicar nos anúncios do meu site e dar uma olhadinha no que os anunciantes tem de bom para te oferecer.