Declaração, definição e chamada — o pilar da modularização em C
Até agora escrevemos todo o código dentro de main(). Isso funciona para programas pequenos,
mas rapidamente se torna um problema: o código fica longo, difícil de ler e repleto de trechos repetidos.
Funções são a solução.
| Benefício | O que significa na prática |
|---|---|
| Reutilização | Escreva uma vez, chame em qualquer lugar — sem copiar e colar código |
| Legibilidade | Um nome bem escolhido (calcularMedia) comunica a intenção melhor do que 10 linhas de código |
| Manutenção | Corrigir um bug em um único lugar corrige o comportamento em todo o programa |
| Testabilidade | Funções pequenas e focadas são muito mais fáceis de testar isoladamente |
Em C, trabalhar com funções envolve três conceitos distintos que é importante não confundir:
| Etapa | O que é | Onde fica |
|---|---|---|
| Declaração (protótipo) | Avisa o compilador que a função existe, qual é o seu tipo de retorno e quais parâmetros ela aceita — sem o corpo | Antes do main(), ou em um arquivo .h |
| Definição | O código completo da função: cabeçalho + corpo com as instruções | Antes ou depois do main() (se antes, a declaração pode ser omitida) |
| Chamada | O ponto do programa onde a função é executada, passando os argumentos e (opcionalmente) capturando o retorno | Em qualquer lugar após a declaração ou definição |
tipo_retorno nome_da_funcao(tipo1 param1, tipo2 param2);
O ponto-e-vírgula no final é obrigatório no protótipo. Os nomes dos parâmetros são opcionais — apenas os tipos são exigidos pelo compilador:
float calcularMedia(int[], int); // válido — sem nomes de parâmetros float calcularMedia(int v[], int n); // mais legível — com nomes
tipo_retorno nome_da_funcao(tipo1 param1, tipo2 param2) {
// corpo da função
instrucao1;
instrucao2;
return valor; // obrigatório se tipo_retorno != void
}
// sem capturar o retorno (quando não interessa) nome_da_funcao(arg1, arg2); // capturando o retorno em uma variável tipo_retorno resultado = nome_da_funcao(arg1, arg2); // usando o retorno diretamente numa expressão printf("%.2f", nome_da_funcao(arg1, arg2));
int n). Argumentos são os valores reais passados
na chamada (calcularMedia(notas, 5)). A distinção é técnica mas importante
para entender mensagens de erro do compilador.
Vamos transformar o cálculo de média da Aula 10 numa função reutilizável:
#include <stdio.h> /* ── Declaração (protótipo) ─────────────────────────────── */ float calcularMedia(float v[], int n); /* ── Definição ──────────────────────────────────────────── */ float calcularMedia(float v[], int n) { float soma = 0; for (int i = 0; i < n; i++) soma += v[i]; return soma / n; // retorna o resultado calculado } /* ── main: apenas chama a função ────────────────────────── */ int main() { float turmaA[] = {7, 5, 6, 8, 9}; float turmaB[] = {4, 6, 7, 5}; // Chamada — mesma função, argumentos diferentes printf("Média turma A: %.1f\n", calcularMedia(turmaA, 5)); printf("Média turma B: %.1f\n", calcularMedia(turmaB, 4)); return 0; }
calcularMedia foi chamada duas vezes com vetores e
tamanhos diferentes — sem reescrever nenhuma linha de lógica. Isso é reutilização de código em ação.
Se amanhã precisarmos corrigir a fórmula da média, alteramos em um único lugar.
| Trecho | Significado |
|---|---|
float (antes do nome) | Tipo do valor que a função retorna |
calcularMedia | Nome da função — deve descrever o que ela faz |
float v[] | Parâmetro: vetor de floats (recebe o endereço do primeiro elemento) |
int n | Parâmetro: número de elementos do vetor |
return soma / n; | Encerra a função e devolve o valor calculado ao chamador |
void — Sem Valor de Retornovoid como tipo de retorno. Nesse caso, o return;
é opcional (pode ser omitido ou usado sem valor para sair antecipadamente).
#include <stdio.h> // Função void: executa uma ação, não retorna valor void imprimirLinha(int n, char c) { for (int i = 0; i < n; i++) printf("%c", c); printf("\n"); // return; — opcional em void } void imprimirCaixaTexto(char *titulo) { imprimirLinha(30, '='); // chamada de função dentro de função printf(" %s\n", titulo); imprimirLinha(30, '='); } int main() { imprimirCaixaTexto("Relatório de Notas"); imprimirLinha(10, '-'); return 0; }
imprimirCaixaTexto chama imprimirLinha
internamente. Em C, qualquer função pode chamar qualquer outra — desde que a função chamada tenha sido
declarada ou definida antes. Essa composição de funções é o coração da programação modular.
Esta é uma dúvida frequente. Em C há duas abordagens válidas:
#include <stdio.h>
int dobro(int x) { // definida antes
return x * 2;
}
int main() {
printf("%d", dobro(5)); // pode chamar
return 0;
}
✅ Funciona — o compilador já conhece dobro quando chega ao main.
#include <stdio.h> int dobro(int x); // protótipo — avisa o compilador int main() { printf("%d", dobro(5)); return 0; } int dobro(int x) { // definida depois return x * 2; }
✅ Funciona — o protótipo garante que o compilador saiba da assinatura.
dobro(5) antes de saber que dobro existe.
Em C89/C90 ele assume que a função retorna int (regra implícita — perigosa).
Em C99 e C11 isso é erro de compilação. Sempre prefira declarar os protótipos.
.h) e as definições em arquivos de implementação (.c).
Isso é o que veremos na Aula 17. Por ora, coloque os protótipos no topo do mesmo arquivo .c.
Entender o que o computador faz quando chama uma função ajuda a evitar vários bugs comuns.
main() executa normalmentecalcularMedia(turmaA, 5)calcularMediareturn soma/nmain() retoma de onde parou, com o valor 7.0 disponível
#include <stdio.h>
int soma(int a, int b) {
printf(" [dentro de soma] a=%d, b=%d\n", a, b);
int resultado = a + b;
printf(" [dentro de soma] retornando %d\n", resultado);
return resultado;
}
int main() {
printf("[main] antes da chamada\n");
int s = soma(3, 4);
printf("[main] depois da chamada, s = %d\n", s);
return 0;
}
a e b dentro de soma são
cópias dos valores 3 e 4 — não as variáveis originais do chamador.
Alterar a ou b dentro de soma não afeta nada
em main. Esse comportamento chama-se passagem por valor
e será aprofundado na Aula 14.
Um programa que lê notas de uma turma e usa funções separadas para cada responsabilidade:
#include <stdio.h> #define MAX 50 /* ── Protótipos ─────────────────────────────────────────── */ void lerNotas(float v[], int n); float calcularMedia(float v[], int n); float calcularMaior(float v[], int n); float calcularMenor(float v[], int n); void imprimirRelatorio(float v[], int n); /* ── Definições ─────────────────────────────────────────── */ void lerNotas(float v[], int n) { for (int i = 0; i < n; i++) { printf("Nota %d: ", i + 1); scanf("%f", &v[i]); } } float calcularMedia(float v[], int n) { float soma = 0; for (int i = 0; i < n; i++) soma += v[i]; return soma / n; } float calcularMaior(float v[], int n) { float maior = v[0]; for (int i = 1; i < n; i++) if (v[i] > maior) maior = v[i]; return maior; } float calcularMenor(float v[], int n) { float menor = v[0]; for (int i = 1; i < n; i++) if (v[i] < menor) menor = v[i]; return menor; } void imprimirRelatorio(float v[], int n) { printf("\n===== RELATÓRIO =====\n"); printf("Alunos: %d\n", n); printf("Média: %.2f\n", calcularMedia(v, n)); printf("Maior: %.2f\n", calcularMaior(v, n)); printf("Menor: %.2f\n", calcularMenor(v, n)); printf("=====================\n"); } /* ── main: orquestração ─────────────────────────────────── */ int main() { float notas[MAX]; int n; do { printf("Quantos alunos? (1 a %d): ", MAX); scanf("%d", &n); } while (n < 1 || n > MAX); lerNotas(notas, n); imprimirRelatorio(notas, n); return 0; }
main ficou limpo e legível — ele apenas orquestra:
lê os dados e gera o relatório. Toda a lógica está encapsulada em funções com nomes
autoexplicativos. Se o cliente pedir para adicionar a mediana amanhã, basta escrever
calcularMediana e chamá-la em imprimirRelatorio — sem
tocar no restante do código.
| Aspecto | C | Java |
|---|---|---|
| Nome | Funções — existem independentemente de qualquer classe | Métodos — sempre pertencem a uma classe |
| Onde ficam | No escopo global do arquivo .c |
Dentro de uma classe (class) |
| Protótipo obrigatório | Sim, se a definição vier depois do chamador | Não — o compilador Java resolve a ordem automaticamente |
| Sem retorno | void nomeFuncao(...) |
void nomeMetodo(...) — idêntico |
| Ponto de entrada | int main() — função global |
public static void main(String[] args) — método estático |
| Sobrecarga (overload) | ❌ Não existe em C — cada função precisa de nome único | ✅ Permitida — mesmo nome, parâmetros diferentes |
| Recursão | Suportada (Aula 16) | Suportada — mesmo comportamento |
| Passagem de arrays | Por ponteiro — o tamanho deve ser passado separado | Por referência — .length disponível na função |
float media(float v[], int n) {
float s = 0;
for (int i = 0; i < n; i++) s += v[i];
return s / n;
}
int main() {
float notas[] = {7, 8, 9};
printf("%.1f", media(notas, 3));
}
public class Notas {
static float media(float[] v) {
float s = 0;
for (float x : v) s += x;
return s / v.length; // .length disponível!
}
public static void main(String[] args) {
float[] notas = {7, 8, 9};
System.out.printf("%.1f", media(notas));
}
}
Responda às questões abaixo e clique em Verificar Respostas.