Aula 13 — Funções

Declaração, definição e chamada — o pilar da modularização em C

1. Por que Usar Funções?

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.

Uma função é um bloco de código com nome próprio que realiza uma tarefa específica. Ela pode receber dados de entrada (parâmetros), executar instruções e devolver um resultado (valor de retorno). Uma vez definida, pode ser chamada quantas vezes for necessário em qualquer ponto do programa.

Os quatro benefícios principais

BenefícioO 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
Analogia: Pense numa receita de bolo. "Fazer a cobertura" é uma etapa que pode ser descrita em detalhes em um bloco separado e chamada toda vez que precisarmos dela — sem reescrever os passos. Funções são exatamente isso para o código.

2. As Três Etapas: Declaração, Definição e Chamada

Em C, trabalhar com funções envolve três conceitos distintos que é importante não confundir:

EtapaO 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
Declaração vs Definição: A declaração é como um contrato — diz "existe uma função com essa assinatura". A definição é o cumprimento do contrato — diz "e é assim que ela funciona". Você pode declarar sem definir (erro de ligação) mas não pode usar sem declarar.

3. Sintaxe Completa

Declaração (protótipo)

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

Definição

tipo_retorno nome_da_funcao(tipo1 param1, tipo2 param2) {
    // corpo da função
    instrucao1;
    instrucao2;
    return valor;   // obrigatório se tipo_retorno != void
}

Chamada

// 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));
Parâmetros vs Argumentos: Parâmetros são as variáveis listadas na definição da função (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.

4. Primeiro Exemplo Completo

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;
}
Média turma A: 7.0
Média turma B: 5.5
Observe que a função 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.

Anatomia linha a linha

TrechoSignificado
float (antes do nome)Tipo do valor que a função retorna
calcularMediaNome da função — deve descrever o que ela faz
float v[]Parâmetro: vetor de floats (recebe o endereço do primeiro elemento)
int nParâmetro: número de elementos do vetor
return soma / n;Encerra a função e devolve o valor calculado ao chamador

5. Funções void — Sem Valor de Retorno

Quando uma função não precisa devolver nenhum valor ao chamador — por exemplo, apenas imprime algo na tela — usamos o tipo void 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;
}
==============================
  Relatório de Notas
==============================
----------
Funções chamando outras funções: 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.

6. 🔍 Posição da Definição — Antes ou Depois do main?

Esta é uma dúvida frequente. Em C há duas abordagens válidas:

Opção A — Definição ANTES do main (sem protótipo):
#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.

Opção B — Definição DEPOIS do main (com protótipo):
#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.

O que acontece sem protótipo e com definição depois do main? O compilador encontra a chamada 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.
Boas práticas do mercado: Em projetos reais, os protótipos ficam em arquivos de cabeçalho (.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.

7. Fluxo de Execução — O que acontece quando uma função é chamada

Entender o que o computador faz quando chama uma função ajuda a evitar vários bugs comuns.

main() executa normalmente
↓ encontra chamada calcularMedia(turmaA, 5)
Suspende main — empilha os argumentos
↓
Executa o corpo de calcularMedia
↓ encontra return soma/n
Desempilha — retorna 7.0 ao chamador
↓
main() 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;
}
[main] antes da chamada
  [dentro de soma] a=3, b=4
  [dentro de soma] retornando 7
[main] depois da chamada, s = 7
Os parâmetros 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.

8. Exemplo Completo — Programa com Múltiplas Funções

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;
}
Quantos alunos? (1 a 50): 4
Nota 1: 7.5
Nota 2: 8.0
Nota 3: 6.0
Nota 4: 9.5

===== RELATÓRIO =====
Alunos: 4
Média: 7.75
Maior: 9.50
Menor: 6.00
=====================
Note como o 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.

9. C vs Java — Funções vs Métodos

AspectoCJava
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
C — função global:
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));
}
Java — método estático:
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));
    }
}

10. Exercícios de Fixação

Responda às questões abaixo e clique em Verificar Respostas.

1. Qual é a diferença entre a declaração e a definição de uma função em C?

2. O que o tipo de retorno void indica numa função?

3. Qual é a saída do código abaixo?

int triplo(int x) { return x * 3; }

int main() {
    int a = 4;
    printf("%d", triplo(a + 1));
    return 0;
}

4. Se uma função é definida depois do main() sem protótipo, o que ocorre em C99/C11?

5. Nos parâmetros float media(float v[], int n), o que é n?

6. Qual afirmação sobre funções em C vs métodos em Java é correta?

7. Qual é a saída do programa abaixo?

void dobrar(int x) { x = x * 2; }

int main() {
    int n = 5;
    dobrar(n);
    printf("%d", n);
    return 0;
}

8. Qual das opções é um protótipo de função válido em C?