Ponteiros e Tabelas
Ponteiros e Endereços
Ponteiro são Endereç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 x = 10;
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 */
int *a, b;
/* c e d são ponteiros para floats */
float *c, *d;Operador &
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 */Operador *
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 a = 43; /* um inteiro inicializado a 43 */
int *iptr; /* ponteiro para inteiro */
int b;
iptr = &a; /* iptr passa a guardar o endereço de a */
b = *iptr; /* b passa a guardar */
/* o valor apontado por iptr (43) */#include <stdio.h>
int main() {
int y, x = 1;
int *px;
px = &x; /* px guarda o endereço de x */
y = *px; /* y toma o valor guardado no endereço de memoria px */
*px = 0; /* alteramos o valor de x para 0 */
printf("%d %d\n", x, y);
/* output: 0 1 */
return 0;
}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)).
Passagem de Parâmetros para Funções
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 */
int aux;
aux = a;
a = b;
b = aux;
}
// vs.
void swap(int *a, int *b) { /* existe troca dentro e fora da função */
int aux;
aux = *a;
*a = *b;
*b = aux;
}Ponteiro Nulo
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".
Ponteiros e Tabelas
Aritmética
Ponteiros têm uma aritmética própria - é possível realizar somas e subtrações, respetivamente + e -, com ponteiros.
#include <stdio.h>
int main() {
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 */
return 0;
}Declarações
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.
// Alternativamente, poderíamos escrever o argumento como int *vec
void read_vector(int vec[], int size) {
int i;
for (i = 0; i < size; i++) {
scanf('%d', &vec[i]);
// Alternativamente, poderíamos usar
// scanf("%d", vec + i);
}
}