Pular para conteúdo

Semana 05 — Modularização de código

Nesta semana

Unidade 2 · Seg 07/09 · Qua 09/09 · Sex 11/09 · ⚠️ feriado: 07/09 (Independência)

Na Semana 04 você aprendeu a quebrar um programa em funções. Agora damos o passo seguinte e maior: quebrar o programa em vários arquivos. Um projeto de verdade raramente cabe em um único .c — ele se organiza em módulos, cada um com um conjunto de funções relacionadas, sua interface (o cabeçalho .h) e sua implementação (o .c). Essa é a forma como praticamente todo software profissional em C é escrito.

Aqui você vai entender por que separar código dessa maneira, o que vai em um .h e o que vai em um .c, como o #include e o pré-processador realmente funcionam, o que são as guardas de inclusão, e como compilar e ligar vários arquivos com o gcc. Como sempre: digite, compile e execute cada exemplo. Modularização se aprende montando o projeto com as próprias mãos.

🎯 Objetivos de aprendizagem

Ao final desta semana, você deverá ser capaz de:

  • explicar por que dividir um programa em vários arquivos (tamanho, reuso entre projetos, trabalho em equipe, recompilação parcial);
  • distinguir protótipo (declaração) de definição, e saber o que vai no .h e o que vai no .c;
  • escrever um cabeçalho (.h) com protótipos, #define e struct, protegido por guarda de inclusão;
  • usar corretamente o #include com aspas duplas ("meu.h") e com <>, e explicar que o pré-processador apenas cola texto;
  • compilar separadamente (gcc -c) e ligar (gcc *.o -o prog) um projeto de vários arquivos, tanto no terminal quanto no Code::Blocks (como Project);
  • entender a ideia de um Makefile para automatizar a compilação;
  • organizar um mini-projeto em módulos coesos, seguindo boas práticas.

📖 Segunda — Estudo do conteúdo

1. Por que dividir um programa em vários arquivos?

Até agora, todos os seus programas moraram em um único arquivo .c. Para os exercícios que fizemos, isso é perfeitamente adequado. Mas imagine um programa com milhares de linhas: um sistema de biblioteca, um jogo, um compilador. Manter tudo num arquivo só rapidamente se torna insustentável. A modularização — dividir o programa em vários arquivos, cada um cuidando de uma parte — resolve quatro problemas concretos:

  • Programas grandes ficam navegáveis. Um arquivo de 5.000 linhas é impossível de ler. Dividido em entrada.c, calculo.c, relatorio.c, cada arquivo é pequeno, coeso e fácil de entender. Você sabe onde procurar cada coisa.
  • Reuso entre projetos. Se você escreve um módulo mat.c/mat.h com funções matemáticas úteis, pode copiar esses dois arquivos para outro projeto e usá-los imediatamente, sem recortar-e-colar funções soltas. É assim que nascem as bibliotecas.
  • Trabalho em equipe. Numa equipe, cada pessoa pode trabalhar em um módulo diferente ao mesmo tempo, sem ficar tropeçando no arquivo do colega. As interfaces (os .h) definem o "contrato" entre os módulos.
  • Recompilar só o que mudou. Este é um ganho técnico enorme. Se você alterou apenas calculo.c, o compilador precisa recompilar somente esse arquivo e depois religar o programa — os demais permanecem intactos. Num projeto grande, isso é a diferença entre esperar 1 segundo ou vários minutos a cada alteração.

Curiosidade 💡 — por que .h e .c separados? (um pouco de história)

A separação entre interface (.h) e implementação (.c) nasceu com o próprio C, nos anos 1970, para permitir a compilação separada: cada .c podia ser compilado sozinho, sabendo apenas dos protótipos das funções que estavam em outros arquivos. Naquela época, computadores eram lentos e recompilar tudo levava muito tempo — poder compilar só o arquivo modificado era essencial. Essa ideia de separar "o que um módulo oferece" (interface) de "como ele faz" (implementação) sobreviveu e é hoje um dos pilares da engenharia de software, presente em praticamente todas as linguagens.

2. Recapitulando: protótipo × definição

Antes de espalhar código por vários arquivos, precisamos revisitar um conceito da Semana 04: a diferença entre declarar e definir uma função.

  • Um protótipo (ou declaração) é a assinatura da função — tipo de retorno, nome e tipos dos parâmetros — seguida de ;. Ele promete ao compilador que a função existe em algum lugar, sem dizer como ela funciona.
  • A definição é a implementação de verdade: a assinatura seguida do corpo { ... }.
