Ponteiros não são tão difíceis — você só aprendeu do jeito errado
Ao primeiro contato, ponteiros acabam assustando muitas pessoas. Na maioria das vezes, isso não acontece porque ponteiros é um tópico difícil, mas porque são mal explicados. Vou tentar te ajudar com isso.
Suponha que você declare um valor no seu código e queira aplicar uma função que altere essa variável. Por exemplo:
struct exemplo {
int x;
};
void set_x(struct exemplo a, int x) {
a.x = x;
}
int main(void) {
struct exemplo b = {0}; // iniciaiza tudo com 0
set_x(b, 1);
printf("b.x = %d\n", b.x);
return 0;
}
À primeira vista, parece que isso deveria funcionar. Mas não funciona.
Em C, os argumentos de função são passados por valor. Isso significa que a função recebe uma cópia do que você passa para ela. No código acima, estamos passando uma cópia da struct, portanto quando alteramos essa cópia nada acontece com o valor original. Quando usamos ponteiros, a função ainda recebe uma cópia, porém estamos passando uma cópia do endereço. Como o endereço aponta para o lugar original, conseguimos alterar o valor real.
É por esse e outros motivos que usamos ponteiros.
Para fazer esse código funcionar, podemos mudar um pouco. Em vez de passar a variável para a função, vamos passar o endereço dela.
Quando escrevemos:
int a = 10;
Estamos criando um espaço na memória que armazena o valor 10, e esse espaço tem um endereço na memória do computador. Por exemplo, 0x00000001.
Quando criamos um ponteiro:
int* ptr = &a;
Estamos criando um espaço na memória que armazena o endereço de outro valor; neste caso, *ptr aponta para o endereço 0x00000001 da variável a. Então, na memória, teremos algo assim.
| Nome | Endereço | Valor | O que significa? |
|---|---|---|---|
| a | 0x00000001 | 10 | Um número inteiro. |
| ptr | 0x00000002 | 0x00000001 | O endereço de memória da variável a. |
Para acessar o valor de a através do ponteiro, precisamos usar o * (operados de desreferência).
ptrsozinho vale0x00000001(endereço dea).*ptr“segue” o endereço e chega no valor dea(10).
Obs: Cuidado! *ptr não é o nome da variável. O nome da variável é apenas ptr. O asterisco no int* serve para dizer que o tipo daquela variável é um ponteiro para um número inteiro. O asterisco antes do nome *ptr é apenas uma instrução para o computador dizendo para acessar o que está naquele endereço.
Agora que entendemos isso, podemos ver que, se mudarmos o valor apontado por ptr (usando *ptr = 0) para 0, o valor de a também será alterado para 0, porque ambos se referem ao mesmo local de memória.
Sabendo disso, conseguimos corrigir o problema anterior:
struct exemplo {
int x;
};
void set_x(struct exemplo* a, int x) {
a->x = x;
}
int main(void) {
struct exemplo b = {0}; // iniciaiza tudo com 0
struct exemplo* ptr = &b;
set_x(ptr, 1);
printf("b.x = %d\n", b.x);
return 0;
}
Agora, repare no que mudamos
- Criamos um ponteiro que armazena o endereço da struct;
- A função
set_xagora recebe um ponteiro para a struct, não uma struct comum; - Dentro da função,
a->x = xmodifica o valor naquele endereço de memória referenciado.
Agora o código funciona perfeitamente.
Obs: note que a->x = x; é apenas syntax sugar para (*a).x = x;.
Claro que ponteiros são usados por vários outros motivos, mas este exemplo já é suficiente para entender a ideia central :).