manual psi - m5

39
Disciplina: Programação de Sistemas Informáticos Ano Letivo 2011/2012 Aida Meira Curso Profissional de Técnico de Gestão e Programação de Sistemas Informáticos Módulo V Estrutura de Dados Composta

Upload: aida-meira

Post on 28-Mar-2016

270 views

Category:

Documents


20 download

DESCRIPTION

Estruturas de Dados Compostas

TRANSCRIPT

Page 1: Manual PSI - M5

Disciplina: Programação de Sistemas Informáticos

Ano Letivo 2011/2012

Aida Meira

Curso Profissional de Técnico de Gestão e Programação de Sistemas Informáticos

Módulo V

Estrutura de Dados Composta

Page 2: Manual PSI - M5

EstruturasdeDadosCompostas M"

2 Professora Aida Meira

INDICE

1. Apontadores .................................................................................................. 3

A memória ........................................................................................................ 3

Apontadores ..................................................................................................... 5

Declaração ..................................................................................................... 10

Operadores .................................................................................................... 11

Expressões ..................................................................................................... 12

Comparação ................................................................................................... 13

Ponteiros e vetores ........................................................................................ 13

Ponteiros como parametros de funções ......................................................... 16

Ponteiros para funções .................................................................................. 20

2. Tipos Estruturados ...................................................................................... 23

O tipo estrutura ............................................................................................... 23

Ponteiro para estruturas ................................................................................. 25

Passagem de estruturas para funções ........................................................... 26

Definição de “novos” tipos .............................................................................. 28

Vetores de estruturas ..................................................................................... 30

Vetores de ponteiros para estruturas ............................................................. 32

Page 3: Manual PSI - M5

EstruturasdeDadosCompostas M"

3 Professora Aida Meira

1. Apontadores

AMEMÓRIAA memória de um computador encontra-se organizada em células ou palavras

de memória individuais. Cada palavra de memória é referenciada por um

endereço e armazena um dado conteúdo. Designa-se por “número de bits da

arquitetura” o número máximo de bits que um processador ´e capaz de ler num

único acesso à memória. Cada palavra de memória de referência de um

processador tem em geral um número de bits idêntico ao número de bits da

arquitetura.

DOUBLE FLOAT

i=2450

k=113

j=11331

F=225.345 22.5E+145

INT

Page 4: Manual PSI - M5

EstruturasdeDadosCompostas M"

4 Professora Aida Meira

Admite-se aqui que a palavra de memória e o tipo inteiro são representados por

32 bits.

De acordo com a figura, durante a compilação foram atribuídos as variáveis i, j, k, f os endereços 1002,1005, 1006 e 1004, respetivamente, enquanto a

variável d foi atribuído o endereço 1007.

Note-se que, com exceção do tipo double, cada variável tem uma

representação interna de 32 bits, ocupando por isso apenas um endereço de

memória. A variável d, de tipo double, tem uma representação interna de 64

bits, exigindo por isso duas palavras de memória: os endereços 1007 e 1008.

Na figura encontram-se representados outras posições de memória,

provavelmente ocupadas por outras variáveis, e cujo conteúdo é desconhecido

do programador.

Page 5: Manual PSI - M5

EstruturasdeDadosCompostas M"

5 Professora Aida Meira

APONTADORES

Um ponteiro é uma variável que contém um endereço

de memória, normalmente referenciando a posição de

uma outra variável.

(Schildt-1996)

Um apontador é uma variável cujo conteúdo é um endereço de outra posição

de memória. A declaração de variáveis do tipo apontador pode ser construída a

partir de qualquer tipo definido anteriormente, e deve especificar o tipo de

variável referenciada pelo apontador.

A declaração de uma variável do tipo apontador é feita colocando um * antes

do nome da variável. Assim, na declaração:

As variáveis i, j, k, m são do tipo int, f é do tipo float e d, d2 do tipo double.

Além destas variáveis de tipos elementares, são declaradas as variáveis pi, pi2

do tipo apontador para inteiro, enquanto pd e pd2 são do tipo apontador para

double.

Page 6: Manual PSI - M5

EstruturasdeDadosCompostas M"

6 Professora Aida Meira

Analogamente o funcionamento de um ponteiro é assim:

Esquematicamente:

Page 7: Manual PSI - M5

EstruturasdeDadosCompostas M"

7 Professora Aida Meira

