Estrutura de dados II Carlos Oberdan Rolim Ciência da Computação Sistemas de Informação Revisão Ponteiros, passagem de parâmetros para funções, alocação dinâmica e estruturas Ponteiros Ponteiros em Linguagem C O Que é uma variável? É uma área da memória do computador onde é armazenado um valor…. Exemplo 1: int a = 1; Atribui ao endereço 1000 o valor 1 1000 1 1001 1002 1003 Variável Posição a 1000 Ponteiros em Linguagem C O Que É Um Ponteiro? Um ponteiro é uma variável que aponta para outra variável. Isto significa que um ponteiro mantém o endereço de memória de outra variável. Em outras palavras, o ponteiro não contém um valor no sentido tradicional, mas sim o endereço de outra variável. Um ponteiro "aponta para" esta outra variável mantendo uma cópia de seu endereço Convém dizer que a expressão “apontar para...” significa “armazenar o endereço de memória de...” Como um ponteiro contém um endereço, e não um valor, terá duas partes. O ponteiro contém um endereço e o endereço aponta para um valor. Há o ponteiro e o valor para o qual ele aponta. Este fato pode ser um tanto confuso, até você se familiarizar com ele. Superada a etapa da familiarização, ele se torna extremamente eficaz. Ponteiros em Linguagem C Operadores relacionados a Ponteiros: *(asterisco): informa que uma variável irá armazenar o endereço de outra variável; ou: Informa ao computador que vc deseja o valor que está no endereço armazenado; - Pode ser lido como “no endereço” q = *m; q recebe o valor armazenado no endereço m & (e comercial): retorna o endereço de uma variável; - Pode ser lido como “o endereço de” m = &count; m recebe o endereço de count Ponteiros em Linguagem C int main() { int i,j; int *p; p = &i; *p=5; j=i; printf(("%d %d %d\n", i, j, *p); return 0; } ==> 5 5 5 Operadores de ponteiros * (asterisco) indica que a variável é um ponteiro tipo_dado *nome_ponteiro; Ex: int x; int *pi; /* compilador sabe que pi é ponteiro */ /* pi é um ponteiro para inteiro */ Operadores de ponteiros o operador “&” quando aplicado sobre uma variável retorna o seu endereço Ex: int x = 10, *pi; pi = &x; printf(“&x: %p pi: %p”, &x, pi); => &x: 0x03062fd8 pi: 0x03062fd8 Operadores de ponteiros o operador “*” quando aplicado sobre um ponteiro retorna o dado apontado 0xABA0 Ex: tmp_ptr void main () { int *tmp_ptr; int x, y; x = 10; tmp_ptr = &x; y = *tmp_ptr; /* (*tmp_ptr) = 10 */ } 0xABA0 10 x 0xABA2 10 y Outro exemplo ilustrado int i; int *p; p = &i; *p=5; Relembrando... operador * declara-se com * int *x acessa-se (alterar, modificar, ler) também com * *x = 10; // atribui o valor 10 ao local apontado pelo ponteiro ‘x’ printf(“%d”, *x); // imprime o valor armazenado no local apontado por ‘x’ observação: strings e vetores funcionam de forma diferente: um vetor ou string é um ponteiro por definição operador & acessa (alterar, modificar, ler) o endereço de uma variável (que é um ponteiro) Exemplo de uso declara variavel inteiro com valor 1 int *pt_a; declara um ponteiro para um inteiro pt_a = &a; ponteiro recebe o endereco da variavel a printf(“%d”, *pt_a); imprime o valor apontado pelo ponteiro int a = 1; Ponteiros ponteiros são variáveis tipadas: (int *) ≠ (float *) ≠ (char *) As variaveis ponteiro devem sempre apontar para os tipos de dados corretos. Uma variavel ponteiro declarada como apontador de dados inteiros deve sempre apontar para dados deste tipo. Ex: main() { int *ip, x; float *fp, z; ip = &x; /* OK */ fp = &z; /* OK */ ip = &z; /* erro */ fp = &x; /* erro */ } Ponteiros espaço ocupado pelas variáveis Ponteiro aponta para o tamanho segundo seu tipo 1 byte (int *) 1 byte (float *) (char *) Exemplo de uso Exemplo: int a = 1; int *pt_a; pt_a = &a; 1000 1 1001 1000 1002 1003 Variável Posição a 1000 pt_a 1001 Ponteiros em Linguagem C Onde usar isto??? Funções! Alocação Dinâmica Não sei o tamanho que o vetor precisa ter….! Não sei o tamanho que cada string precisa ter… Não sei o tamanho que a matriz precisa ter… Utilizando Ponteiros void main() { int x = 10; int *pi; pi = &x; /* *pi == 10 */ (*pi)++; /* *pi == 11 */ printf(“%d”, x); } ==> 11 ao alterar *pi estamos alterando o conteúdo de x Utilizando Ponteiros void main() { int x = 10; int *pi, *pj; pi = &x; /* *pi == 10 */ pj = pi; /* *pj == 10 */ (*pi)++; /* (*pi, *pj, x) == 11 */ (*pj)++; /* (*pi, *pj, x) == 12 */ printf(“%d”, x); /* ==> 12 */ printf(“%x”, &pj); /* Endereco de x ==> 0x0c220c */ } Ponteiros e Strings Quando imprimimos uma cadeia de caracteres constantes (string) com printf o que é passado é o ponteiro para a cadeia. Printf(“Ola como vai?”); Dessa forma é possível carregar o endereço da string em um ponteiro do tipo char char * lista; lista = "Ola como vai ?"; printf("%s", lista ); Ponteiros e Strings Na verdade, strings são arrays de caracteres e podem ser acessados através de char * void main () { char str[]=“abcdef”, *pc; for (pc = str; *pc != ‘\0’; pc++) putchar(*pc); } ==> abcdef o incremento de pc o posiciona sobre o próximo caracter (byte a byte) Ponteiros e Strings Outra forma de mostrar uma string usando laço char *origem = "testando"; do{ printf("%c ", *origem); }while (*origem++); /* origem == 0 encerra o laco */ Cuidados com Strings É comum esquecer de alocar uma área para armazenamento de caracteres void main() { char *pc; char str[] = “Um string”; strcpy(pc, str); /* erro! pc indeterminado */ ... } Alocação dinâmica Alocação dinâmica de memória Alocação dinâmica x alocação estática Pode-se alocar dinâmicamente (em tempo de execução) um espaço de memória para uso com arrays, structs, etc... int main() { int *p; p = (int *) malloc(sizeof(int)); if (p == 0) { printf("ERRO: Sem memória\n"); return 1; } *p = 5; printf("&d\n", *p); free(p); return 0; } Aloca de forma dinâmica espaço para um inteiro Alocação dinâmica de memória malloc Utilizamos a função malloc() quando não conseguimos prever a quantidade de memória que nosso programa irá necessitar. A função malloc() pode ser utilizada em run time para determinar o tamanho de um array. Exemplo char * p; p = malloc(50); free Libera memória alocada previamente Exemplo free(p); Alocação dinâmica de memória void main() { int numero = 10; int *arranjo; arranjo=(int *)malloc(numero * sizeof(int)); for (i=0; i < numero; i++) { arranjo[i] = i; } free(arranjo); } Aloca de forma dinâmica espaço para um array de inteiros de 10 posições #include <stdio.h> #include <stdlib.h> #include <conio.h> int main() { int *a; int i, k, m, min, temp, n; /* Alocar memória para guardar o array */ printf("Quantos números quer ordenar? "); scanf("%d", &n); /* Ordenar o array */ for( k=0; k<=n-1; k++ ) { /* descobre o índice do mínimo */ min = a[k]; m = k; for( i=k; i<=n-1; i++ ) if( a[i] < min ){ min = a[i]; m = i; } /* troca a[k] com a[m] */ temp = a[k]; a[k] = a[m]; a[m] = temp; a = (int *) malloc( n * sizeof(int) ); if( a == NULL ) { printf("ERRO: nao ha memoria.\n"); exit(1); } /* Receber os valores a ordenar */ for (i=0; i<n; i++) { printf("%d.º numero -> ",i+1); scanf("%d",&a[i]); } } /* Escrever os elementos ordenados */ for( i=0; i<n; i++ ) printf("%d ", a[i]); printf("\n"); free( a ); /* libertar a memória */ getche(); return 0; } Estrutura de Dados Compostos (Structs) Definição Uma estrutura (struct) ou registro em C é uma coleção de um ou mais valores, agrupados sob um único nome. Estruturas constituem um recurso importante para organizar os dados utilizados por um programa graças à possibilidade de tratar um grupo de valores como uma única variável Exemplo de uso struct ponto { int x; int y; }; struct funcionario { int registro; char nome[30]; char depto[5]; float salario; }; Exemplo de uso As declarações de ponto e funcionario, definem os respectivos tipos de dados, que podem ser utilizados em declarações de variáveis. Exemplos: struct ponto p1, p2, p3; struct funcionario Joao; Na primeira declaração, estão sendo declaradas as variáveis p1, p2 e p3 do tipo ponto. Na segunda declaração, é declarada a variável Joao do tipo funcionário. Exemplo de uso Para uma variável do tipo ponto, dizemos que x e y são seus campos ou membros. Os campos de uma variável podem ser acessados individualmente como variáveis usando-se o nome da variável seguido de "." e o nome do campo. Exemplos: p1.x = 10; p1.y = 20; p2.x = p1.x + 5; p2.y = p2.y + 5; Além disso, é possível atribuir a uma estrutura o valor de outra estrutura do mesmo tipo. Exemplos: funcionario f = Joao; p3 = p2; Estruturas complexas Os campos de uma estrutura podem ser de qualquer tipo: tipos simples (int, char, float, etc), vetores, ou até mesmo estruturas. Exemplo: struct retangulo { struct ponto pa; struct ponto pb; } Uso de estruturas com funções Uma vez que o tipo de uma estrutura foi declarado, é possível utilizá-lo em outras declarações (variáveis simples, vetores, funções, etc). ... struct ponto poligono[10]; ... float dist(ponto p, ponto q) { int dx = p.x - q.x; int dy = p.y - q.y; return sqrt((double)dx*dx + (double)dy*dy); } Passagem de estrutura por referência Uma estrutura pode ser passada como parâmetro por referência numa função. Quando se usa uma referência (apontador), o acesso aos campos da mesma é feito através do operador "->" ao invés de ".". Exemplo: void moveP(ponto* p, int dx, int dy){ p -> x += dx; p -> y += dy; } ... moveP(&(r -> pa), dx, dy); moveP(&(r -> pb), dx, dy); Retornando uma estrutura Uma função pode ter uma estrutura como valor de retorno struct ponto constroiPonto(int x, int y){ struct ponto temp; temp.x = x; temp.y = y; return temp; } ... struct ponto Origem = constroiPonto(0,0); Declaração com valores iniciais Ao declararmos uma estrutura, podemos também definir o seu valor inicial, de forma análoga a aquela utilizada para vetores. Exemplos: struct ponto origem = {0,0}; ... struct ponto trapezio[] = { { 5,5}, {5, 10}, {10,5}, {10,13} }; ... Usando ponteiros com structs struct data{ int dia; int mês; int ano; }; Definindo uma variável do tipo data struct data dt; Definindo um ponteiro para dt struct data *pdt = &dt; Fazendo referência a um elemento da estrutura dt.dia ou (*pdt).dia ou pdt->dia dt.mes ou (*pdt).mes ou pdt->mês dt.ano ou (*pdt).ano ou pdt->ano #include <stdio.h> typedef struct estrutura{ int valor; } ESTRUTURA; Exemplo de programa demonstrando uso de ponteiro, passagem de parâmetro para função e estrutura void incrementa(ESTRUTURA * e){ printf(" Valor --> %i \n", e->valor); printf(" Valor --> %i \n", (*e).valor); /* outra forma de acessar */ (e->valor)++; /* incrementa valor */ } int main(){ ESTRUTURA d; /* declara variavel struct */ d.valor = 0; /* atribui valor inicial */ printf("Antes da funcao --> %d \n", d.valor); incrementa(&d); /* invoca a funcao */ printf("Apos a funcao --> %d\n", d.valor); return 0; } Alocação dinâmica de estruturas É preciso armazenar esse endereço retornado pela malloc num ponteiro de tipo apropriado Para alocar um tipo de dado que ocupa vários bytes, é preciso recorrer ao operador sizeof, que diz quantos bytes o tipo especificado tem Outra forma de declaração typedef struct tipo_data{ int dia, mes, ano; } DATA; Vamos utilizar outra forma de declaração da struct: struct tipo_data{ int dia, mes, ano; }; typedef struct tipo_data DATA; DATA *d; d = malloc (sizeof (DATA)); d->dia = 31; d->mes = 12; d->ano = 2008; ATENÇÃO: data é o nome to tipo