Até agora, você guardou números, caracteres e sequências deles. Mas o mundo real não vem em pedaços soltos: um aluno tem nome, idade e nota, tudo junto; um ponto no plano tem coordenada x e y, inseparáveis. Manter esses dados em variáveis avulsas é frágil e desorganizado. Hoje você aprende a criar seus próprios tipos, agrupando informações relacionadas num único bloco coeso — as structs. É o primeiro passo para modelar problemas de verdade em C, e a fundação sobre a qual construiremos, mais adiante, as estruturas de dados.
O problema que as structs resolvem
Imagine representar uma data. Sem structs, você espalha três variáveis:
int dia = 15;
int mes = 6;
int ano = 2026;
Funciona para uma data. Mas e para cem datas? E para passar uma data a uma função — três argumentos toda vez? E se você acidentalmente misturar o mes de uma data com o ano de outra? Os dados que pertencem juntos deveriam viver juntos. É isso que a struct faz: ela cola campos relacionados num único tipo.
Declarando uma struct
Uma struct é definida com a palavra-chave struct, um nome, e os campos entre chaves:
#include <stdio.h>
struct Data {
int dia;
int mes;
int ano;
};
int main(void) {
struct Data hoje; // declara uma variável do tipo struct Data
hoje.dia = 15;
hoje.mes = 6;
hoje.ano = 2026;
printf("%02d/%02d/%d\n", hoje.dia, hoje.mes, hoje.ano);
return 0;
}
Repare em dois pontos. O tipo criado se chama struct Data — as duas palavras juntas (voltaremos a isso com typedef, mais adiante). E o operador ponto (.) acessa cada campo: hoje.dia, hoje.mes. Cada variável struct Data tem seu próprio conjunto de campos, independente das outras. O %02d no printf, a propósito, formata o número com dois dígitos, preenchendo com zero à esquerda — daí sair 15/06/2026 em vez de 15/6/2026.
Inicializando de uma vez
Como nos vetores, você pode preencher os campos na declaração:
#include <stdio.h>
struct Ponto {
double x;
double y;
};
int main(void) {
struct Ponto origem = {0.0, 0.0}; // por ordem dos campos
struct Ponto p = {.x = 3.0, .y = 4.0}; // por nome (mais claro)
printf("origem: (%.1f, %.1f)\n", origem.x, origem.y);
printf("p: (%.1f, %.1f)\n", p.x, p.y);
return 0;
}
A segunda forma, {.x = 3.0, .y = 4.0}, é a inicialização designada (do C99). Ela nomeia cada campo explicitamente, o que torna o código mais legível e imune a erros de ordem — se um dia você reorganizar os campos da struct, a inicialização por nome continua correta. Prefira-a quando a clareza importar.
Structs contêm qualquer tipo — inclusive outras structs
Um campo pode ser de qualquer tipo: inteiro, double, vetor, ou até outra struct. Isso permite compor modelos ricos:
#include <stdio.h>
#include <string.h>
struct Data {
int dia, mes, ano; // vários campos do mesmo tipo, na mesma linha
};
struct Pessoa {
char nome[50]; // um vetor de char como campo
int idade;
struct Data nascimento; // uma struct dentro de outra
};
int main(void) {
struct Pessoa ana;
strcpy(ana.nome, "Ana Silva"); // copiamos a string (não se atribui com =)
ana.idade = 30;
ana.nascimento.dia = 12; // acesso encadeado com pontos
ana.nascimento.mes = 3;
ana.nascimento.ano = 1996;
printf("%s, %d anos, nascida em %02d/%02d/%d\n",
ana.nome, ana.idade,
ana.nascimento.dia, ana.nascimento.mes, ana.nascimento.ano);
return 0;
}
Note ana.nascimento.dia: para chegar a um campo de uma struct aninhada, você encadeia os pontos. E observe que o campo nome é um vetor de char — para preenchê-lo, usamos strcpy, não =, pela mesma razão que vimos na aula de strings: não se copia um vetor com atribuição simples.
A diferença fundamental: struct é copiada por valor
Aqui está algo que distingue a struct do vetor, e que você precisa gravar. Ao contrário de um vetor (que decai para ponteiro), uma struct é um valor de verdade: quando você a atribui ou a passa para uma função, ela é copiada por inteiro, campo a campo:
#include <stdio.h>
struct Ponto { int x, y; };
int main(void) {
struct Ponto a = {1, 2};
struct Ponto b = a; // CÓPIA completa: b é independente de a
b.x = 99; // altera só b
printf("a.x = %d\n", a.x); // 1 — a não mudou
printf("b.x = %d\n", b.x); // 99
return 0;
}
b = a copia os dois campos. Depois disso, a e b são objetos separados; mexer em um não afeta o outro. Essa é uma diferença crucial em relação aos vetores. Ela é conveniente (você trabalha com o valor sem medo de efeitos colaterais), mas tem um custo: copiar uma struct grande a cada chamada de função pode ser caro. É por isso que, muitas vezes, passamos structs para funções por ponteiro — exatamente o tema da próxima aula.
Passando structs para funções
Você pode passar uma struct para uma função como qualquer valor. A função recebe uma cópia:
#include <stdio.h>
struct Retangulo {
double largura;
double altura;
};
double area(struct Retangulo r) { // recebe uma CÓPIA de r
return r.largura * r.altura;
}
struct Retangulo escalar(struct Retangulo r, double fator) {
r.largura *= fator; // altera só a cópia local
r.altura *= fator;
return r; // devolve uma nova struct
}
int main(void) {
struct Retangulo caixa = {4.0, 3.0};
printf("Área: %.1f\n", area(caixa)); // 12.0
struct Retangulo dobro = escalar(caixa, 2.0);
printf("Dobro: %.1f x %.1f\n", dobro.largura, dobro.altura); // 8.0 x 6.0
printf("Original intacto: %.1f x %.1f\n", caixa.largura, caixa.altura); // 4.0 x 3.0
return 0;
}
Como escalar recebe uma cópia, ela pode modificá-la à vontade sem tocar no original — e devolve a versão modificada como um novo valor. Esse estilo, que trata structs como valores imutáveis e retorna versões novas, é limpo e seguro. Quando você quiser que a função altere o original, ou quando a struct for grande demais para copiar, aí entra o ponteiro.
Um exemplo que modela um problema
Vamos juntar tudo para representar uma conta bancária simples:
#include <stdio.h>
#include <string.h>
struct Conta {
char titular[50];
int numero;
double saldo;
};
struct Conta depositar(struct Conta c, double valor) {
c.saldo += valor;
return c;
}
void extrato(struct Conta c) {
printf("Conta %d (%s): R$ %.2f\n", c.numero, c.titular, c.saldo);
}
int main(void) {
struct Conta minha = {.numero = 1001, .saldo = 500.0};
strcpy(minha.titular, "Marcelo Fontes");
extrato(minha); // R$ 500.00
minha = depositar(minha, 250.0); // reatribuímos o resultado
extrato(minha); // R$ 750.00
return 0;
}
Repare como a struct organiza o problema: em vez de três variáveis soltas para cada conta, temos um tipo Conta que anda junto. As funções recebem e devolvem Conta inteiras, e o código lê quase como a descrição do domínio.
O que vem a seguir
Hoje você deu um salto de expressividade: deixou de usar apenas os tipos prontos do C e passou a criar os seus, agrupando dados relacionados em structs. Vimos como declará-las, inicializá-las, aninhá-las, e — o ponto central — que structs são copiadas por valor, ao contrário dos vetores. Essa cópia é conveniente, mas às vezes indesejável ou custosa. Na próxima aula, vamos combinar structs com o que aprendemos na Fase 2 e acessá-las por ponteiro — o que traz um novo operador, a seta (->), e abre caminho para as estruturas de dados dinâmicas.
Fontes e leituras recomendadas
- The C Programming Language (K&R), Kernighan & Ritchie — Cap. 6, Structures
- cppreference — Struct declaration — https://en.cppreference.com/w/c/language/struct
- cppreference — Designated initializers (inicialização designada, C99) — https://en.cppreference.com/w/c/language/struct_initialization
- Modern C, Jens Gustedt — capítulo sobre tipos agregados — https://gustedt.gitlabpages.inria.fr/modern-c/
- C Programming: A Modern Approach, K. N. King — Cap. 16, sobre structs, unions e enums
Exercícios
Exercício 1
Defina uma struct Livro com campos titulo (string de até 100 caracteres), ano (int) e preco (double). Crie uma variável, preencha-a e imprima os três campos formatados.
Ver resposta
✓ Resposta:
#include <stdio.h>
#include <string.h>
struct Livro {
char titulo[100];
int ano;
double preco;
};
int main(void) {
struct Livro l;
strcpy(l.titulo, "The C Programming Language");
l.ano = 1988;
l.preco = 89.90;
printf("%s (%d) — R$ %.2f\n", l.titulo, l.ano, l.preco);
return 0;
}
Exercício 2
Defina uma struct Ponto com campos x e y (double). Escreva uma função double distancia(struct Ponto a, struct Ponto b) que calcule a distância euclidiana entre dois pontos (use sqrt de <math.h>; compile com -lm).
Ver resposta
✓ Resposta: A distância euclidiana é a raiz da soma dos quadrados das diferenças:
#include <stdio.h>
#include <math.h>
struct Ponto { double x, y; };
double distancia(struct Ponto a, struct Ponto b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
int main(void) {
struct Ponto p = {0.0, 0.0};
struct Ponto q = {3.0, 4.0};
printf("%.2f\n", distancia(p, q)); // 5.00
return 0;
}
Compile com gcc arquivo.c -o prog -lm — o -lm liga a biblioteca matemática, necessária para sqrt.
Exercício 3
Demonstre, com um pequeno programa, que atribuir uma struct a outra (b = a) cria uma cópia independente: altere um campo de b e mostre que o campo correspondente de a permaneceu inalterado.
Ver resposta
✓ Resposta:
#include <stdio.h>
struct Ponto { int x, y; };
int main(void) {
struct Ponto a = {10, 20};
struct Ponto b = a; // cópia
b.x = 99;
printf("a.x = %d, b.x = %d\n", a.x, b.x); // 10, 99
return 0;
}
O a.x continua 10: b é uma cópia completa e independente de a, então alterar b.x não afeta a.
Exercício 4
Defina uma struct Horario (campos hora, minuto, segundo) e uma struct Evento que contenha um nome (string) e dois campos Horario: inicio e fim. Preencha um evento e imprima seus horários de início e fim.
Ver resposta
✓ Resposta:
#include <stdio.h>
#include <string.h>
struct Horario { int hora, minuto, segundo; };
struct Evento {
char nome[80];
struct Horario inicio;
struct Horario fim;
};
int main(void) {
struct Evento e;
strcpy(e.nome, "Aula de C");
e.inicio = (struct Horario){19, 0, 0};
e.fim = (struct Horario){20, 30, 0};
printf("%s: %02d:%02d — %02d:%02d\n",
e.nome,
e.inicio.hora, e.inicio.minuto,
e.fim.hora, e.fim.minuto);
return 0;
}
O (struct Horario){...} é um literal composto (C99), uma forma prática de criar uma struct temporária para atribuir de uma vez.
Exercício 5
Escreva uma função struct Ponto ponto_medio(struct Ponto a, struct Ponto b) que retorne o ponto médio entre dois pontos (média das coordenadas). Teste com (0,0) e (4,6), esperando (2,3).
Ver resposta
✓ Resposta:
#include <stdio.h>
struct Ponto { double x, y; };
struct Ponto ponto_medio(struct Ponto a, struct Ponto b) {
struct Ponto m;
m.x = (a.x + b.x) / 2.0;
m.y = (a.y + b.y) / 2.0;
return m;
}
int main(void) {
struct Ponto a = {0.0, 0.0};
struct Ponto b = {4.0, 6.0};
struct Ponto m = ponto_medio(a, b);
printf("(%.1f, %.1f)\n", m.x, m.y); // (2.0, 3.0)
return 0;
}
A função monta uma nova struct com as médias das coordenadas e a devolve por valor — o chamador recebe uma cópia do resultado.