Exemplo:FunçãoTroca

Função para trocar o valor de duas variáveis inteiras:

– Void troca (int a, int b)

Essa função tem o código:

int aux;

aux = a;

a = b;

b = aux;

• Vamos agora chamar a função troca no main, onde a=5 e b=10.

• Qual é o valor de cada variável depois de chamares a função?

a = 10 e b=5 , Não! O valor fica igual.

O que a função recebeu, na realidade, não foi a variável A nem a variável B.

Ela recebeu os seus valores.

Portanto, dentro da função, realmente houve uma troca mas as variáveis locais

de cada função "morrem" quando a função termina.

Sendo assim, de volta no main, não houve alteração de valores.

Em vez de receberes o valor de cada variável, recebes o local onde esse valor

está armazenado!

Assim podes alterar directamente o seu valor e assim, se mudas o valor no sitio

onde ele está guardado, esse valor é alterado tanto dentro das funções como

fora.

Portanto mudas o cabeçalho da função para:

void swap( int * a, int * b);

Page 8: Manual PSI - M5

EstruturasdeDadosCompostas M"

8 Professora Aida Meira

e o código para:

int aux;

aux = *a;

*a = *b;

*b = aux;

Assim sendo, estás mesmo a alterar a "raiz" da variável e assim, no main, a

alteração mantém-se.

Tal como pode ser observado, a solução adotada consiste em passar à

função não o valor das variáveis a e b, mas sim os seus endereços. Embora

estes endereços sejam passados por valor (ou seja a função recebe uma

copia destes valores), o endereço permite à função o conhecimento da

Page 9: Manual PSI - M5

EstruturasdeDadosCompostas M"

9 Professora Aida Meira

posição das variáveis a e b em memoria e, deste modo, permite a

manipulação do seu conteúdo por meio de um endereçamento indireto.

Vamos observar o mapa de memória das diferentes fases que passa no

programa anterior:

Imagem 1 - Antes da chamada da função

Imagem 2 - No inicio da troca

Page 10: Manual PSI - M5

Imagem 3 - No final da troca

Imagem 4 - Após o regresso ao main

DECLARAÇÃO

A declaração de uma variável do tipo ponteiro (ou apontador) consiste do tipo base (aquele para o qual o ponteiro vai apontar), um * e o nome da variável.

A forma geral é:

tipo *nome; ou tipo* nome;

Exemplos:

int *contador; ponteiro para um inteiro

char *meuString; ponteiro para caracteres

float *raizQuadrada; ponteiro para real.

Page 11: Manual PSI - M5

EstruturasdeDadosCompostas M"

11 Professora Aida Meira

Caso especial: void *simplesPonteiro; ponteiro genérico.

OPERADORES

Existem dois operadores especiais para ponteiros:

* indireção. – Devolve o valor apontado pelo ponteiro.

& operador de endereço. – Devolve o endereço na memória de seu operando.

Exemplo:

main () { int *aponta; int valor1, valor2; valor1 = 5; // inicializa valor1 com 5 aponta = &valor1; // aponta recebe o endereço de valor1, ou seja:

passa a apontar para valor1 valor2 = *aponta; // valor2 recebe o valor apontado por aponta,

nesse caso 5, pois aponta possui como valor o endereço de valor1

}

Page 12: Manual PSI - M5

EstruturasdeDadosCompostas M"

12 Professora Aida Meira

Precedência: Tanto o & como o * possuem precedência maior do que todos os

outros operadores, com exceção do menos unário, que possuem a mesma.

– int valor; int *aponta; valor = *aponta++

EXPRESSÕESATRIBUIÇÃO

Atribuição direta entre ponteiros passa o endereço de memória apontado por

um para o outro.

int *p1, *p2, x;

x = 4;

p1 = &x; /* p1 passa a apontar para x */

p2 = p1; /* p2 recebeu o valor de p1, que é */

/* o endereço de x, ou seja: p2 */

/* também aponta para x. */

printf ("%p", p2 ); /* imprime o endereço de x */

printf ("%i", *p2 ); /* imprime o valor apontado por */

/* p2, seja: o valor de x. */

O operador de endereço &, quando usado como operador sobre um ponteiro,

devolve o endereço ocupado por este ponteiro, não o endereço apontado por

ele.

Duas operações aritméticas são válidas com ponteiros: adição e subtração.