double media(double a, double b);      // PROTÓTIPO (declaração): termina em ;

double media(double a, double b) {     // DEFINIÇÃO: tem corpo { ... }
    return (a + b) / 2.0;
}

A chave da modularização está aqui: para usar uma função, o compilador só precisa conhecer o seu protótipo. A definição pode estar em outro arquivo, compilado separadamente, e será encontrada na etapa de ligação (a mesma etapa 4 da compilação que vimos na Semana 01). Ou seja: colocamos os protótipos num lugar que todos os arquivos podem "enxergar" (o .h), e as definições ficam guardadas no .c.

3. Cabeçalho (.h) e implementação (.c): o que vai em cada um

A partir de agora, um módulo será, tipicamente, um par de arquivos de mesmo nome:

  • Um cabeçalho nome.h (header) — a interface do módulo: o que ele oferece ao mundo externo.
  • Uma implementação nome.c — o como: o código real das funções.

O que vai em cada um:

Vai no cabeçalho (.h) Vai na implementação (.c)
Protótipos das funções públicas Definições (corpos) das funções
#define (constantes e macros públicas) Variáveis e funções auxiliares (uso interno)
Definições de struct e typedef O #include "nome.h" do próprio módulo
Comentários explicando como usar cada função Detalhes de implementação que ninguém "de fora" precisa ver

A regra mental é simples: o .h diz o QUE o módulo faz; o .c diz COMO ele faz. Quem for usar o seu módulo lê apenas o .h — ele não precisa (nem deve) se preocupar com os detalhes do .c. Isso é a abstração da Semana 04, agora aplicada em escala de arquivos.

Regra de ouro: NÃO defina (implemente) funções no .h

No cabeçalho vão apenas protótipos (declarações que terminam em ;). A implementação (o corpo { ... }) vai no .c. Colocar o corpo de uma função no .h costuma provocar erros de "definição múltipla" (multiple definition) quando mais de um .c inclui esse cabeçalho — pois cada inclusão traz uma cópia da função. No .h, só a promessa; no .c, a entrega.

4. O #include: "aspas" × <colchetes>

Você usa o #include desde a Semana 01 (#include <stdio.h>). Agora vamos entender o que ele realmente faz — e as duas formas de escrevê-lo:

#include <stdio.h>     // biblioteca do SISTEMA  -> colchetes < >
#include "mat.h"       // arquivo do SEU PROJETO -> aspas duplas " "
  • <arquivo.h> (colchetes angulares): usado para os cabeçalhos das bibliotecas do sistema (a biblioteca-padrão do C: stdio.h, stdlib.h, math.h, ...). O pré-processador procura esses arquivos nos diretórios padrão do compilador.
  • "arquivo.h" (aspas duplas): usado para os cabeçalhos do seu próprio projeto. O pré-processador procura primeiro na pasta do arquivo atual e depois nos diretórios padrão.

A regra prática é fácil: é seu, use aspas; é do sistema, use colchetes.

Curiosidade 💡 — o pré-processador só faz 'copiar e colar' texto

Aqui vai algo que surpreende muita gente: o #include não é mágica. Na primeira etapa da compilação (o pré-processamento, Semana 01), o pré-processador simplesmente abre o arquivo indicado e cola o seu conteúdo inteiro no lugar da linha #include, como se você tivesse feito um copiar-colar. Depois disso, o #include desaparece — o compilador nem sabe que ele existiu. O mesmo vale para o #define: ele apenas troca texto por texto antes da compilação de verdade. Entender que o pré-processador é um "editor de texto automático" desmistifica quase todos os erros ligados a #.

