Programação de Computadores

Tipos Definidos pelo Programador em C

Professor: Gabriel Soares Baptista

Material Complementar

Baixe os casos de teste da aula (11 - abstract data types test cases.zip).

  • O .zip contém apenas inputs/ e outputs por etapa.
  • A ideia é implementar o código e comparar sua saída com a saída esperada.
  • Cada etapa do estudo dirigido cresce a partir da anterior.

Como usar o material

gcc main.c -o programa
./programa < inputs/case1.in
  • Compile o programa da etapa.
  • Rode com uma das entradas fornecidas.
  • Compare com o arquivo correspondente em outputs/.
  • Só depois avance para a próxima etapa.

Objetivos

  • Entender por que variáveis soltas deixam de funcionar bem em programas maiores.
  • Usar struct, typedef, enum e union em problemas reais.
  • Integrar tipos definidos pelo programador com vetores, funções e bibliotecas.
  • Compreender passagem por valor, passagem por referência e operador ->.

Ideia da Aula

Nesta aula, nós não vamos estudar cada recurso isoladamente.

Vamos construir um pequeno sistema de boletim.

Cada etapa muda a entrada, aumenta a regra de negócio e obriga o código a ficar mais organizado.

O Projeto Vai Crescer Assim

  1. Um aluno com variáveis soltas.
  2. O mesmo aluno usando struct.
  3. Uma turma com vetor de estruturas.
  4. Situações formais com enum.
  5. Separação em .h e .c.
  6. Alterações por referência com ->.
  7. Estruturas aninhadas.
  8. Um contraste final com union.

Etapa 1

Um aluno com variáveis soltas

Entrada:

nome matricula nota1 nota2 nota3

Exemplo:

Ana 1001 8.0 7.0 9.0

Etapa 1

O que o programa precisa fazer

  • Ler os dados de um aluno.
  • Calcular a média.
  • Classificar a situação final.
  • Imprimir um relatório simples.

Etapa 1

O que já sabemos usar

float calcular_media(float n1, float n2, float n3){
    return (n1 + n2 + n3) / 3.0f;
}

const char *classificar_media(float media){
    if(media >= 7.0f) return "APROVADO";
    if(media >= 5.0f) return "RECUPERACAO";
    return "REPROVADO";
}

Etapa 1

Onde a solução começa a falhar

  • Funciona para um aluno.
  • Mas e se precisarmos de 30 alunos?
  • E se quisermos passar “um aluno” para uma função?
  • E se quisermos guardar tudo como uma única entidade lógica?
Problema real

O cálculo ainda funciona. O que começa a falhar é a organização dos dados.

Etapa 2

O mesmo problema, mas com struct

Agora a entrada continua a mesma.

O que muda é a modelagem do programa.

As informações deixam de ser “peças soltas” e passam a representar explicitamente um aluno.

Antes de usar struct

O que ela resolve?

  • Uma struct permite agrupar dados diferentes sob um mesmo nome.
  • Ela é útil quando várias informações pertencem à mesma entidade lógica.
  • Em vez de pensar em nome, matricula, nota1, nota2 e nota3 separadamente, passamos a pensar em um Aluno.

struct não existe para "enfeitar" o código. Ela existe para fazer o programa representar melhor o problema.

Antes de usar struct

Forma geral

struct NomeDaStruct {
    tipo campo1;
    tipo campo2;
    tipo campo3;
};
  • Cada campo pode ter um tipo diferente.
  • Depois da definição, podemos declarar variáveis desse tipo.
struct NomeDaStruct variavel;

Antes de usar struct

Exemplo mínimo

struct Aluno {
    char nome[50];
    int matricula;
    float nota1;
    float nota2;
    float nota3;
};
struct Aluno a;
  • Aqui, Aluno passa a ser um modelo de dados do programa.

Como usar uma struct

Acessando campos com .

struct Aluno a;

strcpy(a.nome, "Ana");
a.matricula = 1001;
a.nota1 = 8.0f;
a.nota2 = 7.0f;
a.nota3 = 9.0f;
  • O operador . acessa um campo da estrutura.
  • Se o campo for string, continuamos precisando de strcpy().

Como usar uma struct

Inicialização direta

struct Aluno a = {"Ana", 1001, 8.0f, 7.0f, 9.0f};
  • Os valores seguem a ordem dos campos.
  • Se a ordem estiver errada, a leitura do código e a inicialização ficam confusas.

Em struct, a ordem da inicialização importa.

Etapa 2

A estrutura do aluno

typedef struct {
    char nome[50];
    int matricula;
    float notas[3];
} Aluno;
  • nome reaproveita a ideia de strings.
  • notas[3] reaproveita a ideia de vetor.
  • Aluno agora vira um tipo de verdade dentro do programa.

Etapa 2

O que melhora