Estas são muito úteis com vetores.

A expressão abaixo é válida em "C":

int *p1, *p2, *p3, *p4, x=0;

p1 = &x;

p2 = p1++;

p3 = p2 + 4;

p4 = p3 - 5; /* p4 acaba tendo o mesmo valor que p1 */

/* no começo. */

Page 13: Manual PSI - M5

EstruturasdeDadosCompostas M"

13 Professora Aida Meira

/* Note que p1 foi incrementado e */

/* agora tem o valor (&x + 1). */

Observe que aqui as expressões *p2 e *p3 vão resultar em um erro, já que

esses ponteiros estarão apontando para áreas de memória que não estão

associadas com nenhuma variável. O único endereço de memória acessável é

o de x.

COMPARAÇÃOPodes comparar ponteiros, para saber se um ponteiro aponta para um

endereço de memória mais alto do que outro.

Exemplo:

int *p, *q;

....

if (p < q)

printf("p aponta para um endereço menor que o de q");

É um trecho de programa perfeitamente válido em "C".

– Isto pode ser útil em testes em matrizes e vetores.

PONTEIROSEVETORESPonteiros, Vetores e Matrizes possuem uma relação muito estreita em "C”

A qual podemos aproveitar de muitas formas para escrever programas que

ninguém entende...

Em C, os elementos de um vetor são sempre guardados sequencialmente, a

uma distância fixa um do outro. Com isso, é possível facilmente passar de um

elemento a outro, percorrendo sempre uma mesma distância para frente ou

Page 14: Manual PSI - M5

EstruturasdeDadosCompostas M"

14 Professora Aida Meira

para trás na memória. Dessa maneira, podemos usar ponteiros e a aritmética

de ponteiros para percorrer vetores. Na verdade, vetores são ponteiros ― um

uso particular dos ponteiros.

Exemplo:

char nome[30] = "José da Silva"; char *p1, *p2; char car; int i; p1 = nome; // nome sozinho é um ponteiro // para o 1º elemento de nome[]. car = nome[3]; // Atribui 'é' a car. car = p1[0]; // Atribui 'J' a car. Válido. p2 = &nome[5]; // Atribui a p2 o endereço da 6ª // posição de nome, no caso 'd'. printf( "%s", p2); // Imprime "da Silva"... p2 = p1; // Evidentemente válido. p2 = p1 + 5; // Equivalente a p2 = &nome[5] printf( "%s",(p1 + 5)); // Imprime "da Silva"... printf( "%s",(p1 + 20)); // Cuidado: Imprime lixo!! for (i=0; strlen(nome)- 1; i++) { printf ("%c", nome[i]); // Imprime 'J','o','s',etc p2 = p1 + i; printf ("%c", *p2); // Imprime 'J','o','s',etc }

Acompanhe o exemplo a seguir.

Page 15: Manual PSI - M5

EstruturasdeDadosCompostas M"

15 Professora Aida Meira

Começamos declarando um vetor com três elementos; depois, criamos um

ponteiro para esse vetor. Mas repare que não colocamos o operador de endereço em vetorTeste; fazemos isso porque um vetor já representa um

endereço, como você pode verificar pelo resultado da primeira chamada a

printf().

Podemos usar a sintaxe *(ptr + 1) para aceder o inteiro seguinte ao

apontado pelo ponteiro ptr. Mas, se o ponteiro aponta para o vetor, o próximo

inteiro na memória será o próximo elemento do vetor!

De fato, em C as duas formas *(ptr + n) e ptr[n] são equivalentes.

Não é necessário criar um ponteiro para usar essa sintaxe; o vetor em si já é

um ponteiro, de modo que qualquer operação com ptr será feita igualmente

com vetorTeste. Todas as formas abaixo de aceder o segundo elemento do

vetor são equivalentes:

vetorTeste[1];

*(vetorTeste + 1);

ptr[1];

*(ptr + 1)

Page 16: Manual PSI - M5

EstruturasdeDadosCompostas M"

16 Professora Aida Meira

Exemplo:

PONTEIROSCOMOPARAMETROSDEFUNÇÕES

Comecemos por uma situação-problema: eu tenho 2 variáveis e quero trocar o

valor delas. Vamos começar com um algoritmo simples, dentro da função

main():

Page 17: Manual PSI - M5

EstruturasdeDadosCompostas M"