5. Guardas de inclusão (#ifndef / #define / #endif)

Como o #include cola o conteúdo do arquivo, surge um problema. Imagine três arquivos:

  • mat.h — define uma struct Ponto.
  • geom.h — usa Ponto, então faz #include "mat.h".
  • main.c — usa os dois, então faz #include "mat.h" e #include "geom.h".

Ao processar main.c, o pré-processador cola mat.h (por causa do primeiro include) e, logo depois, cola geom.h, que por sua vez cola mat.h de novo. Resultado: a struct Ponto aparece duas vezes — e o compilador reclama de redefinição. Isso é a inclusão dupla (ou inclusão múltipla).

A solução é a guarda de inclusão (include guard): um trio de diretivas que faz o conteúdo do cabeçalho ser processado uma única vez, não importa quantas vezes ele seja incluído:

mat.h
#ifndef MAT_H          // "if not defined": se MAT_H ainda NÃO foi definido...
#define MAT_H          // ...define MAT_H (uma marca) e processa o conteúdo abaixo

// ... todo o conteúdo do cabeçalho: protótipos, #define, structs ...

#endif                 // fim do bloco protegido

Como funciona, passo a passo:

  1. Na primeira vez que mat.h é incluído, MAT_H ainda não existe, então o #ifndef é verdadeiro: o #define MAT_H cria a "marca" e todo o conteúdo é processado.
  2. Numa segunda inclusão (na mesma compilação), MAT_H está definido, o #ifndef é falso, e o pré-processador pula tudo até o #endif. O conteúdo não é colado de novo.

Por convenção, o nome da marca é o nome do arquivo em MAIÚSCULAS, com o ponto virando _: mat.hMAT_H; minha_lista.hMINHA_LISTA_H. Todo cabeçalho que você escrever deve ter uma guarda de inclusão — é um hábito automático.

Curiosidade 💡 — #pragma once, o atalho moderno

Muitos compiladores aceitam uma alternativa mais curta à guarda de inclusão: basta escrever, na primeira linha do cabeçalho:

#pragma once
Ele tem o mesmo efeito — o arquivo é incluído uma só vez. É mais curto e não exige inventar um nome de marca. A pegadinha é que o #pragma once não faz parte do padrão oficial do C (embora seja suportado por praticamente todos os compiladores atuais, incluindo o gcc). O trio #ifndef/#define/#endif é 100% portável e por isso ainda é o mais ensinado. Conheça os dois; use o que preferir (ou o que a equipe adotar).

6. Compilação separada com o gcc

Com o código dividido em vários .c, como transformá-los num único executável? Há duas maneiras equivalentes.

Modo direto (tudo de uma vez): passe todos os .c para o gcc. Ele compila cada um e liga tudo num só comando:

gcc -Wall -std=c11 main.c mat.c -o prog
./prog

Modo em duas etapas (compilar, depois ligar): este modo revela o que acontece por baixo e é a base da recompilação parcial. Primeiro, gera-se um arquivo-objeto (.o) para cada .c com a flag -c (que significa "compile, mas não ligue ainda"):

gcc -Wall -std=c11 -c main.c      # gera main.o
gcc -Wall -std=c11 -c mat.c       # gera mat.o

Depois, liga-se todos os .o num executável (etapa de linking):

gcc main.o mat.o -o prog          # liga os objetos e produz o executável
./prog

A vantagem: se depois você mudar apenas mat.c, basta recompilar mat.c (gerando um novo mat.o) e religar — não é preciso recompilar main.c. É exatamente o ganho "recompilar só o que mudou" da seção 1.

flowchart LR
    subgraph fontes["Código-fonte (.c) e cabeçalho (.h)"]
    A["main.c"]
    B["mat.c"]
    H["mat.h"]
    end
    H -.incluido por.-> A
    H -.incluido por.-> B
    A -->|gcc -c| AO["main.o"]
    B -->|gcc -c| BO["mat.o"]
    AO -->|ligacao| E["prog<br/>(executavel)"]
    BO -->|ligacao| E

No Code::Blocks, isso é um 'Project'

No terminal você lista os arquivos à mão; no Code::Blocks, essa organização se chama Project. Em vez de abrir um .c solto, você cria um File → New → Project → Console application e vai adicionando os arquivos a ele (Project → Add files...). Ao apertar F9, o Code::Blocks compila todos os .c do projeto e liga tudo automaticamente — ele monta os comandos gcc por você. Se o seu programa tem mais de um .c, você precisa de um Project; abrir só um dos arquivos e compilar dará o erro de undefined reference que veremos na seção 11.

7. A ideia de um Makefile

Digitar os comandos gcc -c para cada arquivo e depois o comando de ligação, toda vez, é cansativo e sujeito a erro. Em projetos reais usa-se uma ferramenta chamada make, que lê um arquivo de receita chamado Makefile e automatiza a compilação. Você não precisa dominar make nesta disciplina — basta ter a ideia: o Makefile descreve quais arquivos dependem de quais, e o make é esperto o bastante para recompilar apenas o que mudou.

Makefile (apenas para conhecer a ideia)
prog: main.o mat.o
    gcc main.o mat.o -o prog

main.o: main.c mat.h
    gcc -c main.c

mat.o: mat.c mat.h
    gcc -c mat.c

Com esse arquivo na pasta, basta digitar make no terminal e todo o processo acontece. Se você mudar só mat.c, o make recompila apenas mat.o e religa. Por ora, guarde só o conceito: make = automatizar a compilação de projetos com vários arquivos.

8. Um mini-projeto completo, do começo ao fim

Vamos juntar tudo num exemplo completo e correto: um pequeno módulo de matemática (mat.h + mat.c) usado por um programa principal (main.c). Estude os três arquivos como um conjunto.

Primeiro, a interface — só protótipos, uma constante e a guarda de inclusão:

mat.h
#ifndef MAT_H
#define MAT_H

// Constante publica do modulo
#define MAT_PI 3.14159265358979

// Protótipos das funções oferecidas pelo módulo:
int soma(int a, int b);              // devolve a + b
long fatorial(int n);                // devolve n! (n >= 0)
int eh_primo(int n);                 // 1 se n e primo, 0 caso contrario
double area_circulo(double raio);    // area de um circulo de dado raio

#endif

Agora a implementação — os corpos das funções. Note que mat.c inclui o próprio mat.h: assim o compilador confere se as definições batem com os protótipos.

mat.c
#include "mat.h"     // o modulo inclui o seu proprio cabecalho

int soma(int a, int b) {
    return a + b;
}

long fatorial(int n) {
    long resultado = 1;
    for (int i = 2; i <= n; i++) {
        resultado *= i;
    }
    return resultado;       // 0! e 1! resultam em 1 (o laco nem executa)
}

int eh_primo(int n) {
    if (n < 2) {
        return 0;           // 0 e 1 nao sao primos
    }
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return 0;       // achou um divisor -> nao e primo
        }
    }
    return 1;
}

