Structs e ponteiros
Nesta aula, você aprenderá a combinar structs e ponteiros em C, incluindo o operador seta (->), alocação dinâmica de structs na heap e passagem eficiente de structs para funções. Exemplos práticos demonstram como manipular structs de forma flexível e econômica em memória.
Structs permitem agrupar dados relacionados, e ponteiros oferecem flexibilidade na manipulação de memória. Juntos, eles formam a base para estruturas de dados dinâmicas e passagem eficiente de argumentos. Nesta aula, vamos explorar como usar ponteiros para structs, alocar structs na heap e passá-las para funções, evitando cópias desnecessárias e permitindo modificações in-place.
Dominar esse tópico é essencial para implementar listas encadeadas, árvores e outros tipos de dados dinâmicos em C.
Operador ->
Quando você tem um ponteiro para uma struct, o operador ponto (.) não pode ser usado diretamente. Em vez disso, você precisa primeiro desreferenciar o ponteiro com * e depois usar o ponto, ou usar o operador seta (->), que combina as duas operações. A expressão ptr->membro é equivalente a (*ptr).membro.
Exemplo:
#include <stdio.h>
struct Ponto {
int x;
int y;
};
int main() {
struct Ponto p = {10, 20};
struct Ponto *ptr = &p;
// Usando operador seta
printf("x = %d, y = %d\n", ptr->x, ptr->y);
// Equivalente com desreferência
printf("x = %d, y = %d\n", (*ptr).x, (*ptr).y);
return 0;
}O operador seta é mais legível e amplamente utilizado. Lembre-se: se você tem um ponteiro para struct, use -> para acessar membros; se tem a struct diretamente, use ponto.
Structs na heap
Alocar structs na heap (memória dinâmica) com malloc permite que a struct persista além do escopo da função que a criou, além de possibilitar a criação de estruturas de tamanho variável (como vetores ou listas). Para alocar uma struct na heap, use malloc com o tamanho da struct e depois atribua o ponteiro retornado a um ponteiro do tipo da struct.
Exemplo:
#include <stdio.h>
#include <stdlib.h>
struct Pessoa {
char nome[50];
int idade;
};
int main() {
struct Pessoa *p = (struct Pessoa *) malloc(sizeof(struct Pessoa));
if (p == NULL) {
printf("Erro de alocação\n");
return 1;
}
// Acessando membros via seta
p->idade = 25;
snprintf(p->nome, sizeof(p->nome), "Maria");
printf("%s tem %d anos\n", p->nome, p->idade);
free(p); // Libera a memória alocada
return 0;
}É crucial verificar se malloc retornou NULL (falha de alocação) e sempre liberar a memória com free quando não for mais necessária. Alocar structs na heap é comum ao criar nós de listas encadeadas, árvores, etc.
Passando structs para funções
Passar uma struct por valor para uma função faz uma cópia completa da struct, o que pode ser ineficiente para structs grandes. Além disso, alterações feitas na struct dentro da função não afetam a original. Para evitar isso, passe um ponteiro para a struct. Isso permite modificar a struct original e economiza memória e tempo de cópia.
Exemplo de passagem por valor vs. por ponteiro:
#include <stdio.h>
struct Ponto {
int x;
int y;
};
// Passagem por valor (cópia)
void move_por_valor(struct Ponto p, int dx, int dy) {
p.x += dx;
p.y += dy;
}
// Passagem por ponteiro (referência)
void move_por_ponteiro(struct Ponto *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
int main() {
struct Ponto ponto = {0, 0};
move_por_valor(ponto, 5, 3);
printf("Após valor: (%d, %d)\n", ponto.x, ponto.y); // (0,0) - não alterou
move_por_ponteiro(&ponto, 5, 3);
printf("Após ponteiro: (%d, %d)\n", ponto.x, ponto.y); // (5,3) - alterou
return 0;
}Use const no ponteiro se a função não precisar modificar a struct, para garantir segurança:
void imprime_ponto(const struct Ponto *p) {
printf("(%d, %d)\n", p->x, p->y);
}Exemplos
Vamos consolidar com um exemplo mais completo: uma função que cria e retorna um ponteiro para uma struct alocada na heap, e outra que a libera.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Aluno {
char nome[100];
int matricula;
float nota;
};
struct Aluno* criar_aluno(const char *nome, int matricula, float nota) {
struct Aluno *a = (struct Aluno*) malloc(sizeof(struct Aluno));
if (a != NULL) {
strncpy(a->nome, nome, sizeof(a->nome) - 1);
a->nome[sizeof(a->nome) - 1] = '\0';
a->matricula = matricula;
a->nota = nota;
}
return a;
}
void liberar_aluno(struct Aluno **a) {
if (*a != NULL) {
free(*a);
*a = NULL;
}
}
int main() {
struct Aluno *aluno = criar_aluno("João", 12345, 9.5);
if (aluno != NULL) {
printf("Aluno: %s, Mat: %d, Nota: %.2f\n",
aluno->nome, aluno->matricula, aluno->nota);
liberar_aluno(&aluno);
}
return 0;
}Note que liberar_aluno recebe um ponteiro para ponteiro para poder modificar o ponteiro original para NULL, evitando ponteiros soltos. Esse padrão é comum em bibliotecas que gerenciam memória dinâmica.
Boas práticas
- Sempre verifique o retorno de
malloce trate erros. - Libere memória alocada dinamicamente com
freequando não for mais usada. - Evite vazamentos de memória: para cada
malloc, deve haver umfreecorrespondente. - Use
constem parâmetros de funções que não modificam a struct. - Prefira passar structs grandes por ponteiro para evitar cópias desnecessárias.
Referências
- cppreference: Struct declaration
- cppreference: Member access operators
- cppreference: malloc
- cppreference: free
- GNU C Manual: Pointers to Structures
- cppreference: const qualifier
Exercícios
- Crie uma struct
Retangulocom membroslarguraealtura(float). Escreva uma função que receba um ponteiro para a struct e calcule a área (largura * altura), retornando o valor. Teste no main. - Escreva um programa que aloque dinamicamente um vetor de 3 structs
Ponto(com x e y inteiros) usando malloc. Preencha os valores e imprima todos. Não esqueça de liberar a memória. - Crie uma função que receba um ponteiro para uma struct
Pessoa(com nome[50] e idade) e modifique a idade para 30. Teste com uma struct alocada na stack. - Implemente uma função que receba um ponteiro para uma struct
Produto(com nome[30], preço float e quantidade int) e aplique um desconto de 10% no preço. A função deve modificar a struct original. Teste com alocação dinâmica. - Crie uma struct
Livrocom título (char[100]), autor (char[50]) e ano (int). Escreva uma função que receba um ponteiro para a struct e imprima os dados formatados. No main, aloque um livro na heap, preencha e chame a função. Depois libere.
#include <stdio.h>
struct Retangulo {
float largura;
float altura;
};
float area(const struct Retangulo *r) {
return r->largura * r->altura;
}
int main() {
struct Retangulo ret = {5.0, 3.0};
printf("Área: %.2f\n", area(&ret));
return 0;
}#include <stdio.h>
#include <stdlib.h>
struct Ponto {
int x;
int y;
};
int main() {
struct Ponto *vetor = (struct Ponto*) malloc(3 * sizeof(struct Ponto));
if (vetor == NULL) return 1;
for (int i = 0; i < 3; i++) {
vetor[i].x = i * 10;
vetor[i].y = i * 10 + 5;
}
for (int i = 0; i < 3; i++) {
printf("Ponto %d: (%d, %d)\n", i, vetor[i].x, vetor[i].y);
}
free(vetor);
return 0;
}#include <stdio.h>
struct Pessoa {
char nome[50];
int idade;
};
void definir_idade(struct Pessoa *p, int nova_idade) {
p->idade = nova_idade;
}
int main() {
struct Pessoa p1 = {"Ana", 25};
definir_idade(&p1, 30);
printf("%s tem %d anos\n", p1.nome, p1.idade);
return 0;
}#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Produto {
char nome[30];
float preco;
int quantidade;
};
void aplicar_desconto(struct Produto *p) {
p->preco *= 0.9;
}
int main() {
struct Produto *prod = (struct Produto*) malloc(sizeof(struct Produto));
if (prod == NULL) return 1;
strcpy(prod->nome, "Caneta");
prod->preco = 2.50;
prod->quantidade = 10;
aplicar_desconto(prod);
printf("%s: R$ %.2f (qtd: %d)\n", prod->nome, prod->preco, prod->quantidade);
free(prod);
return 0;
}#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Livro {
char titulo[100];
char autor[50];
int ano;
};
void imprimir_livro(const struct Livro *l) {
printf("\"%s\" por %s (%d)\n", l->titulo, l->autor, l->ano);
}
int main() {
struct Livro *livro = (struct Livro*) malloc(sizeof(struct Livro));
if (livro == NULL) return 1;
strncpy(livro->titulo, "1984", sizeof(livro->titulo)-1);
livro->titulo[sizeof(livro->titulo)-1] = '\0';
strncpy(livro->autor, "George Orwell", sizeof(livro->autor)-1);
livro->autor[sizeof(livro->autor)-1] = '\0';
livro->ano = 1949;
imprimir_livro(livro);
free(livro);
return 0;
}