17 Professora Aida Meira

Esse exemplo funcionará exatamente como esperado: primeiramente ele

imprimirá "5 10" e depois ele imprimirá "10 5". Mas e se quisermos trocar várias

vezes o valor de duas variáveis? É muito mais conveniente criar uma função

que faça isso. Vamos fazer uma tentativa de implementação da função swap

(troca, em inglês):

Page 18: Manual PSI - M5

EstruturasdeDadosCompostas M"

18 Professora Aida Meira

No entanto, o que queremos não irá acontecer.

Verá que o programa imprime duas vezes "5 10". Por que isso acontece?

Lembre-se do escopo das variáveis: as variáveis a e b são locais à função

main(), e quando as passamos como argumentos para swap(), os valores são

copiados e passam a ser chamados de i e j; a troca ocorre entre i e j, de modo

que quando voltamos à função main() nada mudou.

Então como poderíamos fazer isso? Como são retornados dois valores, não

podemos usar o valor de retorno de uma função. Mas existe uma alternativa: os

ponteiros.

Page 19: Manual PSI - M5

EstruturasdeDadosCompostas M"

19 Professora Aida Meira

Neste exemplo, definimos a função swap() como uma função que toma como

argumentos dois ponteiros para inteiros; a função faz a troca entre os valores

apontados pelos ponteiros. Já na função main(), passamos os endereços das

variáveis para a função swap(), de modo que a função swap() possa modificar

variáveis locais de outra função. O único possível inconveniente é que, quando

usarmos a função, teremos de lembrar de colocar um & na frente das variáveis

que estivermos passando para a função.

Se pensar bem, já vimos uma função em que passamos os argumentos

precedidos de &: é a função scanf()!

Por que fazemos isso?

É simples: chamamos a função scanf() para que ela ponha nas nossas

variáveis valores digitados pelo usuário.

Essas variáveis são locais, e portanto só podem ser alteradas por outras

funções através de ponteiros!

Quando uma função recebe como parâmetros os endereços e não os valores

das variáveis, dizemos que estamos a fazer uma chamada por referência; é o

caso desse último exemplo. Quando passamos diretamente os valores das

Page 20: Manual PSI - M5

EstruturasdeDadosCompostas M"

20 Professora Aida Meira

variáveis para uma função, dizemos que é uma chamada por valor; foi o caso

do segundo exemplo.

PONTEIROSPARAFUNÇÕESOs ponteiros para funções servem, geralmente, para passar uma função como

argumento de uma outra função.

Neste exemplo:

Veja que criamos uma função que retorna a soma dos dois inteiros a ela

fornecidos; no entanto, ela não é chamada diretamente.

Ela é chamada pela função operação, através de um ponteiro. A função main

passa a função soma como argumento para operação, e a função operação

chama essa função que lhe foi dada como argumento.

Page 21: Manual PSI - M5

EstruturasdeDadosCompostas M"

21 Professora Aida Meira

Note bem o terceiro argumento da função operação: ele é um ponteiro para uma função.

Neste caso, ele foi declarado como um ponteiro para uma função que toma

dois inteiros como argumentos e retorna outro inteiro.

O * indica que estamos declarando um ponteiro, e não uma função. Os

parênteses em torno de *func são essenciais, pois sem eles o compilador

entenderia o argumento como uma função que retorna um ponteiro para um

inteiro.

A forma geral para declarar um ponteiro para uma função é: tipo_retorno (*nome_do_ponteiro)(lista de argumentos)

Para chamar a função apontada pelo ponteiro, há duas sintaxes. A sintaxe

original é: (*nome_do_ponteiro)(argumentos);

Se ptr é um ponteiro para uma função, faz bastante sentido que a função em si

seja chamada por *ptr. No entanto, a sintaxe mais moderna permite que

ponteiros para funções sejam chamados exatamente da mesma maneira que

funções: nome_do_ponteiro(argumentos);

Por fim, para inicializar um ponteiro para função, não precisamos usar o

operador de endereço (ele já está implícito). Por isso, quando chamamos a

função operação, não precisamos escrever &soma.

Veja mais um exemplo — na verdade, uma extensão do exemplo

anterior:

Page 22: Manual PSI - M5

EstruturasdeDadosCompostas M"

22 Professora Aida Meira

Aqui, criamos mais uma função, subtracao, além de criar um outro ponteiro