double area_circulo(double raio) {
    return MAT_PI * raio * raio;    // usa a constante do proprio cabecalho
}

Por fim, o programa principal — ele inclui mat.h e usa as funções, sem precisar saber como elas foram implementadas:

main.c
#include <stdio.h>
#include "mat.h"     // aspas: e um cabecalho do nosso projeto

int main(void) {
    int a, b;
    printf("Digite dois inteiros: ");
    scanf("%d %d", &a, &b);
    printf("Soma = %d\n", soma(a, b));

    int n;
    printf("Digite n para o fatorial: ");
    scanf("%d", &n);
    printf("%d! = %ld\n", n, fatorial(n));

    printf("Primos de 2 a 20: ");
    for (int i = 2; i <= 20; i++) {
        if (eh_primo(i)) {
            printf("%d ", i);
        }
    }
    printf("\n");

    double raio;
    printf("Raio do circulo: ");
    scanf("%lf", &raio);            // %lf na LEITURA de double
    printf("Area = %.2f\n", area_circulo(raio));

    return 0;
}

Para compilar e executar este projeto no terminal:

gcc -Wall -std=c11 -c mat.c            # gera mat.o
gcc -Wall -std=c11 -c main.c           # gera main.o
gcc mat.o main.o -o prog               # liga tudo
./prog

Ou, de uma vez só: gcc -Wall -std=c11 main.c mat.c -o prog. No Code::Blocks, crie um Project e adicione main.c e mat.c (o mat.h é incluído automaticamente por estar na mesma pasta). Repare como o main.c ficou limpo: ele conta a "história" do programa e delega os cálculos ao módulo mat.

Exercício-relâmpago mental

Onde está a definição da função fatorial? E o seu protótipo? Se você quisesse mudar o cálculo do fatorial (por exemplo, de laço para recursão — Semana 06), qual arquivo precisaria alterar, e qual arquivo não mudaria em nada? Responder isso é entender a separação interface/implementação.

9. Boas práticas de organização de código

  • Um módulo, um assunto. Agrupe no mesmo par .h/.c funções que tratam do mesmo tema (tudo de matemática em mat, tudo de strings em strutil). Isso é chamado de coesão.
  • .h mínimo e público. No cabeçalho, exponha apenas o que quem usa o módulo precisa ver. Funções auxiliares, usadas só internamente, ficam apenas no .c (sem protótipo no .h).
  • Sempre uma guarda de inclusão em todo .h — sem exceção.
  • O .c inclui o seu próprio .h. Além de organizado, isso deixa o compilador conferir se as implementações batem com os protótipos.
  • Nomes de arquivo iguais aos do módulo. lista.h/lista.c, e não header1.h.
  • Comente a interface, não a implementação óbvia. No .h, um comentário curto ao lado de cada protótipo dizendo o que a função faz e o que ela espera vale ouro.
  • Compile sempre com -Wall. Os avisos do compilador apontam problemas de interface (tipos que não batem, protótipo ausente) antes que virem bugs.

10. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • Esquecer a guarda de inclusão (#ifndef/#define/#endif) no .h → erro de redefinição por inclusão dupla.
  • Definir (implementar) uma função no .h em vez de só declará-la → erro de múltipla definição (multiple definition) na ligação. No .h, só protótipos.
  • Esquecer de compilar/ligar o .c de um módulo → erro undefined reference na etapa de ligação (a etapa 4 da Semana 01). O compilador conhece o protótipo, mas não encontrou a definição porque o .c não entrou na compilação. No Code::Blocks, isso quase sempre significa que você não criou um Project (ou não adicionou o arquivo a ele).
  • Incluir o .c em vez do .h (#include "mat.c") → cola o código-fonte inteiro e provoca múltipla definição ao ligar. Inclua sempre o .h.
  • Nome da marca da guarda repetido em dois cabeçalhos diferentes (copiar-colar sem trocar o nome) → um dos cabeçalhos é silenciosamente ignorado.

👥 Quarta — Encontro

Chegue ao encontro tendo estudado o material e, principalmente, tendo montado e executado o mini-projeto da seção 8 na sua máquina. Modularização só "entra na cabeça" quando você vê os arquivos se ligando de verdade. Roteiro previsto:

  1. Revisão relâmpago: protótipo × definição, o que vai no .h × no .c, e o papel das guardas de inclusão.
  2. Ao vivo: partir de um programa "tudo num .c" e extrair um módulo (.h + .c), compilando em duas etapas para ver os .o surgirem.
  3. Galeria de erros: provocamos de propósito o undefined reference, a múltipla definição (implementando no .h) e a inclusão dupla (removendo a guarda), para você aprender a ler e localizar cada um — e saber em qual etapa (compilação ou ligação) ele acontece.

Problema-guia do encontro — módulo de conversões: vamos criar um módulo conv com funções de conversão de unidades, e um main.c que o usa. Estrutura de partida:

conv.h
#ifndef CONV_H
#define CONV_H

double celsius_para_fahrenheit(double c);   // F = C * 9/5 + 32
double fahrenheit_para_celsius(double f);   // C = (F - 32) * 5/9
double km_para_milhas(double km);           // 1 km = 0.621371 milhas
double milhas_para_km(double mi);

#endif

No encontro, implementaremos conv.c juntos (atenção à divisão real: use 9.0/5.0, não 9/5) e escreveremos um main.c com um pequeno menu que lê a opção do usuário e chama a conversão certa. Perguntas para pensar antes: por que conv.c deve incluir conv.h? O que aconteceria se removêssemos a guarda de inclusão de conv.h e dois arquivos o incluíssem?


✍️ Sexta — Estudo dirigido

Atenção: o juiz Beecrowd usa um único arquivo

A modularização em .h/.c não é testada diretamente no Beecrowd: o juiz aceita a submissão de um único arquivo-fonte. Ou seja, você não sobe projetos multi-arquivo lá. Isso não torna o tema menos importante — é exatamente o oposto: a modularização é uma habilidade de projeto local, que você pratica montando projetos de verdade na sua máquina. Por isso, o foco desta sexta são os Desafios para escrever do zero (projetos multi-arquivo locais). A tabela do Beecrowd abaixo serve para treinar a boa decomposição em funções, que é a base da modularização.

Exercícios guiados (Beecrowd)

A ideia aqui é reforçar a decomposição em funções bem separadas — o passo anterior a espalhá-las por módulos. Refaça exercícios já conhecidos das semanas anteriores, agora organizando o código em funções pequenas, coesas e bem nomeadas (mesmo que, no Beecrowd, tudo fique num só arquivo).

Problema Como organizar em funções
1153 Fatorial Simples isole long fatorial(int n) — a mesma que iria num módulo mat
1165 Números Primos isole int eh_primo(int n) e use-a no laço principal
1157 Divisores int conta_divisores(int n), candidata a um módulo mat
1478 Tabela de Multiplicação void imprime_tabuada(int n) reutilizável
1176 Fibonacci Array long fibonacci(int n) bem isolada

Ao resolver, pergunte-se: "se eu fosse mover esta função para um mat.h/mat.c, qual seria o protótipo?". Se a resposta é imediata, sua função está bem desenhada.

Desafios para escrever do zero (projetos multi-arquivo, na sua máquina)

Estes são o coração da semana. Monte cada um como um projeto de verdade, com cabeçalho, implementação e main separados, e compile em duas etapas (gcc -c + ligação) ou como um Project no Code::Blocks.

  1. Biblioteca de matemática (mat.h + mat.c): parta do mini-projeto da seção 8 e acrescente funções: int mdc(int a, int b), int mmc(int a, int b), long potencia(int base, int expoente) e int eh_perfeito(int n). Escreva um main.c que exercite todas. Todo protótipo novo entra no .h; toda implementação, no .c.
  2. Biblioteca de utilidades de string (strutil.h + strutil.c): crie funções como int tamanho(const char s[]) (conta caracteres até o \0), int conta_vogais(const char s[]) e void para_maiusculas(char s[]). (Strings são o tema da Semana 08 — aqui você pode começar com o básico e voltar para incrementar depois.)
  3. Reorganize um exercício antigo em módulos: pegue um programa maior que você já escreveu (por exemplo, a calculadora com menu da Semana 04) e quebre-o em módulos: um operacoes.h/operacoes.c com as contas e um main.c só com o menu e a E/S. Compare a legibilidade do main.c antes e depois.
  4. Módulo de geometria (geom.h + geom.c): funções de área e perímetro de retângulo, círculo e triângulo, mais uma constante #define GEOM_PI 3.14159. Faça o main.c imprimir um pequeno relatório usando o módulo.

Para investigar (curiosidade prática)

  • Monte o projeto da seção 8 e compile em duas etapas. Depois, altere mat.c, recompile apenas mat.o e religue. Confirme que main.o não precisou ser recompilado.
  • Provoque o erro undefined reference de propósito: compile e ligue esquecendo o mat.o (gcc main.o -o prog). Leia a mensagem do ligador. Em qual etapa o erro ocorre?
  • Remova a guarda de inclusão de um .h que seja incluído por dois arquivos diferentes e observe o erro de redefinição. Depois recoloque a guarda e confirme que some.
  • Crie um .h e inclua-o duas vezes no mesmo .c (#include "mat.h" repetido). Com a guarda, nada de ruim acontece. Entenda por quê.

✅ Checklist de autoavaliação

Marque com sinceridade — se algum item não estiver ✅, volte à seção correspondente.

  • Explico por que dividir um programa em vários arquivos (tamanho, reuso, equipe, recompilar só o que mudou).
  • Sei o que vai no .h (protótipos, #define, struct) e o que vai no .c (implementações).
  • Uso #include com aspas para arquivos do projeto e <> para o sistema, e sei que o pré-processador apenas cola texto.
  • Escrevo um cabeçalho protegido por guarda de inclusão e explico o problema da inclusão dupla (e conheço o #pragma once).
  • Compilo separadamente (gcc -c) e ligo (gcc *.o -o prog) um projeto de vários arquivos; sei que no Code::Blocks isso é um Project.
  • Tenho a ideia do que um Makefile automatiza.
  • Reconheço e sei corrigir os erros de undefined reference, múltipla definição e redefinição por inclusão dupla.

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 15: Modularização de Código.
  • BACKES, André. Linguagem C: completa e descomplicada (organização de programas, cabeçalhos e compilação separada).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language (o clássico "K&R"), cap. 4 (escopo externo, cabeçalhos e o pré-processador — seção 4.11).
  • Referência: cppreference — Preprocessor (detalhes de #include, #define e #pragma once).
  • Prática: Beecrowd (para treinar a decomposição em funções — lembre-se: submissão em arquivo único).

Antes de seguir para a Semana 06

Você agora sabe organizar o código em módulos — o que o QUE fica separado do COMO. Na próxima semana vamos explorar uma ideia poderosa e elegante que costuma "quebrar a cabeça" de quem vê pela primeira vez: a recursão, funções que chamam a si mesmas. Lembra da pilha de chamadas que citamos na Semana 04? Ela é a peça-chave para entender como uma função pode se chamar sem se perder. Traga o fatorial que você escreveu esta semana — na Semana 06 ele vai renascer em versão recursiva.