Ponteiros e Tabelas | Resumos LEIC-A
Controlo da Execução
Algoritmos Elementares de Ordenação
Algoritmos Eficientes de Ordenação
Algoritmos Elementares de Ordenação (Continuação)
Ponteiros e Tabelas
Estruturas Auto-Referenciadas e Listas
Tipos Abstratos de Dados
Tabelas de Dispersão (Hash Tables)
Algoritmos de Ordenação (Cheat Sheet)
Estruturas de Dados (Cheat Sheet)
📚 Guias
Ponteiros e Endereços
Passagem de Parâmetros para Funções
Ponteiro Nulo
Ponteiro são Enderenços de Memória
Na memória do computador, cada posição é referenciada por um endereço, atribuído de forma sequencial. Posições adjacentes têm, assim, endereços consecutivos. Um ponteiro corresponde a uma variável que contém um endereço de outra variável.
int *px = &x; /* px inicializado com o endereço de x */
Na declaração de um ponteiro temos de indicar ao compilador para que tipo de variável estamos a endereçar (tal como no resto das variáveis "normais"):
// <tipo> *<nome da variável>;
char *cptr;   /* ponteiro para caracter */
int *iptr;    /* ponteiro para inteiro */
double *dptr; /* ponteiro para double */
/* apenas a é um ponteiro */
/* c e d são ponteiros para floats */
float *c, *d;
O endereço de uma variável é obtido através do operador &. Podemos, aqui, olhar para o porquê de usarmos o operador & em scanf's: o scanf guarda o valor lido do stdin no endereço da variável indicada.
int a = 43; /* um inteiro inicializado a 43 */
int *iptr = &a;
/* declaro um ponteiro para inteiro e esse ponteiro passa
a guardar o endereço de a */
O operador * permite aceder ao conteúdo de uma posição de memória endereçada pelo ponteiro (i.e., o conteúdo para onde um ponteiro "aponta"). O valor guardado num determinado endereço é dado pelo operador *.
int *iptr;  /* ponteiro para inteiro */
iptr = &a; /* iptr passa a guardar o endereço de a */
b = *iptr; /* b passa a guardar */
/* o valor apontado por iptr (43) */
int y, x = 1;
px = &x; /* px guarda o endereço de x */
y = *px; /* y toma o valor gurdado no endereco de memoria px */
*px = 0; /* alteramos o valor de x para 0 */
/* output: 0 1 */
O próprio valor de retorno de uma função pode ser um ponteiro - int* xpto() é uma função possível, onde retornamos um ponteiro para inteiro. Argumentos de funções também podem, claro, ser ponteiros (como em int abcd(char *a, int *b)).
Por definição, a passagem de variáveis como argumento de funções em C é feita por valor, não referência: se queremos que as alterações realizadas a uma variável dentro de uma função sejam visíveis fora da mesma, temos de a passar como ponteiro.
void swap(int a, int b) { /* não existe troca fora da função */
void swap(int *a, int *b) { /* existe troca dentro e fora da função */
aux = *a;
*b = aux;
NULL é um ponteiro especial, utilizado para representar o endereço 0 (int *ptr = NULL). Está definido na standard library de C, stdlib, sendo preciso incluí-la no nosso código para utilizar esta ponteiro. Utilizamo-lo para indicar situações especiais: considerando uma árvore binária, por exemplo, NULL pode ser utilizado como que significando "este pai não tem este filho", por exemplo.
Ponteiros têm uma aritmética própria - é possível realizar somas e subtrações, respetivamente + e -, com ponteiros.
int a[6] = {1, 2, 7, 0, 11, 6};
int *pa = a;
printf("%d %d %d\n", a[2], *(a + 2), *(pa + 2));
/* O output vai ser 7 7 7 */
/* Logo as expressões acima são equivalentes */
A declaração int *p1; declara o mesmo que int p2[] - um vetor de inteiros. p1, contudo, poderá ser alterado, enquanto que p2 não pode. Aqui, a noção de alteração pode não ser clara: podemos alterar elementos de p2 (por exemplo,p2[2] = 3; é possível), mas não podemos dizer "ok, p2 = <vector diferente>;. Esta noção tornará-se-á porventura mais clara na próxima página, quando falarmos de alocação dinâmica de memória (e das limitações da alocação estática, como a de p2).
Pegando num exemplo prático, e seguindo a lógica das diferenças entre p1 e p2, qual será a diferença entre as duas declarações seguintes?
char t1[] = "ola";
char *t2 = "ola";
Ambas alocam 4 bytes e copiam para essa posição de memória a sequência de caracteres 'o','l','a','\0'. Contudo, em t1 é possível modificar o conteúdo da memória alocada - fazer t1[0] = 'c', por exemplo - mas não é possível alterar o valor de t1 (não é possível pôr t1 a endereçar outra posição de memória). Podemos, claro, alterar o valor de t2.