Structs e ponteiros
Esta aula aborda o uso de ponteiros com structs em C, incluindo o operador de seta (->), alocação dinâmica de structs na heap e passagem de structs para funções por valor e referência. Exemplos práticos ilustram cada conceito, e exercícios consolidam o aprendizado.
Nesta aula, vamos explorar a combinação de structs e ponteiros, um tópico fundamental para manipular dados de forma eficiente em C. Entender como acessar membros de uma struct através de um ponteiro, alocar structs dinamicamente na heap e passar structs para funções por referência permite escrever código mais flexível e com melhor desempenho.
Operador ->
Quando temos um ponteiro para uma struct, o operador de seta (->) é usado para acessar os membros da struct apontada. Sem ele, precisaríamos desreferenciar o ponteiro e depois usar o operador ponto, o que é mais verboso. O operador -> combina desreferência e acesso a membro em um só passo.
Por exemplo, se p é um ponteiro para uma struct do tipo Pessoa, então p->nome é equivalente a (*p).nome. O operador -> é mais legível e é a forma preferida em C.
#include <stdio.h>
#include <string.h>
typedef struct {
char nome[50];
int idade;
} Pessoa;
int main() {
Pessoa p1 = {"Alice", 30};
Pessoa *ptr = &p1;
// Usando operador ->
printf("Nome: %s\n", ptr->nome);
printf("Idade: %d\n", ptr->idade);
// Equivalente com desreferência
printf("Nome: %s\n", (*ptr).nome);
printf("Idade: %d\n", (*ptr).idade);
return 0;
}
Structs na heap
Alocar structs dinamicamente na heap (com malloc, calloc ou realloc) é útil quando o número de elementos não é conhecido em tempo de compilação ou quando queremos que a struct persista além do escopo da função. A alocação na heap retorna um ponteiro para a memória alocada, que deve ser liberada com free quando não for mais necessária.
Para acessar os membros de uma struct alocada na heap, usamos o operador ->. É importante verificar se a alocação foi bem-sucedida (ponteiro não nulo).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char nome[50];
int idade;
} Pessoa;
int main() {
Pessoa *ptr = (Pessoa*) malloc(sizeof(Pessoa));
if (ptr == NULL) {
printf("Erro de alocação\n");
return 1;
}
// Atribuindo valores
strcpy(ptr->nome, "Bob");
ptr->idade = 25;
printf("Nome: %s, Idade: %d\n", ptr->nome, ptr->idade);
free(ptr);
return 0;
}
Passando structs para funções
Structs podem ser passadas para funções por valor (cópia) ou por referência (ponteiro). Passar por valor é simples, mas para structs grandes pode ser ineficiente e a função não pode modificar a struct original. Passar por referência (um ponteiro) é mais eficiente e permite modificar a struct original.
Quando passamos por referência, usamos o operador -> para acessar os membros. A função recebe um ponteiro e pode alterar o conteúdo da struct original.
#include <stdio.h>
#include <string.h>
typedef struct {
char nome[50];
int idade;
} Pessoa;
// Passagem por valor (cópia)
void imprimePessoa(Pessoa p) {
printf("Nome: %s, Idade: %d\n", p.nome, p.idade);
}
// Passagem por referência (ponteiro)
void atualizaIdade(Pessoa *p, int novaIdade) {
p->idade = novaIdade; // Modifica a struct original
}
int main() {
Pessoa p1 = {"Charlie", 40};
imprimePessoa(p1);
atualizaIdade(&p1, 45);
imprimePessoa(p1); // Agora idade é 45
return 0;
}
Exemplos
Vamos ver um exemplo mais completo que combina structs, ponteiros, alocação dinâmica e funções. Criaremos uma função que inicializa uma struct alocada na heap e outra que a libera.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char *nome; // Ponteiro para string alocada dinamicamente
int idade;
} Pessoa;
// Função que cria uma nova Pessoa na heap
Pessoa* criaPessoa(const char *nome, int idade) {
Pessoa *p = (Pessoa*) malloc(sizeof(Pessoa));
if (p == NULL) return NULL;
p->nome = (char*) malloc(strlen(nome) + 1);
if (p->nome == NULL) {
free(p);
return NULL;
}
strcpy(p->nome, nome);
p->idade = idade;
return p;
}
// Função que libera a memória da Pessoa
void liberaPessoa(Pessoa *p) {
if (p != NULL) {
free(p->nome);
free(p);
}
}
int main() {
Pessoa *p = criaPessoa("Diana", 35);
if (p == NULL) {
printf("Erro ao criar pessoa\n");
return 1;
}
printf("Nome: %s, Idade: %d\n", p->nome, p->idade);
liberaPessoa(p);
return 0;
}
Boas práticas e observações finais
Sempre verifique o retorno de malloc e funções de alocação para evitar erros. Libere a memória alocada com free quando não for mais usada. Prefira passar structs por referência quando a função precisar modificar a struct ou quando a struct for grande, para evitar cópias desnecessárias. O operador -> é mais legível e seguro que (*ptr).membro.
Referências
- cppreference: Struct initialization
- cppreference: malloc
- GNU C Library: Pointers
- TutorialsPoint: C Structures
- Wikipedia: C dynamic memory allocation
Exercícios
-
Declare uma struct
Pontocom camposxey(int). Crie uma função que receba um ponteiro para Ponto e imprima as coordenadas no formato "(x, y)".✓ Resposta:#include <stdio.h> typedef struct { int x; int y; } Ponto; void imprimePonto(Ponto *p) { printf("(%d, %d)\n", p->x, p->y); } int main() { Ponto p = {10, 20}; imprimePonto(&p); return 0; } -
Escreva uma função que aloque dinamicamente uma struct
Livro(com campostitulo(string) eano(int)) e a inicialize com valores fornecidos. A função deve retornar um ponteiro para a struct alocada.✓ Resposta:#include <stdio.h> #include <stdlib.h> #include <string.h> typedef struct { char titulo[100]; int ano; } Livro; Livro* criaLivro(const char *titulo, int ano) { Livro *livro = (Livro*) malloc(sizeof(Livro)); if (livro == NULL) return NULL; strcpy(livro->titulo, titulo); livro->ano = ano; return livro; } int main() { Livro *livro = criaLivro("1984", 1949); if (livro != NULL) { printf("%s (%d)\n", livro->titulo, livro->ano); free(livro); } return 0; } -
Crie uma função que receba um ponteiro para uma struct
Pessoa(com nome e idade) e atualize a idade para o dobro da atual. Teste no main.✓ Resposta:#include <stdio.h> typedef struct { char nome[50]; int idade; } Pessoa; void dobraIdade(Pessoa *p) { p->idade *= 2; } int main() { Pessoa p = {"Eva", 20}; dobraIdade(&p); printf("Idade: %d\n", p.idade); // 40 return 0; } -
Explique a diferença entre
p->membroe(*p).membroe demonstre com um exemplo.✓ Resposta:Ambos acessam o membro de uma struct apontada por um ponteiro.
p->membroé açúcar sintático para(*p).membro. O operador->é mais legível e comumente usado.#include <stdio.h> typedef struct { int valor; } Exemplo; int main() { Exemplo e = {42}; Exemplo *ptr = &e; printf("%d\n", ptr->valor); // 42 printf("%d\n", (*ptr).valor); // 42 return 0; } -
Crie uma struct
Alunocom matrícula (int) e nota (float). Escreva uma função que receba um array de ponteiros para Aluno (alocados dinamicamente) e o tamanho, e imprima a média das notas. Não esqueça de liberar a memória.✓ Resposta:#include <stdio.h> #include <stdlib.h> typedef struct { int matricula; float nota; } Aluno; float mediaNotas(Aluno **alunos, int n) { float soma = 0; for (int i = 0; i < n; i++) { soma += alunos[i]->nota; } return soma / n; } int main() { int n = 3; Aluno **alunos = (Aluno**) malloc(n * sizeof(Aluno*)); for (int i = 0; i < n; i++) { alunos[i] = (Aluno*) malloc(sizeof(Aluno)); alunos[i]->matricula = i + 1; alunos[i]->nota = 7.0 + i; } printf("Média: %.2f\n", mediaNotas(alunos, n)); for (int i = 0; i < n; i++) { free(alunos[i]); } free(alunos); return 0; }