Instituto Federal de Santa Catarina - Câmpus Chapecó Ensino Médio Integrado em Informática - Módulo IV Unidade Curricular: Programação Estruturada Professora: Lara Popov Zambiasi Bazzi Oberderfer Sumário 1 Ponteiros.......................................................................................................................................................1 1.1 Endereços.............................................................................................................................................1 1.2 Declarando Ponteiros............................................................................................................................2 1.3 Manipulação de Ponteiros....................................................................................................................2 1.4 Expressões com Ponteiros.....................................................................................................................2 1.5 Ponteiros para ponteiros.......................................................................................................................3 1.6 Problemas com ponteiros.....................................................................................................................4 1.7 Exemplos de Ponteiros..........................................................................................................................4 1.8 Aplicação...............................................................................................................................................4 2 Vetores e endereços.....................................................................................................................................5 3 Alocação dinâmica de memória....................................................................................................................6 4 Exercícios......................................................................................................................................................6 5 Referências Bibliográficas.............................................................................................................................7 1 Ponteiros É uma variável que contém o endereço de outra variável. Os ponteiros são utilizados para alocação dinâmica, podendo substituir matrizes com mais eficiência. Também fornecem a maneira pelas quais funções podem modificar os argumentos chamados, como veremos no capítulo de funções. Sintaxe: tipo *nome_variavel; 1.1 Endereços A memória de qualquer computador é uma sequência de bytes. Cada byte armazena um de 256 possíveis valores. Os bytes são numerados sequencialmente. O número de um byte é o seu endereço (=address). Cada objeto na memória do computador ocupa um certo número de bytes consecutivos. No meu computador, um char ocupa 1 byte, um int ocupa 4 bytes e um double ocupa 8 bytes. Cada objeto na memória do computador tem um endereço. Na maioria dos computadores, o endereço de um objeto é o endereço do seu primeiro byte. Por exemplo, depois das declarações char c; int i; struct { int x, y; } ponto; int v[4]; os endereços das variáveis poderiam ser: c 89421 i 89422 ponto 89426 1 v[0] 89434 v[1] 89438 v[2] 89442 (O exemplo é fictício). O endereço de uma variável é dado pelo operador &. (Não confunda esse uso de "&" com o operador lógico and, que em C se escreve "&&".) Se i é uma variável então &i é o seu endereço. No exemplo acima, &i vale 89422 e&v[3] vale 89446. Exemplo: O segundo argumento da função de biblioteca scanf é o endereço da posição na memória onde devem ser depositados os objetos lidos no dispositivo padrão de entrada: int i; scanf( "%d", &i); 1.2 Declarando Ponteiros Um ponteiro (apontador / pointer) é um tipo especial de variável que armazena endereços. Se uma variável conterá um ponteiro, então ela dever ser declarada como tal: int x,*px; px=&x; /*a variável px aponta para x */ Se quisermos utilizar o conteúdo da variável para qual o ponteiro aponta: y=*px; O que é a mesma coisa que: y=x; 1.3 Manipulação de Ponteiros Desde que os pointers são variáveis, eles podem ser manipulados como as variáveis podem. Se py é um outro ponteiro para um inteiro então podemos fazer a declaração: py=px; Exemplo: int main(){ int x,*px,*py; x=9; px=&x; py=px; printf("x= %d\n",x); printf("&x= %d\n",&x); printf("px= %d\n",px); printf("*px= %d\n",*px); printf("*px= %d\n",*px); return 0; } 1.4 Expressões com Ponteiros Os ponteiros podem aparecer em expressões, se px aponta para um inteiro x, então *px pode ser 2 utilizado em qualquer lugar que x seria. O operador * tem maior precedência que as operações aritméticas, assim a expressão abaixo pega o conteúdo do endereço que px aponta e soma 1 ao seu conteúdo. y=*px+1; No próximo caso somente o ponteiro será incrementado e o conteúdo da próxima posição da memória será atribuído a y: y=*(px+1); Os incrementos e decrementos dos endereços podem ser realizados com os operadores ++ e --, que possuem procedência sobre o * e operações matemáticas e são avaliados da direita para a esquerda: *px++; /* sob uma posição na memória */ *(px--); /* mesma coisa de *px-- */ No exemplo abaixo os parênteses são necessários, pois sem eles px seria incrementado em vez do conteúdo que é apontado, porque os operadores * e ++ são avaliados da direita para esquerda. (*px)++ /* equivale a x=x+1; ou *px+=1 */ Exemplo: int main() { nt x,*px; x=1; px=&x; printf("x= %d\n",x); printf("px= %u\n",px); printf("*px+1= %d\n",*px+1); printf("px= %u\n",px); printf("*px= %d\n",*px); printf("*px+=1= %d\n",*px+=1); printf("px= %u\n",px); printf("(*px)++= %d\n",(*px)++); printf("px= %u\n",px); printf("*(px++)= %d\n",*(px++)); printf("px= %u\n",px); printf("*px++-= %d\n",*px++); printf("px= %u\n",px); return 0; } 1.5 Ponteiros para ponteiros Um ponteiro para um ponteiro é uma forma de indicação múltipla. Num ponteiro normal, o valor do ponteiro é o valor do endereço da variável que contém o valor desejado. Nesse caso o primeiro ponteiro contém o endereço do segundo, que aponta para a variável que contém o valor desejado. float **balanço; //balanço é um ponteiro para um ponteiro float. Exemplo: 3 int main() { int x,*p,**q; x=10; p=&x; q=&p; printf("%d",**q); return 0; } 1.6 Problemas com ponteiros O erro chamado de ponteiro perdido é um dos mais difíceis de se encontrar, pois a cada vez que a operação com o ponteiro é utilizada, poderá estar sendo lido ou gravado em posições desconhecidas da memória. Isso pode acarretar em sobreposições sobre áreas de dados ou mesmo área do programa na memória. int,*p; x=10; *p=x; Estamos atribuindo o valor 10 a uma localização desconhecida de memória. A consequência desta atribuição é imprevisível. 1.7 Exemplos de Ponteiros Suponha que a, b e c são variáveis inteiras. Eis um jeito bobo de fazer "c=a+b": int int p = q = c = *p; *q; &a; &b; *p + *q; /* p é um ponteiro para um inteiro */ /* o valor de p é o endereço de a */ /* q aponta para b */ Outro exemplo bobo: int int p = r = c = *p; **r; &a; &p; **r + b; /* r é um ponteiro para um ponteiro para um inteiro */ /* p aponta para a */ /* r aponta para p e *r aponta para a */ 1.8 Aplicação Suponha que precisamos de uma função que troque os valores de duas variáveis inteiras, digamos i e j. É claro que a função void troca( int i, int j) /* errado! */ { int temp; temp = i; i = j; j = temp; } não produz o efeito desejado, pois recebe apenas os valores das variáveis e não as variáveis propriamente ditas. A função recebe "cópias" das variáveis e troca os valores dessas cópias, enquanto as variáveis "originais" permanecem inalteradas. Para obter o efeito desejado, é preciso passar à função os endereços das variáveis: 4 void troca( int *p, int *q) { int temp; temp = *p; *p = *q; *q = temp; } Para aplicar a função às variáveis i e j basta dizer troca( &i, &j); ou ainda int *p, *q; p = &i; q = &j; troca( p, q); 2 Vetores e endereços Os elementos de qualquer vetor (=array) têm endereços consecutivos na memória do computador. [Na verdade, os endereços não são consecutivos, pois cada elemento do vetor pode ocupar vários bytes. Mas o compilador C acerta os detalhes internos de modo a criar a ilusão de que a diferença entre os endereços de elementos consecutivos vale 1.] Por exemplo, depois da declaração int *v; v = malloc( 100 * sizeof (int)); o ponteiro v aponta o primeiro elemento de um vetor de 100 elementos. O endereço do segundo elemento do vetor é v+1 e o endereço do terceiro elemento é v+2. Se i é uma variável do tipo int então v + i é o endereço do(i+1)-ésimo elemento do vetor. A propósito, as expressões v+i e &v[i] têm exatamente o mesmo valor e portanto as atribuições *(v+i) = 87; v[i] = 87; têm o mesmo efeito. Analogamente, qualquer dos fragmentos de código abaixo pode ser usado para "carregar" o vetor v: for (i = 0; i < 100; ++i) for (i = 0; i < 100; ++i) scanf( "%d", &v[i]); scanf( "%d", v + i); Todas essas considerações também valem se o vetor for alocado pela declaração int v[100]; mas nesse caso v é uma espécie de "ponteiro constante", cujo valor não pode ser alterado. 5 3 Alocação dinâmica de memória As declarações abaixo alocam memória para diversas variáveis. A alocação é estática, pois acontece antes que o programa comece a ser executado: char c; int i; int v[10]; Às vezes, a quantidade de memória a alocar só se torna conhecida durante a execução do programa. Para lidar com essa situação é preciso recorrer à alocação dinâmica de memória. A alocação dinâmica é gerenciada pelas funções malloc e free, que estão na biblioteca stdlib. Para usar esta biblioteca, é preciso dizer #include <stdlib.h> no início do programa. Função malloc A função malloc (abreviatura de memory allocation) aloca um bloco de bytes consecutivos na memória do computador e devolve o endereço desse bloco. O número de bytes é especificado no argumento da função. No seguinte fragmento de código, malloc aloca 1 byte: char *ptr; ptr = malloc( 1); scanf( "%c", ptr); O endereço devolvido por malloc é do tipo "genérico" void *. O programador armazena esse endereço num ponteiro de tipo apropriado. No exemplo acima, o endereço é armazenado num ponteiropara-char. Para alocar um tipo-de-dado que ocupa vários bytes, é preciso recorrer ao operador sizeof, que diz quantos bytes o tipo especificado tem: typedef struct { int dia, mes, ano; } data; data *d; d = malloc( sizeof (data)); d->dia = 31; d->mes = 12; d->ano = 2008; [As aparências enganam: sizeof não é uma função.] Overhead. Cada invocação de malloc aloca um bloco de bytes consecutivos maior que o solicitado: os bytes adicionais são usados para guardar informações administrativas sobre o bloco de bytes (essas informações permitem que o bloco seja corretamente desalocado, mais tarde, pela função free). O número de bytes adicionais pode ser grande, mas não depende do número de bytes solicitado no argumento de malloc. Não é recomendável, portanto, invocarmalloc repetidas vezes com argumento muito pequeno. É preferível alocar um grande bloco de bytes e retirar pequenas porções desse bloco na medida do necessário. 4 Exercícios 1. Por que o código abaixo está errado? void troca( int *i, int *j) { int *temp; *temp = *i; *i = *j; *j = *temp; } 2. Um ponteiro pode ser usado para dizer a uma função onde ela deve depositar o resultado de seus cálculos. Escreva uma função hm que converta minutos em horas-e-minutos. A função recebe um inteiro mnts e os endereços de duas variáveis inteiras, digamos h e m, e atribui valores a essas variáveis de modo que mseja menor que 60 e que 60*h+m seja igual a mnts. Escreva também uma função main que use a função hm. 6 3. Escreva uma função mm que receba um vetor inteiro v[0..n-1] e os endereços de duas variáveis inteiras, digamos min e max, e deposite nessas variáveis o valor de um elemento mínimo e o valor de um elemento máximo do vetor. Escreva também uma função main que use a função mm. 4. Suponha que os elementos do vetor v são do tipo int e cada int ocupa 8 bytes no seu computador. Se o endereço de v[0] é 55000, qual o valor da expressão v+3? 5. Suponha que v é um vetor declarado assim: int v[100]; Descreva, em português, a sequência de operações que deve ser executada para calcular o valor da expressão &v[k + 9]; 6. Suponha que v é um vetor. Descreva a diferença conceitual entre as expressões v[3] e v+3. 7. O que há de errado com o seguinte trecho de código? char *a, *b; a = "abacate"; b = "uva"; if (a < b) printf( "%s vem antes de %s no dicionário", a, b); else printf( "%s vem depois de %s no dicionário", a, b); 8. Diga (sem usar o computador) qual o conteúdo do vetor a depois dos seguintes comandos. int a[99]; for (i = 0; i < 99; ++i) a[i] = 98 - i; for (i = 0; i < 99; ++i) a[i] = a[a[i]]; 7 5 Referências Bibliográficas 1. UNICAMP. Introdução a Linguagem C. Disponível em: http://www.fsc.ufsc.br/~canzian/root/tutorialc-unicamp.pdf . Acesso em: 25/03/2013. 2. TRENTIN, Paulo. Curso gratuito de programação em C. Disponível em: http://www.paulotrentin.com.br/programacao/curso-gratuito-programacao-c/ . Acesso em: 10/04/2013. 3. NETO, Samuel Dias. Linguagem C: Intermediário. Disponível em: http://homepages.dcc.ufmg.br/~joaoreis/Site%20de%20tutoriais/c_int/es.htm. Acesso em: 11/02/2013. 4. USP. Projeto de Algoritmos: Endereços e ponteiros. Disponível em: http://www.ime.usp.br/~pf/algoritmos/aulas/pont.html . Acesso em: 15/04/2013. 8