<aside>
Conhecimentos necessários:
Estruturas de dados homogêneas: array (vetor/matriz)
</aside>
<aside>
Navegação
</aside>
Bom… aprendemos a usar ponteiros, mas para quê eles servem além dos parâmetros das funções? Não existe outro uso para eles?
Vimos que tudo que é criado dentro das funções (qualquer variável) é meio que apagada depois que a função acaba, né?
Bom… e se tiver uma forma de criar variáveis dentro de funções sem que elas sejam apagadas?
A verdade é que isso é possível e é nesses momentos que os ponteiros são nossos melhores amigos!
Imagine reversar um espaço na memória. Só vamos reservar mesmo, mas precisamos saber onde reservamos para depois usá-lo para alguma coisa, então salvamos o endereço desse lugar que alocamos em um ponteiro.
Pode parecer confuso, mas na prática só fazemos isso daqui:
int *ponteiro = malloc(sizeof(int));
Criamos um ponteiro e reservamos um lugar na memória para um int (já, já vemos com calma como isso funciona). Depois disso, guardamos o endereço dessa variável no ponteiro, bem como falamos.
Agora podemos usar isso como uma variável do tipo int!
Se fizemos isso em uma função, tecnicamente criamos uma variável que não se apaga assim que a função termina e podemos usar em qualquer lugar do nosso código.
Mas tome cuidado! Uma variável criada assim (dizemos que ela foi criada dinamicamente), se perdermos o lugar onde ela está (ou seja, perdermos sem querer o seu endereço), não conseguimos fazer mais nada com ela depois, então não podemos, em hipótese alguma, perder esse endereço.
Assim, aprendemos alocação de memória! Agora vamos esclarecer um pouco mais as coisas…
Obs.: Tudo visto aqui está contido na biblioteca <stdlib.h>.
malloc() e free()Essa primeira função usamos para alocar a memória. Ela retorna um ponteiro genérico void* com o endereço onde a memória foi alocada, por isso que salvamos isso em um ponteiro. Como argumento, ela só precisa do tamanho, em bytes, do espaço que precisamos alocar, por isso que usamos o sizeof(), por padrão!
Caso a função não consiga alocar a memória por algum motivo, ela retorna um ponteiro nulo (NULL), então é sempre importante verificarmos se foi alocado corretamente!
Também é possível alocar arrays dinamicamente usando essa função, basta alocarmos mais memória. Por exemplo:
int main(){
int quantidade_id = 12;
int *id = malloc(quantidade_id * sizeof(int));
if(id){ //verifica se foi alocado tudo corretamente
id[0] = 198
id[1] = 200;
id[2] = 202;
}
return 0;
}
Viu? Aí usamos como um array normalmente. Podemos criar funções que retornem os ponteiros desses arrays (já que eles não se apagam assim que a função termina, pois foram criados dinamicamente).
Já o free(), serve para liberar o espaço alocado durante o programa. Para evitar que o programa aloque cada vez mais memória e não libere, é preciso colocar um free(ptr); no final do código para garantir que essa memória seja liberada.
Ao usá-lo, é bom atribuir NULL ao ponteiro para evitar usar ele acidentalmente. Então o exemplo acima escrito do jeito certo, fica assim:
int main(){
int quantidade_id = 12;
int *id = malloc(quantidade_id * sizeof(int));
if(id){ //verifica se foi alocado tudo corretamente
id[0] = 198
id[1] = 200;
id[2] = 202;
}
free(id);
id = NULL;
return 0;
}
calloc()Esse é mais recomendado para arrays dinâmicos, pois ele já os inicializa. Ele funciona da seguinte maneira:
calloc(quantidade_de_elementos, tamanho_de_um_elemento);
O mesmo exemplo acima usando essa função, fica assim:
int main(){
int quantidade_id = 12;
int *id = calloc(quantidade_id, sizeof(int));
if(id){ //verifica se foi alocado tudo corretamente
id[0] = 198
id[1] = 200;
id[2] = 202;
}
free(id);
id = NULL;
return 0;
}
realloc()Serve para adicionar mais memória alocada aos arrays, nos possibilitando aumentar o tamanho deles e preservar o conteúdo original. Sua escrita é:
realloc(ponteiro, novo_tamanho);
Por exemplo:
int main(){
int quantidade_id = 12;
int *id = calloc(quantidade_id, sizeof(int)); //tem o tamanho alocado para colocar até 12 elementos
int nova_quantidade_id = quantidade_id * 2;
id = realloc(id, nova_quantidade_id * sizeof(int)); //tem o tamanho alocado para colocar até 24 elementos agora, mas esses últimos 12 não estão inicializados
free(id);
id = NULL;
return 0;
}
Vamos criar uma matriz 3x3 para vermos como é:
int main(){
int **matriz = malloc(3 * sizeof(int*)); //um ponteiro de ponteiro de int para guardar cada uma das linhas da matriz (ou cada um dos vetores)
if(matriz){ //verifica se os três ponteiros foram alocados
matriz[0] = malloc(3 * sizeof(int)); //3 elementos alocados para a primeira linha
matriz[1] = malloc(3 * sizeof(int)); //3 para a segunda
matriz[2] = malloc(3 * sizeof(int)); //e 3 para a terceira
if (matriz[0] && matriz[1] && matriz[2]) { //verifica se todos correram bem
//... resto do código
free(matriz[0]); //libera a primeira linha
free(matriz[1]); //libera a segunda
free(matriz[2]); //e esse a terceira
matriz[0] = matriz[1] = matriz[2] = NULL; //para evitar uso acidental
}
free(matriz); //esse libera a alocação dos ponteiros para cada um das linhas
matriz = NULL;
}
return 0;
}
Parecido com o que fazíamos com os ponteiros genéricos, podemos escrever o tipo de ponteiro para fazer uma conversão direta. Em C, isso não é obrigatório, mas em C++ é. O exemplo acima com isso fica assim:
int main(){
int **matriz = (int**) malloc(3 * sizeof(int*)); //um ponteiro de ponteiro de int para guardar cada uma das linhas da matriz (ou cada um dos vetores)
if(matriz){ //verifica se os três ponteiros foram alocados
matriz[0] = (int*) malloc(3 * sizeof(int)); //3 elementos alocados para a primeira linha
matriz[1] = (int*) malloc(3 * sizeof(int)); //3 para a segunda
matriz[2] = (int*) malloc(3 * sizeof(int)); //e 3 para a terceira
if (matriz[0] && matriz[1] && matriz[2]) { //verifica se todos correram bem
//... resto do código
free(matriz[0]); //libera a primeira linha
free(matriz[1]); //libera a segunda
free(matriz[2]); //e esse a terceira
matriz[0] = matriz[1] = matriz[2] = NULL; //para evitar uso acidental
}
free(matriz); //esse libera a alocação dos ponteiros para cada um das linhas
matriz = NULL;
}
return 0;
}