Aula 17 — Protótipos de Funções e Arquivos de Cabeçalho

Declarações antecipadas, .h como interface e inclusion guards — a base da organização em C

1. O Problema: Ordem das Funções Importa em C

Em C, o compilador lê o arquivo de cima para baixo, linha por linha. Se você chamar uma função antes de defini-la, o compilador ainda não sabe que ela existe — e emite um aviso ou erro.

#include <stdio.h>

int main() {
    int r = somar(3, 4);        // ❌ PROBLEMA: somar ainda não foi declarada!
    printf("%d\n", r);
    return 0;
}

int somar(int a, int b) {       // definição vem DEPOIS da chamada
    return a + b;
}

Em versões antigas do C (C89/C90), o compilador assumia que a função retornava int e continuava — o que podia gerar bugs silenciosos. Em C99 e C11, isso gera um erro. A solução correta é o protótipo.

Duas soluções existem: (1) sempre definir as funções antes de usá-las — o que força uma ordem específica e pode ser impraticável em projetos grandes; ou (2) usar protótipos, que informam ao compilador a assinatura da função com antecedência, liberando você para organizar o código como quiser.

2. Protótipos de Função

Um protótipo (ou declaração antecipada) é uma linha que informa ao compilador o nome, o tipo de retorno e os tipos dos parâmetros de uma função, antes de sua definição completa aparecer. Com essa informação, o compilador consegue verificar se as chamadas estão corretas em termos de tipos e quantidade de argumentos.

Sintaxe

/* Protótipo — assinatura completa, terminada com ponto-e-vírgula */
int somar(int a, int b);

/* Os nomes dos parâmetros são opcionais no protótipo: */
int somar(int, int);         // equivalente — apenas os tipos são obrigatórios

/* Outros exemplos: */
float calcularMedia(float, float, float);
void exibirRelatorio(char *titulo, int linhas);
int ehPrimo(int n);

O mesmo programa, corrigido com protótipo

#include <stdio.h>

/* ── Protótipos ─────────────────────────────────── */
int somar(int a, int b);
float calcularMedia(float a, float b);
void classificarAluno(char *nome, float media);

/* ── main ──────────────────────────────────────── */
int main() {
    float n1 = 7.5f, n2 = 8.5f;
    float media = calcularMedia(n1, n2);
    classificarAluno("Ana Souza", media);
    printf("Soma de 3 e 4: %d\n", somar(3, 4));
    return 0;
}

/* ── Definições (podem vir em qualquer ordem) ───── */
int somar(int a, int b) {
    return a + b;
}

float calcularMedia(float a, float b) {
    return (a + b) / 2.0f;
}

void classificarAluno(char *nome, float media) {
    printf("%s: %.1f — %s\n", nome, media,
           media >= 7.0f ? "Aprovado" : "Reprovado");
}
Ana Souza: 8.0 — Aprovado
Soma de 3 e 4: 7

O que o compilador verifica com o protótipo

Verificação Sem protótipo Com protótipo
Número correto de argumentos ❌ Não verifica ✅ Erro se errado
Tipos dos argumentos ❌ Assume sem checar ✅ Converte ou avisa se incompatível
Tipo do valor de retorno ❌ Assume int ✅ Verifica e converte corretamente

3. Caso Especial: Recursão Mútua

Protótipos se tornam indispensáveis quando duas funções se chamam mutuamente — impossível resolver apenas com ordem de definição:

#include <stdio.h>

/* Sem esses protótipos, uma das funções sempre "veria" a outra antes dela */
int ehPar(int n);
int ehImpar(int n);

int ehPar(int n) {
    if (n == 0) return 1;           // 0 é par
    return ehImpar(n - 1);          // chama ehImpar — precisa do protótipo acima
}

int ehImpar(int n) {
    if (n == 0) return 0;           // 0 não é ímpar
    return ehPar(n - 1);            // chama ehPar
}

int main() {
    for (int i = 0; i <= 6; i++)
        printf("%d é %s\n", i, ehPar(i) ? "par" : "ímpar");
    return 0;
}
0 é par
1 é ímpar
2 é par
3 é ímpar
4 é par
5 é ímpar
6 é par
Este exemplo é didático. Na prática, verificar par/ímpar com n % 2 é o caminho correto. Mas a estrutura de recursão mútua aparece em algoritmos reais como parsers de expressões matemáticas e máquinas de estado.

