Declarações antecipadas, .h como interface e inclusion guards — a base da organização 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.
/* 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);
#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"); }
| 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 |
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; }
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.
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.
.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.
.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 |
.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; }
#include "arquivo.h" vs #include <arquivo.h>"" → o compilador procura primeiro no diretório do projeto, depois nos caminhos do sistema. Use para seus próprios cabeçalhos.<> → o compilador procura apenas nos diretórios padrão do sistema. Use para bibliotecas do sistema (stdio.h, math.h, etc.).
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"
| 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 |
. substituído por _. Exemplos: matematica.h → MATEMATICA_H, lista_alunos.h → LISTA_ALUNOS_H. Isso garante unicidade entre todos os arquivos do projeto.
#pragma onceMuitos 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.
Um módulo bem estruturado com três arquivos: notas.h, notas.c e main.c.
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 */
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];
}
}
}
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;
}
| 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 |
/* matematica.h */ int somar(int a, int b) { // ❌ definição no .h! return a + b; // Se incluído em 2 arquivos .c → "multiple definition" }
/* 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; }
| 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 |
/* 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);
}
/* 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);
}
}