Ponteiros e indireção múltipla
Aviso: Você precisa saber o básico de C para entender este texto, várias pessoas tem dificuldade em ponteiros e espero que isso seja útil, recomendo os livros C Completo e Total, Curso de programação em C da UFMG, O Fantástico mundo da linguagem C, se quiser se aprofundar, siga com o curso da USP
Um assunto muitas vezes ignorado na graduação ou mesmo em livros que passamos batido, é a indireção múltipla. Um recurso da linguagem C/C++ que permite criarmos até mesmo estruturas de dados complexas que sejam dinâmicas ou códigos mais enxutos e elásticos, onde podemos fazer vários formatos na estrutura de dado, algo bem abstrato.
Um ponteiro é um tipo de variável que não aponta para o valor de uma variável em si, mas sim para um endereço de memória. O mais interessante, é que o ponteiro é uma variável que tem um endereço, então um ponteiro de ponteiro armazena endereços de ponteiros e o limite disso depende do compilador que você está utilizando.
Porém, a ideia central aqui é criar um ponteiro de ponteiro para um ponteiro, ou seja, criar mais níveis de apontamento, contribuindo para que seu código fique ainda mais dinâmico.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdlib.h>
#include <stdio.h>
void main (){
char *ponteiro = NULL;
char **ponteiro_de_ponteiro = NULL;
char ***ponteiro_de_ponteiro_para_ponteiro = NULL;
char letra = 'L';
ponteiro = &letra; //o parâmetro & indica que o que será passado é
//o endereço da variável letra e não seu conteúdo
ponteiro_de_ponteiro = &ponteiro;
ponteiro_de_ponteiro_para_ponteiro = &ponteiro_de_ponteiro;
printf ("A letra é: %c\n", ***ponteiro_de_ponteiro_para_ponteiro);
}
Quando executamos esse código, a saída resultante é “A letra é: L”, porém não utilizamos a referência da variável letra, mas sim o endereço físico onde a variável está armazenada, algo que não é muita novidade, porque se vocẽ está aqui, provavelmente já conhece ponteiros. Este é um exemplo básico de indireção múltipla, onde usamos ponteiros para referenciar outros ponteiros. Mas olhando desta forma, parece ser algo até mesmo trivial, não é mesmo?
Apontando pra ponteiros em massa - matrizes
Começa a ficar interessante, quando começamos a pensar na possibilidade de apontar para outros ponteiros em massa, sendo assim, poderíamos consumir quanta memória desejássemos e ainda por cima, teríamos uma estrutura onde poderíamos organizar nossas informações! Aqui um outro exemplo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdlib.h>
void main (int argc, char *argv[]){
char letra = 'L';
char **ponteiro_de_ponteiro = NULL; //precisamos apenas de um
//ponteiro para ponteiro nesse caso
int tamanho, i, j;
tamanho = atoi(argv[1]);
//Assim podemos armazenar ponteiros de ponteiros
ponteiro_de_ponteiro = (char**)malloc (sizeof(char*)*tamanho);
for (i = 0; i < tamanho; i++){
//assim criamos blocos de caractere dentro dos ponteiros
ponteiro_de_ponteiro[i] = (char*) malloc (sizeof(char)*tamanho);
}
//com o auxílio desse for, conseguimos preencher toda a matriz
for (i = 0; i < tamanho; i++){
for (j = 0; j < tamanho; j++){
ponteiro_de_ponteiro[i][j] = letra;
}
}
//Aqui é um teste de prova de que está preenchido
for (i = 0; i < tamanho; i++){
for (j = 0; j < tamanho; j++){
printf ("%c", ponteiro_de_ponteiro[i][j]);
}
}
}
Ponteiros de ponteiros para ponteiros - N dimensões
Um professor numa aula da graduação, passou o seguinte problema para a sala: A gente deveria criar um programa que armazenasse o o nome do aluno, o número de matrícula, a turma, três provas e o ano da turma, além disso, calcular uma média simples de cada aluno, a média da turma e o comparativo entre os anos da turma, tudo isso, armazenando 5 anos anos de turma com no mínimo 3 turmas para cada ano.
Diante deste problema, comecei a pensar um jeito mais simples de resolvê-lo, não queria fazer uma coisa simples e trabalhosa, queria fazer uma coisa que fosse mais complexa de se pensar, mas ao mesmo tempo mais fácil de implementar. Então, recorri à indireção múltipla! Você pode ver o código completo aqui.
Porém, podemos ver também o seguinte exemplo de forma mais simples:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[]){
char letra = 'L';
char ***ponteiro_de_ponteiro_para_ponteiro = NULL;
int tamanho, i, j, k;
tamanho = atoi(argv[1]);
ponteiro_de_ponteiro_para_ponteiro = \
(char***)malloc (sizeof(char**)*tamanho);
for (i = 0; i < tamanho; i++){
ponteiro_de_ponteiro_para_ponteiro[i] = \
(char**) malloc (sizeof(char*)*tamanho);
for (j = 0; j < tamanho; j++){
ponteiro_de_ponteiro_para_ponteiro[i][j] = \
(char*) malloc (sizeof(char)*tamanho);
}
}
for (i = 0; i < tamanho; i++){
for (j = 0; j < tamanho; j++){
for(k = 0; k < tamanho; k++)
ponteiro_de_ponteiro_para_ponteiro[i][j][k] = letra;
}
}
for (i = 0; i < tamanho; i++){
for (j = 0; j < tamanho; j++){
for (k = 0; k < tamanho; k++)
printf ("%c", ponteiro_de_ponteiro_para_ponteiro[i][j][k]);
}
}
printf ("\n");
return 1;
}
Mas afinal, o que está acontecendo? Na linha 7, quando declaramos um ponteiro de ponteiro para ponteiro, usamos ***, o que significa que é uma informação tridimencional, a quantidade de * significa exatamente isso! Depois, na linha 15 e 18, preenchemos o espaço de ponteiro para ponteiro e ponteiros, observe que o primeiro armazenamento faz uma conversão usando (char***), depois (char**) e por fim (char*), isso mostra qual tipo de informação será armazenada em cada bloco. Então, podemos nos perguntar:
Quantos níveis a informação que quero armazenar tem?
Depois disso, podemos criar blocos de memória com o design que quisermos e o melhor de tudo: eles serão dinâmicos, não precisamos ficar ajustando o código para ler algo com tamanho diferente. Uma das situações que isso pode se colocar de forma muito útil, é o armazenamento em massa de ponteiros para arquivos de um sistema operacional, para estruturas de dados (lista simplesmente encadeada, árvore binária, árvore B ou afins), ou mesmo, pasmem: armazenar o endereço de funções do seu programa em C :)
Conclusão
A utilização de indireção múltipla, muitas vezes tratada como nota de rodapé ou simplesmente ignorada, é uma função da linguagem C/C++ que possibilita organizar e fazer coisas muito belas, como por exemplo organizar dados de forma multidimensional.