para ela (uma espécie de "atalho"), menos. Na função main, referimo-nos à

função de subtração através desse atalho.

Veja também que aqui usamos a sintaxe moderna para a chamada de

ponteiros de funções, ao contrário do exemplo anterior.

Page 23: Manual PSI - M5

EstruturasdeDadosCompostas M"

23 Professora Aida Meira

2. TiposEstruturados

Na linguagem C, existem os tipos básicos (char, int, float, etc.) e seus

respetivos ponteiros que podem ser usados na declaração de variáveis. Para

estruturar dados complexos, nos quais as informações são compostas por

diversos campos, necessitamos de mecanismos que nos permitam agrupar

tipos distintos.

OTIPOESTRUTURAEm C, podemos definir um tipo de dado cujos campos são compostos de vários

valores de tipos mais simples. Para ilustrar, vamos considerar o

desenvolvimento de programas que manipulam pontos no plano cartesiano.

Cada ponto pode ser representado por suas coordenadas x e y, ambas dadas

por valores reais.

Sem um mecanismo para agrupar as duas componentes, teríamos que

representar cada ponto por duas variáveis independentes.

float x;

float y;

No entanto, deste modo, os dois valores ficam dissociados e, no caso do

programa manipular vários pontos, cabe ao programador não misturar a

coordenada x de um ponto com a coordenada y de outro.

Para facilitar este trabalho, a linguagem C oferece recursos para agruparmos

dados. Uma estrutura, em C, serve basicamente para agrupar diversas

variáveis dentro de um único contexto.

No nosso exemplo, podemos definir uma estrutura ponto que contenha as duas

variáveis.

A sintaxe para a definição de uma estrutura é mostrada abaixo:

Page 24: Manual PSI - M5

EstruturasdeDadosCompostas M"

24 Professora Aida Meira

Desta forma, a estrutura ponto passa a ser um tipo e podemos então declarar

variáveis deste tipo.

struct ponto p;

Esta linha de código declara p como sendo uma variável do tipo struct ponto.

Os elementos de uma estrutura podem ser acedidos através do operador de

acesso “ponto” (.). Assim, é válido escrever:

Manipulamos os elementos de uma estrutura da mesma forma que variáveis

simples.

Podemos aceder seus valores, atribuir-lhes novos valores, aceder seus

endereços, etc.

Exemplo:Capturar e imprimir as coordenadas de um ponto.

Para exemplificar o uso de estruturas em programas, vamos considerar um

exemplo simples em que capturamos e imprimimos as coordenadas de um

ponto qualquer.

Page 25: Manual PSI - M5

EstruturasdeDadosCompostas M"

25 Professora Aida Meira

A variável p, definida dentro de main, é uma variável local como outra qualquer.

Quando a declaração é encontrada, aloca-se, na pilha de execução, um

espaço para seu armazenamento, isto é, um espaço suficiente para armazenar

todos os campos da estrutura (no caso, dois números reais).

Atenção que o acesso ao endereço de um campo da estrutura é feito da

mesma forma que com variáveis simples: basta escrever &(p.x), ou

simplesmente &p.x, pois o operador de acesso ao campo da estrutura tem

precedência sobre o operador “endereço de”.

PONTEIROPARAESTRUTURASDa mesma forma que podemos declarar variáveis do tipo estrutura:

struct ponto p;

Podemos também declarar variáveis do tipo ponteiro para estrutura: struct ponto *pp;

Se a variável pp armazenar o endereço de uma estrutura, podemos aceder os

campos dessa estrutura indiretamente, através de seu ponteiro: (*pp).x = 12.0;

Neste caso, os parênteses são indispensáveis, pois o operador “conteúdo de”

tem precedência menor que o operador de acesso. O acesso de campos de

estruturas é tão comum em programas C que a linguagem oferece outro

Page 26: Manual PSI - M5

EstruturasdeDadosCompostas M"

26 Professora Aida Meira

operador de acesso, que permite aceder campos a partir do ponteiro da

estrutura. Este operador é composto por um traço seguido de um sinal de

maior, formando uma seta (->).

Portanto, podemos reescrever a atribuição anterior fazendo: pp->x = 12.0;

Em resumo, se temos uma variável estrutura e queremos aceder seus campos,

usamos o operador de acesso ponto (p.x); se temos uma variável ponteiro

