Semana 14 — Registros (structs) e enumerações¶
Nesta semana
Unidade 3 · Seg 09/11 · Qua 11/11 · Sex 13/11
Até aqui, todas as coleções de dados que você montou eram homogêneas: um vetor de
int, uma matriz de double, uma string de char. Mas o mundo real raramente cabe
num único tipo. Um aluno tem nome (texto), matrícula (inteiro) e média (real) — três
tipos diferentes que descrevem uma só entidade. Nesta semana aprendemos a
agrupar dados heterogêneos num único "pacote" com nome: a struct (registro).
Vamos ver como definir uma struct, declarar variáveis dela, acessar seus
campos com o operador ponto ., simplificar tudo com typedef, passar structs para
funções (por valor e por ponteiro, com o operador seta ->), montar vetores de
structs e, por fim, dar nome a conjuntos de constantes com o enum. É a semana em
que você começa a modelar problemas de verdade.
🎯 Objetivos de aprendizagem¶
Ao final desta semana, você deverá ser capaz de:
- explicar por que existem tipos heterogêneos e quando usar uma
structem vez de variáveis soltas ou vetores paralelos; - definir uma
struct, declarar variáveis dela e acessar seus campos com o operador ponto (.); - inicializar structs e simplificar a sintaxe com
typedef; - passar structs para funções por valor e por ponteiro, usando o operador seta
(
->) e entendendo o custo da cópia; - construir e percorrer vetores de structs para representar coleções de entidades;
- usar
enumpara nomear conjuntos de constantes inteiras e empregá-las emswitch, ganhando legibilidade.
📖 Segunda — Estudo do conteúdo¶
1. O problema: dados que não cabem num único tipo¶
Imagine que você precisa representar um aluno no seu programa. Um aluno tem:
- um nome — texto, ou seja, um vetor de
char(string, da Semana 08); - uma matrícula — um
int; - uma média — um
double.
Com o que você sabe até aqui, restam duas saídas ruins. A primeira é criar variáveis soltas:
Funciona para um aluno. Mas e para quarenta? Você cairia na segunda saída ruim, os vetores paralelos:
O aluno de índice i estaria "espalhado" por três vetores: nomes[i], matriculas[i] e
medias[i]. Isso é frágil: se você ordenar um vetor e esquecer os outros, os dados se
desencontram — o nome de um aluno passa a apontar para a média de outro. Além disso, é
impossível dizer "passe este aluno para uma função": você teria de passar três coisas.
Os vetores que você conhece são homogêneos — guardam elementos do mesmo tipo. O que
falta é uma forma de agrupar dados de tipos diferentes que, juntos, descrevem uma
entidade. Essa forma é a estrutura heterogênea, ou, em C, a struct.
A ideia central em uma frase
Um vetor agrupa muitos dados do mesmo tipo (homogêneo). Uma struct agrupa
poucos dados de tipos diferentes que formam um todo com significado
(heterogêneo). São ferramentas complementares — e, como veremos, combiná-las (um
vetor de structs) é o que resolve o problema dos quarenta alunos.
2. Definindo uma struct¶
Uma struct ("estrutura", ou registro) é um novo tipo de dado que você cria
agrupando campos (também chamados membros). Definimos o "molde":
struct Aluno {
char nome[50];
int matricula;
double media;
}; // <- repare no ponto e vírgula ao final!
Isso não cria nenhuma variável ainda — cria um tipo novo, chamado struct Aluno, do
mesmo jeito que int ou double são tipos. É um molde, uma planta baixa: descreve
o formato de qualquer "aluno", dizendo que ele tem um nome, uma matricula e uma media.
Não esqueça o ; depois da chave } que fecha a struct
Diferentemente de um bloco de função, a definição de uma struct termina com ponto e
vírgula. Esquecê-lo gera mensagens de erro confusas, muitas vezes apontando para a
linha seguinte (frequentemente a main). É um dos erros mais comuns da semana.
3. Declarando variáveis e acessando campos com o operador ponto (.)¶
Com o molde pronto, criamos variáveis daquele tipo — cada uma é um aluno concreto:
Para acessar (ler ou escrever) um campo de a, usamos o operador ponto (.),
escrevendo variavel.campo:
a.matricula = 20250123; // escreve no campo matricula
a.media = 8.75; // escreve no campo media
printf("%d\n", a.matricula); // lê o campo matricula
O campo nome é uma string (vetor de char), então não se atribui com =; usamos
scanf/fgets ou strcpy (Semana 08):
Um programa completinho:
#include <stdio.h>
#include <string.h>
struct Aluno {
char nome[50];
int matricula;
double media;
};
int main(void) {
struct Aluno a; // declara uma variável do tipo struct Aluno
strcpy(a.nome, "Ana Souza"); // preenche os campos, um a um
a.matricula = 20250123;
a.media = 8.75;
printf("Nome: %s\n", a.nome);
printf("Matricula: %d\n", a.matricula);
printf("Media: %.2f\n", a.media);
return 0;
}
4. Inicializando uma struct¶
Assim como uma variável simples pode ser declarada e inicializada de uma vez
(int x = 5;), uma struct também pode. Basta listar os valores dos campos, na ordem em
que foram definidos, entre chaves:
O C moderno (padrão C99 em diante) também aceita inicializadores designados, em que você nomeia cada campo — mais legível e imune à ordem:
Se você inicializar com {0}, todos os campos ficam zerados (números viram 0, a
string vira vazia) — um jeito seguro de começar "limpo":
Ler campos de uma struct com scanf
Cada campo é lido individualmente, respeitando o especificador do seu tipo. Lembre-se
de que campos numéricos precisam do & e que double lê com %lf:
scanf("%d", &a.matricula); // int -> & e %d
scanf("%lf", &a.media); // double -> & e %lf
scanf("%s", a.nome); // string: SEM &, pois vetor já "é" endereço
%s do scanf lê até o primeiro espaço (para "Ana Souza" ele pegaria só "Ana").
Para nomes com espaços, use fgets(a.nome, 50, stdin);, tomando o cuidado usual com o
\n que ele pode deixar no fim. Sem se alongar: escolha %s para uma palavra e fgets
para uma linha inteira.
5. typedef: dando um apelido ao tipo¶
Escrever struct Aluno toda hora é repetitivo. O typedef cria um apelido (um nome
alternativo) para um tipo. Combinado com a struct, permite declarar variáveis usando só
Aluno, sem a palavra struct:
typedef struct {
char nome[50];
int matricula;
double media;
} Aluno; // 'Aluno' passa a ser o nome do tipo
A partir daí:
As duas formas produzem o mesmo resultado; compare lado a lado:
Sem typedef |
Com typedef |
|---|---|
struct Aluno { ... }; |
typedef struct { ... } Aluno; |
struct Aluno a; |
Aluno a; |
Acesso: a.media |
Acesso: a.media (igual) |
Nesta disciplina, adotaremos preferencialmente o typedef por deixar o código mais
limpo — é como a maioria dos programas C profissionais faz.
Curiosidade 💡 — dois nomes na mesma definição
Você pode nomear a struct e criar o apelido de uma vez:
typedef struct Aluno { ... } Aluno;. Aqui struct Aluno (nome da struct) e Aluno
(apelido do typedef) coexistem. Isso é necessário, por exemplo, quando a struct
precisa se referir a si mesma por ponteiro — o caso das listas encadeadas, que
você verá mais adiante em Estruturas de Dados.
6. Structs em funções: por valor × por ponteiro (o operador ->)¶
Você pode passar uma struct para uma função e retorná-la, exatamente como faz com um int.
Mas há um detalhe crucial, que liga diretamente com a
Semana 04: por padrão, C passa argumentos por valor — ou
seja, a função recebe uma cópia. Com uma struct, isso significa copiar TODOS os
campos.
6.1 Por valor (a cópia)¶
#include <stdio.h>
typedef struct {
char nome[50];
int matricula;
double media;
} Aluno;
// recebe uma CÓPIA do aluno; boa para apenas LER/mostrar
void imprime(Aluno x) {
printf("%s (mat. %d) - media %.2f\n", x.nome, x.matricula, x.media);
}
// tenta alterar... mas mexe apenas na cópia local!
void tenta_aprovar(Aluno x) {
x.media = 10.0; // altera a CÓPIA, não o original
}
int main(void) {
Aluno a = { "Ana", 20250123, 5.0 };
tenta_aprovar(a);
imprime(a); // media continua 5.00 — o original NÃO mudou
return 0;
}
Assim como o tenta_dobrar da Semana 04 não mudava o int original, tenta_aprovar não
muda o aluno original: ela mexeu numa cópia que morreu ao fim da função. A diferença é
que, agora, a cópia pode ser grande (esta struct tem mais de 60 bytes). Copiar tudo isso
a cada chamada custa tempo e memória.
6.2 Por ponteiro (o operador seta ->)¶
Para alterar o original — e, de quebra, evitar a cópia — passamos o endereço da
struct, isto é, um ponteiro para ela (ideia da Semana 11).
Dentro da função, temos um ponteiro; para acessar um campo através de um ponteiro, usamos o
operador seta (->):
#include <stdio.h>
typedef struct {
char nome[50];
int matricula;
double media;
} Aluno;
// recebe o ENDEREÇO do aluno; pode ALTERAR o original e não copia tudo
void aprovar(Aluno *p) {
p->media = 10.0; // p->media é atalho para (*p).media
}
int main(void) {
Aluno a = { "Ana", 20250123, 5.0 };
aprovar(&a); // passa o ENDEREÇO de 'a' (repare no &)
printf("%.2f\n", a.media); // agora imprime 10.00 — o original mudou!
return 0;
}
Repare no padrão: . quando temos a struct diretamente (a.media); -> quando
temos um ponteiro para a struct (p->media). Resumindo:
| Você tem... | Para acessar o campo, use | Exemplo |
|---|---|---|
a struct em si (Aluno a) |
ponto . |
a.media |
um ponteiro para struct (Aluno *p) |
seta -> |
p->media |
Curiosidade 💡 — a seta -> é só um atalho
p->media é exatamente o mesmo que (*p).media: primeiro "desreferenciamos" o
ponteiro com *p (para chegar à struct apontada) e depois pegamos o campo com .. Os
parênteses são obrigatórios porque o . tem precedência maior que o *. Como esse
padrão é pouco legível e usadíssimo, a linguagem criou a seta -> como abreviação. As
duas escritas geram o mesmo código; use ->, é o idiomático.
Quando passar por valor e quando por ponteiro?
- Só vai ler a struct e ela é pequena? Por valor está ótimo (é seguro: a função não estraga o original).
- Precisa alterar o original, ou a struct é grande (evitar o custo da cópia)?
Passe por ponteiro. Se for por ponteiro mas só para ler, use
const Aluno *ppara deixar claro (e o compilador garantir) que a função não altera.
6.3 Retornando uma struct¶
Uma função também pode devolver uma struct inteira com return — útil para "montar" e
entregar uma entidade pronta:
Aluno cria_aluno(const char *nome, int mat, double media) {
Aluno novo;
strcpy(novo.nome, nome);
novo.matricula = mat;
novo.media = media;
return novo; // devolve uma cópia do aluno montado
}
7. Vetor de structs: a solução para "muitas entidades"¶
Agora fechamos o ciclo aberto na seção 1. Para representar uma turma inteira, combinamos as duas ideias: um vetor cujos elementos são structs.
Cada turma[i] é um Aluno completo; para chegar a um campo, encadeamos: primeiro o
índice (turma[i]), depois o ponto e o campo (turma[i].media). Percorremos com um laço,
como qualquer vetor:
#include <stdio.h>
typedef struct {
char nome[50];
int matricula;
double media;
} Aluno;
int main(void) {
int n;
Aluno turma[40];
printf("Quantos alunos? ");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("Aluno %d - nome: ", i + 1);
scanf("%s", turma[i].nome); // string: sem &
printf("Aluno %d - matricula: ", i + 1);
scanf("%d", &turma[i].matricula); // int: com &
printf("Aluno %d - media: ", i + 1);
scanf("%lf", &turma[i].media); // double: com & e %lf
}
// percorre e mostra os aprovados (media >= 7.0)
printf("\nAprovados:\n");
for (int i = 0; i < n; i++) {
if (turma[i].media >= 7.0) {
printf(" %s (%.2f)\n", turma[i].nome, turma[i].media);
}
}
return 0;
}
Compare com os vetores paralelos da seção 1: aqui, os dados de cada aluno andam sempre juntos. Ordenar a turma por média, por exemplo, move o registro inteiro de uma vez — nome, matrícula e média nunca se desencontram.
8. Enumerações (enum): dando nome a constantes¶
Muitas vezes precisamos de um conjunto fixo e nomeado de valores relacionados: os dias
da semana, os naipes de um baralho, os estados de um pedido (pendente, pago, enviado). Você
até poderia usar inteiros "mágicos" — 0 para domingo, 1 para segunda... — mas o código
fica ilegível: o que significa if (dia == 3)?
O enum ("enumeração") resolve isso: cria um tipo cujos valores são nomes para
constantes inteiras.
Por baixo, cada nome é um int: por padrão, DOM vale 0, SEG vale 1, ..., SAB vale
6. Mas no código você escreve o nome, não o número — e isso muda tudo em legibilidade:
enum DiaSemana hoje = QUA;
if (hoje == SAB || hoje == DOM) {
printf("Fim de semana!\n");
} else {
printf("Dia util.\n");
}
O enum combina lindamente com o switch (da Semana 02),
porque os case ficam autoexplicativos:
#include <stdio.h>
typedef enum { DOM, SEG, TER, QUA, QUI, SEX, SAB } DiaSemana;
int main(void) {
DiaSemana hoje = SEX;
switch (hoje) {
case SAB:
case DOM:
printf("Descanso!\n");
break;
case SEX:
printf("Sextou!\n");
break;
default:
printf("Dia de estudo.\n");
break;
}
return 0;
}
Repare que usamos typedef também com o enum, do mesmo jeito que fizemos com a struct —
para escrever só DiaSemana em vez de enum DiaSemana.
Curiosidade 💡 — por baixo, um enum é um int
Um enum é, internamente, apenas um int. Por padrão a contagem começa em 0 e
incrementa de 1 em 1, mas você pode fixar valores à mão:
enum Naipe { PAUS = 1, OUROS, COPAS, ESPADAS }; // 1, 2, 3, 4
enum HTTP { OK = 200, NOT_FOUND = 404 }; // valores "salteados"
OUROS vira 2). Como no fundo é um int, printf("%d", COPAS); imprime 3 —
o enum dá nome; o valor continua sendo um número.
9. Structs aninhadas e o tamanho na memória¶
Um campo de uma struct pode ser, ele próprio, outra struct. Isso é natural: uma data tem dia, mês e ano; um aluno tem uma data de nascimento. Chamamos isso de structs aninhadas:
typedef struct {
int dia, mes, ano;
} Data;
typedef struct {
char nome[50];
int matricula;
double media;
Data nascimento; // um campo que é, ele mesmo, uma struct!
} Aluno;
Para chegar a um campo da struct interna, encadeamos os pontos:
Aluno a;
a.nascimento.dia = 15; // campo 'dia' da 'nascimento' do aluno 'a'
a.nascimento.mes = 3;
a.nascimento.ano = 2005;
Curiosidade 💡 — sizeof de uma struct pode ser MAIOR que a soma dos campos
Você imaginaria que uma struct com um int (4 bytes) e um char (1 byte) ocupasse 5
bytes. Frequentemente ela ocupa 8! Isso se chama alinhamento (padding): por
eficiência de acesso, o processador prefere que certos tipos comecem em endereços
"redondos" (múltiplos do seu tamanho). O compilador então insere bytes de
preenchimento invisíveis entre os campos para respeitar esse alinhamento. Por isso, a
regra é: nunca assuma que sizeof(struct) == soma dos sizeof dos campos. Meça com
sizeof — é justamente o que sugerimos investigar na sexta-feira.
10. Erros comuns desta semana (resumão)¶
Checklist de armadilhas
- Esquecer o
;depois do}que fecha a definição dastruct/enum. - Usar
.onde deveria ser->(com ponteiro para struct) — e vice-versa. - Tentar copiar uma string de um campo com
=(a.nome = "Ana";) em vez destrcpy. - Esperar que uma struct passada por valor seja alterada pela função (é cópia!).
- No
scanf, esquecer o&em campos numéricos (&a.media) ou usá-lo no campo string (o vetora.nomejá é um endereço). - Usar
%f(em vez de%lf) para ler um campodouble. - Confundir a definição da struct (o molde, uma vez) com a declaração de variáveis (quantas quiser).
- Assumir que
sizeof(struct)é a soma dos campos (esquecer o padding).
👥 Quarta — Encontro¶
Chegue ao encontro tendo estudado o material acima e tendo executado os exemplos. O encontro é o momento de consolidar e tirar dúvidas — não de ver o conteúdo pela primeira vez. Roteiro previsto:
- Revisão relâmpago: por que existem tipos heterogêneos;
.×->; por valor × por ponteiro (retomando a passagem por valor da Semana 04). - Construção ao vivo: modelamos uma entidade do zero — decidir os campos, escolher os tipos, escrever as funções que a manipulam — e montamos um vetor de structs.
- Galeria de erros: provocamos de propósito os erros da seção 10 (o
;faltando,.no lugar de->, a struct que "não muda") para você aprender a ler o compilador.
Problema-guia do encontro — cadastro de produtos com relatório de estoque: modelar um
Produto (nome, preço, quantidade), ler n produtos num vetor de structs e emitir um
relatório com o valor total de cada item e do estoque inteiro.
#include <stdio.h>
typedef struct {
char nome[50];
double preco;
int quantidade;
} Produto;
// por ponteiro const: só LÊ, e evita copiar a struct
double valor_do_item(const Produto *p) {
return p->preco * p->quantidade; // acesso via -> (temos um ponteiro)
}
int main(void) {
int n;
Produto estoque[100];
printf("Quantos produtos? ");
scanf("%d", &n);
for (int i = 0; i < n; i++) {
printf("Produto %d - nome: ", i + 1);
scanf("%s", estoque[i].nome);
printf("Produto %d - preco: ", i + 1);
scanf("%lf", &estoque[i].preco);
printf("Produto %d - quantidade: ", i + 1);
scanf("%d", &estoque[i].quantidade);
}
double total_geral = 0.0;
printf("\n--- Relatorio de estoque ---\n");
for (int i = 0; i < n; i++) {
double v = valor_do_item(&estoque[i]); // passa o ENDEREÇO do i-esimo
printf("%-20s x%3d R$ %8.2f\n",
estoque[i].nome, estoque[i].quantidade, v);
total_geral += v;
}
printf("----------------------------\n");
printf("Valor total do estoque: R$ %.2f\n", total_geral);
return 0;
}
Perguntas para pensar antes do encontro: por que passamos &estoque[i] (e não
estoque[i]) para valor_do_item? O que mudaria se a função recebesse Produto p por
valor? Por que const no parâmetro é uma boa ideia aqui?
✍️ Sexta — Estudo dirigido¶
Reserve este tempo para praticar de verdade. O objetivo não é "ver a resposta", e sim modelar, escrever, errar, corrigir e executar.
Sobre o Beecrowd nesta semana
Structs e enums costumam ser pouco cobrados isoladamente no Beecrowd — os problemas do juiz online focam em entrada/saída, contas e laços. Mesmo assim, você pode (e deve) usar structs por conta própria em problemas de cadastro/ordenação para treinar a modelagem, ainda que o enunciado não exija. A tabela abaixo traz problemas em que modelar com struct deixa a solução mais organizada, mesmo sendo opcional.
| Nº | Problema | Como uma struct ajuda |
|---|---|---|
| 1005 / 1006 | Média 1 / Média 2 | agrupar as notas + a média num registro |
| 1015 | Distância entre Dois Pontos | um Ponto {x, y} deixa o código limpo |
| 1042 / 1043 | Ordenar Números / Triângulo | agrupar valores relacionados |
| 1133 | Rest of Numbers | intervalos como registro |
| 1168 | LED | modelar dígitos (opcional) |
O foco da semana, porém, são os desafios do zero abaixo — é neles que a modelagem com struct/enum brilha.
Desafios para escrever do zero¶
- Cadastro de turma (vetor de struct): leia
nalunos (nome, matrícula, média) numAluno turma[]; calcule a média da turma e imprima o melhor aluno (maior média). (Dica: guarde o índice do melhor num inteiro enquanto percorre.) - Ponto e distância: defina
typedef struct { double x, y; } Ponto;e uma funçãodouble distancia(Ponto a, Ponto b)que calcula a distância euclidiana entre dois pontos (usesqrtde<math.h>; compile com-lm). - Datas: defina
Data { int dia, mes, ano; }e escrevaint valida(Data d)(o dia/mês fazem sentido?) eint compara(Data a, Data b)(devolve -1, 0 ou 1 conformeaseja anterior, igual ou posterior ab). - Relatório de estoque: parta do
Produtodo encontro e acrescente: encontrar o produto mais caro e o de maior valor em estoque (preço × quantidade). - Enum em ação: crie
enum Naipe { PAUS, OUROS, COPAS, ESPADAS };(ou umenumde dias) e escreva uma função que, dado um valor do enum, imprime seu nome usandoswitch. - Structs aninhadas: junte tudo — um
Alunocom um campoData nascimento. Leia e imprima um aluno completo, acessandoa.nascimento.ano.
Para investigar (curiosidade prática)¶
- Imprima
sizeofde uma struct (por exemplo,struct { int a; char b; double c; }) e compare com a soma dossizeofde cada campo. Bate? Explique a diferença com a ideia de alinhamento/padding da seção 9. Experimente reordenar os campos e veja se o tamanho muda. - Pegue uma struct
ae um ponteiroAluno *p = &a;. Acesse o mesmo campo das três formas:a.media,(*p).mediaep->media. Confirme que dão o mesmo resultado e explique por que->é o atalho preferido.
✅ Checklist de autoavaliação¶
Marque com sinceridade — se algum item não estiver ✅, volte à seção correspondente.
- Explico por que usar uma
struct(dados heterogêneos de uma entidade) e por que ela é melhor que vetores paralelos. - Defino uma
struct, declaro variáveis e acesso campos com o ponto (.) — sem esquecer o;final da definição. - Sei inicializar structs e simplificar o tipo com
typedef. - Entendo que passar struct por valor copia todos os campos e não altera o original; sei quando passar por ponteiro.
- Uso o operador seta (
->) com ponteiros e sei quep->campoé(*p).campo. - Construo e percorro um vetor de structs (
turma[i].media). - Uso
enumpara nomear constantes e o combino comswitch, ganhando legibilidade. - Sei que
sizeofde uma struct pode ser maior que a soma dos campos (padding).
🔗 Referências e para se aprofundar¶
- Material 2026.1 — Aula 12: Estruturas de Dados Heterogêneas.
- BACKES, André. Linguagem C: completa e descomplicada (registros/estruturas e enumerações).
- KERNIGHAN, B.; RITCHIE, D. The C Programming Language (o clássico "K&R"), cap. 6 (structures).
- Referência: cppreference — struct.
- Prática: Beecrowd.
Antes de seguir para a Semana 15
Você acabou de aprender a modelar uma entidade — a juntar num só "pacote" tudo o que descreve um aluno, um produto, uma data. Guarde esse aluno com carinho: na Semana 15 veremos manipulação de arquivos, e as structs serão a peça central para salvar esses registros em disco e lê-los de volta depois — afinal, de que adianta cadastrar quarenta alunos se tudo se perde quando o programa fecha? Um registro cabe naturalmente numa "linha" de arquivo; é a ponte perfeita entre o que você fez esta semana e o armazenamento permanente dos seus dados.