Structs: agrupando dados heterogêneos
1. Introdução às structs
Em Linguagem C, arrays permitem armazenar múltiplos valores do mesmo tipo, mas frequentemente precisamos agrupar dados de tipos diferentes que representam uma entidade lógica. Por exemplo, um registro de aluno contém nome (string), idade (inteiro) e notas (floats). As structs (estruturas) são o mecanismo da linguagem para criar tipos compostos que agrupam membros heterogêneos.
A sintaxe básica para declarar uma struct é:
struct Pessoa {
char nome[50];
int idade;
float altura;
};
Após a declaração, podemos criar variáveis desse tipo:
struct Pessoa pessoa1;
struct Pessoa pessoa2, pessoa3;
2. Acesso e manipulação de membros
O operador ponto (.) é usado para acessar campos individuais de uma struct:
#include <stdio.h>
#include <string.h>
struct Aluno {
char nome[50];
int matricula;
float nota;
};
int main() {
struct Aluno aluno1;
strcpy(aluno1.nome, "Maria Silva");
aluno1.matricula = 2024001;
aluno1.nota = 9.5;
printf("Nome: %s\n", aluno1.nome);
printf("Matrícula: %d\n", aluno1.matricula);
printf("Nota: %.2f\n", aluno1.nota);
return 0;
}
Atribuição direta entre variáveis struct realiza uma cópia rasa (shallow copy) de todos os membros:
struct Aluno aluno2 = aluno1; // Copia todos os campos
Inicialização agregada permite definir valores na declaração:
struct Aluno aluno3 = {"João Pedro", 2024002, 8.0};
Com C99, podemos usar designadores para inicializar campos específicos:
struct Aluno aluno4 = {.nome = "Ana Costa", .nota = 7.5, .matricula = 2024003};
3. Structs aninhadas
Structs podem conter outras structs como membros, permitindo criar hierarquias de dados:
struct Endereco {
char rua[100];
int numero;
char cidade[50];
char estado[3];
};
struct Funcionario {
char nome[50];
int id;
struct Endereco endereco;
float salario;
};
int main() {
struct Funcionario func = {"Carlos", 101, {"Av. Brasil", 300, "São Paulo", "SP"}, 5000.0};
printf("Funcionário: %s\n", func.nome);
printf("Cidade: %s\n", func.endereco.cidade); // Acesso aninhado
printf("Estado: %s\n", func.endereco.estado);
return 0;
}
Structs também podem conter arrays como membros, como já vimos com strings. Arrays multidimensionais também são suportados:
struct Turma {
char nome_turma[30];
float notas[5][3]; // 5 alunos, 3 notas cada
};
4. Typedef e structs
O uso de typedef simplifica a declaração de variáveis, eliminando a necessidade da palavra-chave struct:
typedef struct {
char titulo[100];
char autor[50];
int ano;
float preco;
} Livro;
int main() {
Livro livro1; // Não precisa de "struct Livro"
Livro livro2 = {"1984", "George Orwell", 1949, 39.90};
return 0;
}
Structs anônimas com typedef são comuns em código moderno C. A legibilidade melhora significativamente, especialmente em projetos complexos. A desvantagem é que structs anônimas não podem ser referenciadas antes do typedef.
5. Ponteiros para structs
Ponteiros para structs são essenciais para manipulação eficiente e alocação dinâmica:
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int x;
int y;
} Ponto;
int main() {
Ponto p1 = {10, 20};
Ponto *ptr = &p1;
// Operador seta (->) para acesso indireto
printf("x = %d, y = %d\n", ptr->x, ptr->y);
// Equivalente usando operador ponto
printf("x = %d, y = %d\n", (*ptr).x, (*ptr).y);
// Alocação dinâmica
Ponto *p2 = (Ponto*) malloc(sizeof(Ponto));
if (p2 != NULL) {
p2->x = 30;
p2->y = 40;
free(p2);
}
return 0;
}
Passagem para funções pode ser feita por valor ou por referência:
// Por valor (cópia completa)
void imprimirPonto(Ponto p) {
printf("(%d, %d)\n", p.x, p.y);
}
// Por referência (eficiente, permite modificação)
void moverPonto(Ponto *p, int dx, int dy) {
p->x += dx;
p->y += dy;
}
6. Alinhamento e padding em structs
O compilador pode inserir bytes de preenchimento (padding) entre membros para alinhamento de memória:
#include <stdio.h>
#include <stddef.h>
struct Exemplo {
char c; // 1 byte
int i; // 4 bytes (geralmente alinhado a 4)
short s; // 2 bytes
};
int main() {
printf("Tamanho da struct: %zu bytes\n", sizeof(struct Exemplo));
printf("Offset de c: %zu\n", offsetof(struct Exemplo, c));
printf("Offset de i: %zu\n", offsetof(struct Exemplo, i));
printf("Offset de s: %zu\n", offsetof(struct Exemplo, s));
return 0;
}
Em muitos sistemas, essa struct ocupará 12 bytes (1 + 3 padding + 4 + 2 + 2 padding), não 7 como a soma dos membros sugere. O padding garante que cada membro esteja em um endereço múltiplo do seu tamanho.
7. Structs como tipos de retorno e parâmetros
Structs podem ser retornadas por valor de funções:
typedef struct {
float media;
float maior;
float menor;
} Estatisticas;
Estatisticas calcularNotas(float notas[], int tamanho) {
Estatisticas est = {0};
float soma = 0;
est.maior = est.menor = notas[0];
for (int i = 0; i < tamanho; i++) {
soma += notas[i];
if (notas[i] > est.maior) est.maior = notas[i];
if (notas[i] < est.menor) est.menor = notas[i];
}
est.media = soma / tamanho;
return est;
}
Para structs grandes, a passagem por referência é mais eficiente:
void processarFuncionario(const struct Funcionario *f) {
// Acesso somente leitura sem cópia
printf("%s mora em %s\n", f->nome, f->endereco.cidade);
}
8. Exemplos práticos e boas práticas
Exemplo 1: Registro de aluno com nome, idade e notas
#include <stdio.h>
#include <string.h>
#define MAX_ALUNOS 100
#define NUM_NOTAS 3
typedef struct {
char nome[50];
int idade;
float notas[NUM_NOTAS];
float media;
} Aluno;
void calcularMedia(Aluno *a) {
float soma = 0;
for (int i = 0; i < NUM_NOTAS; i++) {
soma += a->notas[i];
}
a->media = soma / NUM_NOTAS;
}
int main() {
Aluno alunos[MAX_ALUNOS];
int num_alunos = 0;
// Cadastro
strcpy(alunos[0].nome, "Lucas");
alunos[0].idade = 20;
alunos[0].notas[0] = 8.5;
alunos[0].notas[1] = 7.0;
alunos[0].notas[2] = 9.2;
calcularMedia(&alunos[0]);
num_alunos++;
printf("Aluno: %s, Média: %.2f\n", alunos[0].nome, alunos[0].media);
return 0;
}
Exemplo 2: Lista encadeada simples com struct auto-referenciada
#include <stdio.h>
#include <stdlib.h>
typedef struct No {
int dado;
struct No *proximo; // Auto-referência
} No;
No* criarNo(int valor) {
No *novo = (No*) malloc(sizeof(No));
if (novo) {
novo->dado = valor;
novo->proximo = NULL;
}
return novo;
}
void inserirInicio(No **cabeca, int valor) {
No *novo = criarNo(valor);
if (novo) {
novo->proximo = *cabeca;
*cabeca = novo;
}
}
void imprimirLista(No *cabeca) {
while (cabeca) {
printf("%d -> ", cabeca->dado);
cabeca = cabeca->proximo;
}
printf("NULL\n");
}
int main() {
No *lista = NULL;
inserirInicio(&lista, 10);
inserirInicio(&lista, 20);
inserirInicio(&lista, 30);
imprimirLista(lista);
return 0;
}
Boas práticas:
- Declare structs em arquivos de cabeçalho (.h) e implemente funções em arquivos (.c)
- Use typedef para simplificar nomes, mas evite esconder ponteiros em typedefs
- Prefira passar structs grandes por ponteiro, especialmente com const quando não houver modificação
- Documente o propósito de cada campo da struct
Referências
- Documentação oficial do GCC: Estruturas em C — Documentação oficial do GCC sobre implementação de estruturas, unions e bit-fields
- C Programming: Structs Tutorial - Programiz — Tutorial completo e didático sobre structs em C, com exemplos práticos
- C Structures (structs) - W3Schools — Guia introdutório com exemplos interativos sobre declaração e uso de structs
- Struct Padding in C - GeeksforGeeks — Explicação detalhada sobre alinhamento e padding em structs com exemplos visuais
- C Programming: Linked List Tutorial - TutorialsPoint — Tutorial sobre listas encadeadas em C usando structs auto-referenciadas