para estrutura, usamos o operador de acesso seta (pp->x).

Seguindo o raciocínio, se temos o ponteiro e queremos aceder o endereço de

um campo, fazemos &pp->x

PASSAGEMDEESTRUTURASPARAFUNÇÕESPara exemplificar a passagem de variáveis do tipo estrutura para funções,

podemos reescrever o programa simples, mostrado anteriormente, que captura

e imprime as coordenadas de um ponto qualquer. Inicialmente, podemos

pensar em escrever uma função que imprima as coordenadas do ponto. Esta

função poderia ser dada por:

void imprime (struct ponto p)

{

printf("O ponto fornecido foi: (%.2f,%.2f)\n", p.x,

p.y);

}

A passagem de estruturas para funções processa-se de forma análoga à

passagem de variáveis simples, porém exige uma análise mais detalhada. Da

forma como está escrita no código acima, a função recebe uma estrutura inteira

como parâmetro.

Portanto, faz-se uma cópia de toda a estrutura e a função acede aos dados

desta cópia.

Page 27: Manual PSI - M5

EstruturasdeDadosCompostas M"

27 Professora Aida Meira

Existem dois pontos a serem ressaltados. Primeiro, como em toda passagem

por valor, a função não tem como alterar os valores dos elementos da estrutura

original (na função imprime isso realmente não é necessário, mas seria numa

função de leitura).

O segundo ponto diz respeito à eficiência, visto que copiar uma estrutura

inteira pode ser uma operação custosa (principalmente se a estrutura for muito

grande).

É mais conveniente passar apenas o ponteiro da estrutura, mesmo que não

seja necessário alterar os valores dos elementos dentro da função.

Desta forma, uma segunda (e mais adequada) alternativa para escrevermos a

função imprime é:

void imprime (struct ponto* pp)

{

printf("O ponto fornecido foi: (%.2f,%.2f)\n", pp->x,

pp->y);

}

Podemos ainda pensar numa função para ler a hora do evento. Observamos

que, neste caso, obrigatoriamente devemos passar o ponteiro da estrutura,

caso contrário não seria possível passar ao programa principal os dados lidos:

void captura (struct ponto* pp) {

printf("Digite as coordenadas do ponto(x y): ");

scanf("%f %f", &p->x, &p->y);

}

Com estas funções, nossa função main ficaria como mostrado abaixo. int main (void) {

struct ponto p;

captura(&p);

imprime(&p);

Page 28: Manual PSI - M5

EstruturasdeDadosCompostas M"

28 Professora Aida Meira

return 0;

}

Exercício:Função para determinar a distância entre dois pontos.

Considere a implementação de uma função que tenha como valor de retorno a

distância entre dois pontos. O protótipo da função pode ser dado por:

DEFINIÇÃODE“NOVOS”TIPOSA linguagem C permite criar nomes de tipos. Por exemplo, se escrevermos:

typedef float Real;

Podemos usar o nome Real como um mnemônico para o tipo float. O uso de

typedef é muito útil para abreviarmos nomes de tipos e para tratarmos tipos

complexos. Alguns exemplos válidos de typedef:

typedef unsigned char UChar;

typedef int* PInt;

typedef float Vetor[4];

Neste fragmento de código, definimos UChar como sendo o tipo char sem

sinal, PInt como um tipo ponteiro para int, e Vetor como um tipo que

representa um vetor de quatro elementos. A partir dessas definições, podemos

declarar variáveis usando estes mnemônicos:

Vetor v;

Page 29: Manual PSI - M5

EstruturasdeDadosCompostas M"

29 Professora Aida Meira

...

v[0] = 3;

...

Em geral, definimos nomes de tipos para as estruturas com as quais nossos

programas trabalham. Por exemplo, podemos escrever:

struct ponto {

float x;

float y;

};

typedef struct ponto Ponto;

Neste caso, Ponto passa a representar nossa estrutura de ponto. Também

podemos definir um nome para o tipo ponteiro para a estrutura.

typedef struct ponto *PPonto;

Podemos ainda definir mais de um nome num mesmo typedef. Os dois typedef

anteriores poderiam ser escritos por:

typedef struct ponto Ponto, *PPonto;

A sintaxe de um typedef pode parecer confusa, mas é equivalente à da

declaração de variáveis. Por exemplo, na definição abaixo:

typedef float Vector[4];

