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);
}
}