float aluno_media(const Aluno *a){
    return (a->notas[0] + a->notas[1] + a->notas[2]) / 3.0f;
}
  • A função passa a receber um aluno, não três notas soltas.
  • O código fica mais coerente com o problema modelado.

Etapa 3

Agora não é mais um aluno, é uma turma

Nova entrada:

n
nome matricula nota1 nota2 nota3
nome matricula nota1 nota2 nota3
...

Etapa 3

O que precisamos fazer agora

  • Ler n alunos.
  • Calcular a média de cada um.
  • Contar aprovados, recuperação e reprovados.
  • Identificar a maior média da turma.

Etapa 3

Vetor de estruturas

Aluno turma[100];

for(i = 0; i < n; i++)
    ler_aluno(&turma[i]);
  • Aqui juntamos arrays, laços e struct.
  • O código não precisa ser duplicado para cada aluno.

Etapa 4

A situação do aluno precisa virar um estado formal

Nova entrada:

n
nome matricula nota1 nota2 nota3 faltas
...

Nova regra:

  • média continua importando
  • faltas agora também importam

Etapa 4

enum para organizar estados

typedef enum {
    REPROVADO_NOTA,
    RECUPERACAO,
    APROVADO,
    REPROVADO_FALTA
} SituacaoAluno;
  • Não dependemos mais de números mágicos.
  • Também evitamos strings espalhadas pela lógica.

Etapa 4

Regra de negócio mais forte

SituacaoAluno aluno_situacao(const Aluno *a){
    float media = aluno_media(a);

    if(a->faltas > 18) return REPROVADO_FALTA;
    if(media >= 7.0f) return APROVADO;
    if(media >= 5.0f) return RECUPERACAO;
    return REPROVADO_NOTA;
}

Etapa 5

O código cresceu, então a organização também precisa crescer

Até aqui, tudo ainda estava em um arquivo só.

Agora isso começa a conflitar com a aula de bibliotecas.

Precisamos separar:

  • tipos
  • protótipos
  • implementações
  • programa principal

Etapa 5

Estrutura do projeto

aluno.h
aluno.c
main.c
  • aluno.h: tipos e protótipos
  • aluno.c: implementação das funções
  • main.c: leitura da entrada e relatório

Etapa 5

Compilação com múltiplos arquivos

gcc main.c aluno.c -o programa
  • Mesma lógica da etapa anterior.
  • Organização interna muito melhor.

Etapa 6

Agora não basta consultar, precisamos alterar

Nova entrada:

n
... turma ...
m
matricula bonus
matricula bonus
...

Etapa 6

Nova regra

O bônus será aplicado apenas à menor nota do aluno.

  • precisamos buscar um aluno por matrícula
  • precisamos devolver o endereço desse aluno
  • precisamos modificar a estrutura original dentro do vetor

Etapa 6

Ponteiro para estrutura e operador ->

Aluno *buscar_aluno_por_matricula(Aluno turma[], int n, int matricula);
void aplicar_bonus(Aluno *a, float bonus);
a->notas[indice_menor] += bonus;
  • É aqui que referência deixa de ser teoria e vira ferramenta real.

Etapa 7

O aluno agora carrega outra estrutura dentro dele

Nova entrada:

n
nome matricula nota1 nota2 nota3 faltas cidade estado
...
m
... bonus ...
estado_consulta

Etapa 7

Estruturas aninhadas

typedef struct {
    char cidade[30];
    char estado[3];
} Localidade;

typedef struct {
    char nome[50];
    int matricula;
    float notas[3];
    int faltas;
    Localidade local;
} Aluno;

Etapa 7

O que passa a ser possível

  • Imprimir cidade e estado no relatório.
  • Filtrar alunos por estado.
  • Contar aprovados por estado.
a->local.estado

Etapa 8

union entra como contraste, não como núcleo do sistema

O sistema principal não precisa de union.

Então vamos usá-la em um experimento separado.

Problema:

n
tipo nome valor
tipo nome valor
...

Etapa 8

Quando union faz sentido

typedef union {
    int inteiro;
    float real;
    char texto[40];
} Dado;

Mas sozinha ela não basta.

Precisamos combinar com enum para saber qual campo está ativo.

Estruturas x Uniões

  • Em struct, os campos coexistem.
  • Em union, os campos compartilham memória.
  • struct é a solução natural do projeto principal.
  • union aparece quando só um formato de dado é válido por vez.

Como estudar esta aula

  1. Comece pela etapa 1.
  2. Rode os casos de teste.
  3. Entenda por que a etapa seguinte foi necessária.
  4. Só então avance.
Foco correto

O objetivo não é apenas fazer funcionar. O objetivo é entender por que a solução anterior começou a ficar insuficiente.

Próximos Passos

Agora que os dados já podem ser melhor modelados e organizados, surge uma nova pergunta:

e quando a quantidade de dados não é conhecida em tempo de compilação?

Na próxima aula, isso nos levará naturalmente para alocação dinâmica em C.