Se omitíssemos a palavra typedef, estaríamos a declarar a variável Vector

como sendo um vetor de 4 elementos do tipo float. Com typedef, estamos a

definir um nome que representa o tipo vetor de 4 elementos float.

De maneira análoga, na definição:

Page 30: Manual PSI - M5

EstruturasdeDadosCompostas M"

30 Professora Aida Meira

typedef struct ponto Ponto, *PPonto;

Se omitíssemos a palavra typedef, estaríamos a declarar a variável Ponto

como sendo do tipo struct ponto e a variável PPonto como sendo do tipo

ponteiro para

struct ponto.

Por fim, salienta-se que podemos definir a estrutura e associar mnemônicos

para elas em um mesmo comando:

typedef struct ponto {

float x;

float y;

} Ponto, *PPonto;

É comum os programadores de C usarem nomes com as primeiras letras

maiúsculas na definição de tipos. Isso não é uma obrigatoriedade, apenas um

estilo de codificação.

VETORESDEESTRUTURASJá discutimos o uso de vetores para agrupar elementos dos tipos básicos

(vetores de inteiros, por exemplo).

Nesta seção, vamos discutir o uso de vetores de estruturas, isto é, vetores

cujos elementos são estruturas. Para ilustrar a discussão, vamos considerar o

cálculo da área de um polígono plano qualquer delimitado por uma sequência

de npontos. A área desse polígono pode ser calculada somando-se as áreas

dos trapézios formados pelos lados do polígono e o eixo x, conforme ilustra a

Figura

Page 31: Manual PSI - M5

EstruturasdeDadosCompostas M"

31 Professora Aida Meira

FIGURA: Cálculo da área de um polígono

Na figura, ressaltamos a área do trapézio definido pela aresta que vai do ponto

pi ao ponto pi+1. A área desse trapézio é dada por: ( )( ) / 2 i 1 i i 1 i a # x ! x y "

y " " . Somando-se as “áreas” (algumas delas negativas) dos trapézios

definidos por todas as arestas chega-se a área do polígono (as áreas externas

ao polígono são anuladas). Se a sequência de pontos que define o polígono for

dada em sentido anti-horário, chega-se a uma “área” de valor negativo.

Neste caso, a área do polígono é o valor absoluto do resultado da soma.

Um vetor de estruturas pode ser usado para definir um polígono. O polígono

passa a ser representado por uma sequência de pontos. Podemos, então,

escrever uma função para calcular a área de um polígono, dados o número de

pontos e o vetor de pontos que o representa. Uma implementação dessa

função é mostrada abaixo.

Um exemplo de uso dessa função é mostrado no código abaixo:

Page 32: Manual PSI - M5

EstruturasdeDadosCompostas M"

32 Professora Aida Meira

int main (void){

Ponto p[3] = {{1.0,1.0},{5.0,1.0},{4.0,3.0}};

printf("area = %f\n",area (3,p));

return 0;

}

Exercício:Altere o programa acima para capturar do teclado o número de pontos que

delimitam o polígono. O programa deve, então, alocar dinamicamente o vetor

de pontos, capturar as coordenadas dos pontos e, chamando a função área,

exibir o valor da área.

VETORESDEPONTEIROSPARAESTRUTURASDa mesma forma que podemos declarar vetores de estruturas, podemos

também declarar vetores de ponteiros para estruturas. O uso de vetores de

ponteiros é útil quando temos que tratar um conjunto elementos complexos.

Para ilustrar o uso de estruturas complexas, consideremos um exemplo em que

desejamos armazenar uma tabela com dados de alunos.

Podemos organizar os dados dos alunos em um vetor. Para cada aluno, vamos

supor que sejam necessárias as seguintes informações:

• nome:cadeiacomaté80caracteres• matricula:númerointeiro• endereço:cadeiacomaté120caracteres• telefone:cadeiacomaté20caracteres

Para estruturar esses dados, podemos definir um tipo que representa os dados

de um aluno:

struct aluno {

char nome[81];

Page 33: Manual PSI - M5

EstruturasdeDadosCompostas M"

33 Professora Aida Meira

int mat;

char end[121];

char tel[21];

};

typedef struct aluno Aluno;

Vamos montar a tabela de alunos usando um vetor global com um número

máximo de alunos. Uma primeira opção é declarar um vetor de estruturas:

#define MAX 100

Aluno tab[MAX];