4. Arquivos de Cabeçalho (.h)

Você já usa arquivos .h todo dia: #include <stdio.h> inclui o cabeçalho da biblioteca padrão de entrada/saída. Agora vamos entender o que há dentro deles — e criar os seus próprios.

Um arquivo de cabeçalho (header file, extensão .h) é um arquivo de texto que contém protótipos de funções, definições de constantes (#define), definições de tipos (typedef, struct) e qualquer outra coisa que precisa ser compartilhada entre diferentes arquivos .c. É a interface pública de um módulo.

O que vai no .h e o que vai no .c

Elemento Arquivo .h Arquivo .c
Protótipos de funções públicas ✅ Sim —
Constantes (#define) ✅ Sim —
Definições de tipos (typedef, struct) ✅ Sim —
Corpo/implementação das funções ❌ Não ✅ Sim
Funções auxiliares internas (static) ❌ Não ✅ Sim
Variáveis locais ❌ Não ✅ Sim

Exemplo de par .h / .c bem estruturado

/* ─── matematica.h ─── interface pública do módulo ─── */

#ifndef MATEMATICA_H        // inclusion guard (explicado na próxima seção)
#define MATEMATICA_H

#define PI 3.14159265f      // constante compartilhada

/* Protótipos das funções disponíveis ao usuário do módulo */
float calcularMedia(float a, float b);
float areaCirculo(float raio);
int   ehPrimo(int n);
int   mdc(int a, int b);

#endif  /* MATEMATICA_H */
/* ─── matematica.c ─── implementação do módulo ──────── */

#include "matematica.h"     // aspas: arquivo local do projeto
#include <math.h>

float calcularMedia(float a, float b) {
    return (a + b) / 2.0f;
}

float areaCirculo(float raio) {
    return PI * raio * raio;
}

int ehPrimo(int n) {
    if (n < 2) return 0;
    for (int i = 2; i * i <= n; i++)
        if (n % i == 0) return 0;
    return 1;
}

int mdc(int a, int b) {
    if (b == 0) return a;
    return mdc(b, a % b);
}
/* ─── main.c ─── usa o módulo matematica ────────────── */

#include <stdio.h>
#include "matematica.h"     // inclui os protótipos e constantes

int main() {
    printf("Média de 6 e 9:    %.1f\n", calcularMedia(6.0f, 9.0f));
    printf("Área do círculo r=5: %.2f\n", areaCirculo(5.0f));
    printf("7 é primo? %s\n",   ehPrimo(7)  ? "Sim" : "Não");
    printf("MDC(48, 18) = %d\n", mdc(48, 18));
    return 0;
}
Média de 6 e 9: 7.5
Área do círculo r=5: 78.54
7 é primo? Sim
MDC(48, 18) = 6
#include "arquivo.h" vs #include <arquivo.h>

Aspas "" → o compilador procura primeiro no diretório do projeto, depois nos caminhos do sistema. Use para seus próprios cabeçalhos.

Ângulos <> → o compilador procura apenas nos diretórios padrão do sistema. Use para bibliotecas do sistema (stdio.h, math.h, etc.).

5. Inclusion Guard — Evitando Inclusão Dupla

Imagine que main.c inclui matematica.h e também inclui geometria.h, que por sua vez também inclui matematica.h. O preprocessador inseriria o conteúdo de matematica.h duas vezes no mesmo arquivo de compilação — causando erros de "redeclaração".

O inclusion guard resolve isso com três diretivas do preprocessador:

#ifndef MATEMATICA_H     // "se MATEMATICA_H ainda NÃO foi definido..."
#define MATEMATICA_H     // "...define-o agora (marca que este arquivo já foi lido)"

/* todo o conteúdo do .h fica aqui */
float calcularMedia(float a, float b);
float areaCirculo(float raio);

#endif                   // "fim do bloco condicional"

Como o inclusion guard funciona na prática

Situação O que acontece
1ª vez que o arquivo é incluído MATEMATICA_H não existe → o bloco é processado → MATEMATICA_H é definido
2ª vez que o arquivo é incluído MATEMATICA_H já existe → #ifndef é falso → todo o conteúdo é pulado
Convenção de nomenclatura: O símbolo do inclusion guard é o nome do arquivo em maiúsculas com . substituído por _. Exemplos: matematica.h → MATEMATICA_H, lista_alunos.h → LISTA_ALUNOS_H. Isso garante unicidade entre todos os arquivos do projeto.

Alternativa moderna: #pragma once

Muitos compiladores modernos (GCC, Clang, MSVC) suportam uma diretiva mais simples:

#pragma once             // garante inclusão única — mais simples que o inclusion guard

float calcularMedia(float a, float b);
float areaCirculo(float raio);
⚠️ #pragma once não é padrão ISO C — é uma extensão de compilador. Funciona em praticamente todos os compiladores modernos, mas para máxima portabilidade (ex: sistemas embarcados ou compiladores antigos), prefira o inclusion guard tradicional com #ifndef / #define / #endif.

6. Exemplo Completo — Módulo de Notas Escolares

Um módulo bem estruturado com três arquivos: notas.h, notas.c e main.c.

Arquivo notas.h

/* notas.h — interface do módulo de notas escolares */

#ifndef NOTAS_H
#define NOTAS_H

/* Constantes */
#define MAX_ALUNOS  30
#define NUM_NOTAS    4
#define MEDIA_APROV  7.0f

/* Tipo público */
typedef struct {
    char  nome[50];
    float notas[NUM_NOTAS];
} Aluno;

/* Protótipos públicos */
float calcularMedia(const Aluno *a);
void  exibirAluno(const Aluno *a);
void  exibirTurma(const Aluno turma[], int total);
int   contarAprovados(const Aluno turma[], int total);
void  encontrarMelhor(const Aluno turma[], int total, Aluno *melhor);

#endif /* NOTAS_H */

Arquivo notas.c

/* notas.c — implementação do módulo */

#include "notas.h"
#include <stdio.h>
#include <string.h>

/* ── Função auxiliar PRIVADA (static — invisível fora deste arquivo) ── */
static float somarNotas(const float notas[], int n) {
    float soma = 0.0f;
    for (int i = 0; i < n; i++)
        soma += notas[i];
    return soma;
}

/* ── Funções públicas ──────────────────────────────────────────────── */
float calcularMedia(const Aluno *a) {
    return somarNotas(a->notas, NUM_NOTAS) / NUM_NOTAS;
}

void exibirAluno(const Aluno *a) {
    float media = calcularMedia(a);
    printf("%-20s | Notas: %.1f %.1f %.1f %.1f | Media: %.1f | %s\n",
           a->nome,
           a->notas[0], a->notas[1], a->notas[2], a->notas[3],
           media,
           media >= MEDIA_APROV ? "Aprovado" : "Reprovado");
}

void exibirTurma(const Aluno turma[], int total) {
    printf("\n%-20s | %-28s | %-5s | %s\n",
           "Nome", "Notas", "Media", "Situação");
    printf("--------------------------------------------------------------------\n");
    for (int i = 0; i < total; i++)
        exibirAluno(&turma[i]);
}

int contarAprovados(const Aluno turma[], int total) {
    int aprovados = 0;
    for (int i = 0; i < total; i++)
        if (calcularMedia(&turma[i]) >= MEDIA_APROV)
            aprovados++;
    return aprovados;
}

void encontrarMelhor(const Aluno turma[], int total, Aluno *melhor) {
    float melhorMedia = calcularMedia(&turma[0]);
    *melhor = turma[0];
    for (int i = 1; i < total; i++) {
        float m = calcularMedia(&turma[i]);
        if (m > melhorMedia) {
            melhorMedia = m;
            *melhor = turma[i];
        }
    }
}

Arquivo main.c

/* main.c — programa principal: usa o módulo notas */

#include <stdio.h>
#include "notas.h"   /* inclui apenas o .h — nunca o .c! */

int main() {
    Aluno turma[] = {
        {"Ana Oliveira",  {8.0f, 9.0f, 7.5f, 8.5f}},
        {"Bruno Santos",  {5.0f, 6.0f, 4.5f, 5.5f}},
        {"Carla Mendes",  {10.f, 9.5f, 9.0f, 9.5f}},
        {"Diego Nunes",   {6.5f, 7.0f, 6.0f, 7.5f}},
        {"Elena Torres",  {4.0f, 3.5f, 5.0f, 4.5f}}
    };
    int total = 5;

    exibirTurma(turma, total);

    int aprov = contarAprovados(turma, total);
    printf("\nAprovados: %d de %d (%.0f%%)\n",
           aprov, total, (float)aprov / total * 100);

    Aluno melhor;
    encontrarMelhor(turma, total, &melhor);
    printf("Melhor aluno: %s (%.1f)\n",
           melhor.nome, calcularMedia(&melhor));

    return 0;
}
Nome | Notas | Media | Situação
--------------------------------------------------------------------
Ana Oliveira | Notas: 8.0 9.0 7.5 8.5 | Media: 8.2 | Aprovado
Bruno Santos | Notas: 5.0 6.0 4.5 5.5 | Media: 5.2 | Reprovado
Carla Mendes | Notas: 10.0 9.5 9.0 9.5 | Media: 9.5 | Aprovado
Diego Nunes | Notas: 6.5 7.0 6.0 7.5 | Media: 6.8 | Reprovado
Elena Torres | Notas: 4.0 3.5 5.0 4.5 | Media: 4.2 | Reprovado

Aprovados: 2 de 5 (40%)
Melhor aluno: Carla Mendes (9.5)

7. Erros Comuns com Protótipos e Headers

Erro Causa Solução
implicit declaration of function 'f' Função chamada sem protótipo visível Adicionar protótipo antes da chamada ou incluir o .h correto
conflicting types for 'f' O protótipo não bate com a definição (tipos diferentes) Sincronizar o protótipo no .h com a definição no .c
redefinition of struct / typedef Arquivo .h incluído duas vezes sem inclusion guard Adicionar #ifndef / #define / #endif no .h
multiple definition of 'funcao' Corpo da função definido no .h (incluído em vários .c) Mover a definição para o .c; deixar apenas o protótipo no .h
Protótipo e definição com nomes de parâmetros diferentes C permite, mas é confuso e propenso a erro Usar os mesmos nomes no protótipo e na definição — clareza

O erro mais clássico: definir função no .h

❌ Errado — corpo da função no .h
/* matematica.h */
int somar(int a, int b) {    // ❌ definição no .h!
    return a + b;            // Se incluído em 2 arquivos .c → "multiple definition"
}
✅ Correto — apenas protótipo no .h
/* matematica.h */
int somar(int a, int b);     // ✅ apenas protótipo — ponto-e-vírgula no final

/* matematica.c */
#include "matematica.h"
int somar(int a, int b) {    // ✅ definição no .c
    return a + b;
}

8. C vs. Java — Protótipos e Módulos

Aspecto C Java
Ordem de declaração importa? ✅ Sim — sem protótipo, a ordem importa ❌ Não — o compilador Java resolve na segunda passagem
Interface pública do módulo Arquivo .h com protótipos Membros public da classe
Inclusão de módulo #include "modulo.h" import pacote.Classe;
Risco de inclusão dupla ⚠️ Existe — resolvido com inclusion guard ❌ Não existe — Java gerencia imports automaticamente
Separação interface/implementação Manual — par .h / .c Automática — pares interface/classe ou classe pública/privada
Recursão mútua sem protótipo ❌ Impossível — protótipos obrigatórios ✅ Funciona — métodos podem se referenciar em qualquer ordem

Side-by-side: interface pública em C vs. Java

C — par .h / .c
/* notas.h */
#ifndef NOTAS_H
#define NOTAS_H

float calcularMedia(float, float);
void  exibirAluno(const char *);

#endif
/* notas.c */
#include "notas.h"

float calcularMedia(float a, float b){
    return (a + b) / 2.0f;
}
void exibirAluno(const char *nome){
    printf("Aluno: %s\n", nome);
}
Java — classe com membros public
/* Notas.java */
public class Notas {

    public static float calcularMedia(
            float a, float b) {
        return (a + b) / 2.0f;
    }

    public static void exibirAluno(
            String nome) {
        System.out.println("Aluno: " + nome);
    }
}

Quiz — Aula 17

1. Qual é o principal objetivo de um protótipo de função em C?

2. Qual das declarações abaixo é um protótipo válido em C?

3. Por que o inclusion guard (#ifndef / #define / #endif) é necessário num arquivo .h?

4. Qual a diferença entre #include "meuarquivo.h" e #include <stdio.h>?

5. O que pertence ao arquivo .h e o que pertence ao arquivo .c?

6. Por que recursão mútua (função A chama B, B chama A) exige protótipos obrigatoriamente em C?

7. O erro multiple definition of 'somar' ao compilar um projeto com vários arquivos indica que:

8. Em Java, os métodos de uma classe podem se referenciar em qualquer ordem sem necessidade de declarações antecipadas. Por que C não funciona assim por padrão?