Desta forma, podemos armazenar nos elementos do vetor os dados dos alunos

que queremos organizar. Seria válido, por exemplo, uma atribuição do tipo:

...

tab[i].mat = 9912222;

...

No entanto, o uso de vetores de estruturas tem, neste caso, uma grande

desvantagem.

O tipo Aluno definido acima ocupa pelo menos 227 (=81+4+121+21) bytes1. A

declaração de um vetor desta estrutura representa um desperdício significativo

de memória, pois provavelmente estamos a armazenar um número de alunos

bem inferior ao máximo estimado. Para contornar este problema, podemos

trabalhar com um vetor de ponteiros.

typedef struct aluno *PAluno;

#define MAX 100

PAluno tab[MAX];

Assim, cada elemento do vetor ocupa apenas o espaço necessário para

armazenar um ponteiro. Quando precisarmos alocar os dados de um aluno

Page 34: Manual PSI - M5

EstruturasdeDadosCompostas M"

34 Professora Aida Meira

numa determinada posição do vetor, alocamos dinamicamente a estrutura

Aluno e guardamos seu endereço no vetor de ponteiros.

Considerando o vetor de ponteiros declarado acima como uma variável global,

podemos ilustrar a implementação de algumas funcionalidades para manipular

nossa tabela de alunos. Inicialmente, vamos considerar uma função de

inicialização. Uma posição do vetor estará vazia, isto é, disponível para

armazenar informações de um novo aluno, se o valor do seu elemento for o

ponteiro nulo. Portanto, numa função de inicialização, podemos atribuir NULL a

todos os elementos da tabela, significando que temos, a princípio, uma tabela

vazia.

void inicializa (void)

{

int i;

for (i=0; i<MAX; i++)

tab[i] = NULL;

}

Uma segunda funcionalidade que podemos prever armazena os dados de um

novo aluno numa posição do vetor. Vamos considerar que os dados serão

fornecidos via teclado e que uma posição onde os dados serão armazenados

será passada para a função. Se a posição da tabela estiver vazia, devemos

alocar uma nova estrutura; caso contrário, atualizamos a estrutura já apontada

pelo ponteiro. void preenche (int i)

{

if (tab[i]==NULL)

tab[i] = (PAluno)malloc(sizeof(Aluno));

printf("Entre com o nome:");

scanf(" %80[^\n]", tab[i]->nome);

Page 35: Manual PSI - M5

EstruturasdeDadosCompostas M"

35 Professora Aida Meira

printf("Entre com a matricula:");

scanf("%d", &tab[i]->mat);

printf("Entre com o endereco:");

scanf(" %120[^\n]", tab[i]->end);

printf("Entre com o telefone:");

scanf(" %20[^\n]", tab[i]->tel);

Podemos também prever uma função para remover os dados de um aluno da

tabela. Vamos considerar que a posição da tabela a ser liberada será passada

para a função:

void remove (int i) {

if (tab[i] != NULL) {

free(tab[i]);

tab[i] = NULL;

}

}

Para consultarmos os dados, vamos considerar uma função que imprime os

dados armazenados numa determinada posição do vetor:

void imprime (int i) {

if (tab[i] != NULL) {

printf("Nome: %s\n”, tab[i]->nome);

printf("Matrícula: %d\n”, tab[i]->mat);

printf("Endereço: %s\n”, tab[i]->end);

printf("Telefone: %s\n”, tab[i]->tel);

}

}

Page 36: Manual PSI - M5

EstruturasdeDadosCompostas M"

36 Professora Aida Meira

Por fim, podemos implementar uma função que imprima os dados de todos os

alunos da tabela:

void imprime_tudo (void) {

int i;

for (i=0; i<MAX; i++)

imprime(i);

}

Exercício: Faça um programa que utilize as funções da tabela de alunos escritas acima.

Exercício:Re-escreva as funções acima sem usar uma variável global.

Sugestão: Crie um tipo Tabela e faça as funções receberem este tipo como

primeiro parâmetro.

Page 37: Manual PSI - M5

EstruturasdeDadosCompostas M"

37 Professora Aida Meira

Page 38: Manual PSI - M5

EstruturasdeDadosCompostas M"

38 Professora Aida Meira

EXERCICIOS

Page 39: Manual PSI - M5

EstruturasdeDadosCompostas M"

39 Professora Aida Meira