Pular para conteúdo

Semana 04 — Funções e sub-rotinas

Nesta semana

Unidade 1 · Seg 31/08 · Qua 02/09 · Sex 04/09

Até aqui, tudo o que fizemos morava dentro da main. Isso funciona para programas pequenos, mas rapidamente vira um emaranhado difícil de entender e corrigir. Nesta semana aprendemos a quebrar o programa em funções: pequenos blocos com nome, reutilizáveis, cada um resolvendo uma parte do problema. É a ideia de "dividir para conquistar" transformada em código — e uma das habilidades mais importantes de toda a disciplina.

Vamos ver como definir e chamar funções, o que são parâmetros e valor de retorno, o escopo das variáveis e o conceito, central em C, de passagem de parâmetros por valor. Esta semana encerra a Unidade 1 (conteúdo da Avaliação 1).

🎯 Objetivos de aprendizagem

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

  • explicar por que dividir um programa em funções (reuso, organização, legibilidade);
  • definir funções com parâmetros e valor de retorno, e chamá-las corretamente;
  • distinguir parâmetro de argumento e usar void para retorno e para parâmetros;
  • entender a diferença entre declaração (protótipo) e definição, e por que a ordem importa;
  • explicar o escopo e o tempo de vida de variáveis locais e globais;
  • explicar, com clareza, o que é passagem por valor e prever suas consequências.

📖 Segunda — Estudo do conteúdo

1. Por que usar funções?

Uma função é um bloco de código com nome, que executa uma tarefa e pode ser chamado (executado) sempre que necessário. Você já vem usando funções prontas desde a primeira semana — printf, scanf — e a própria main é uma função. Agora vamos criar as nossas.

As vantagens são enormes:

  • Reuso (não se repita): escreva a lógica uma vez e use-a em vários lugares. Se precisar corrigir, corrige em um ponto só. Esse princípio é tão importante que tem nome: DRYDon't Repeat Yourself ("não se repita").
  • Dividir para conquistar: um problema grande vira vários pequenos, cada um em sua função. Problemas pequenos são fáceis de resolver e de testar.
  • Abstração: depois de pronta, você usa a função pelo o que ela faz, sem precisar lembrar como ela faz. Você usa printf sem saber como ela imprime na tela — isso é abstração em ação.
  • Legibilidade e manutenção: uma main que chama ler_dados(), calcular() e mostrar_resultado() conta uma história clara.

Curiosidade 💡 — função, procedimento, sub-rotina, método…

Você verá vários nomes para a mesma ideia geral de "bloco de código reutilizável". Em algumas linguagens, distingue-se função (devolve um valor) de procedimento ou sub-rotina (só executa uma ação, sem devolver valor). Em C, tudo é "função" — mas uma função que não devolve valor (tipo de retorno void) faz o papel do "procedimento". Em linguagens orientadas a objetos, funções dentro de objetos são chamadas de métodos.

2. Anatomia de uma função

tipo_de_retorno  nome_da_funcao(lista_de_parametros) {
    // corpo: o que a função faz
    return valor;   // devolve um resultado (se o tipo de retorno não for void)
}

Um exemplo concreto:

int quadrado(int x) {     // devolve int; recebe um int chamado x
    return x * x;         // calcula e devolve x²
}

Dissecando:

  • int (antes do nome) — o tipo do valor que a função devolve a quem a chamou.
  • quadrado — o nome da função (escolha nomes que digam o que ela faz).
  • int x — o parâmetro: um dado de entrada que a função recebe.
  • return x * x; — encerra a função e devolve o valor calculado.

3. Chamando uma função: o fluxo de controle

Quando o programa chama uma função, o controle "salta" para dentro dela; ao encontrar o return (ou o fim do corpo), o controle volta ao ponto exato da chamada, trazendo o valor devolvido.

usa_quadrado.c
#include <stdio.h>

int quadrado(int x) {
    return x * x;
}

int main(void) {
    int r = quadrado(5);          // r recebe 25
    printf("%d\n", r);            // 25
    printf("%d\n", quadrado(3));  // 9  (a chamada "vira" o valor 9)
    return 0;
}
flowchart LR
    M1["main: r = quadrado(5)"] -->|chama, passando 5| Q["quadrado: x = 5<br/>return 25"]
    Q -->|devolve 25| M2["main: r = 25"]

Uma chamada de função é uma expressão que "vale" o valor retornado — por isso podemos usá-la direto dentro de um printf, de uma atribuição ou de outra expressão.

4. Parâmetro × argumento

Dois termos que costumam ser confundidos:

  • Parâmetro é a variável declarada na definição da função (o int x acima). É um "espaço reservado" para o valor que chegará.
  • Argumento é o valor real passado na chamada (o 5 em quadrado(5)).

Ou seja: "o argumento 5 foi copiado para o parâmetro x". Guarde a palavra copiado — ela será a chave da seção 8.

Uma função pode ter vários parâmetros, separados por vírgula (cada um com seu tipo):

double media(double a, double b) {
    return (a + b) / 2.0;
}
// chamada:
double m = media(7.5, 9.0);   // a=7.5, b=9.0  ->  m = 8.25

5. void: funções sem retorno e sem parâmetros

A palavra void ("vazio") aparece em dois papéis:

  • Como tipo de retorno, indica que a função não devolve valor — ela só faz algo (imprime, por exemplo).
  • Na lista de parâmetros, indica que a função não recebe nada.
void imprime_linha(void) {         // não devolve nada, não recebe nada
    printf("------------------------\n");
}

void saudacao(char inicial) {      // recebe, mas não devolve
    printf("Ola, %c.\n", inicial);
}

// uso:
imprime_linha();
saudacao('A');

Numa função void, você pode usar return; (sem valor) para encerrar antes do fim — mas nunca return algum_valor;.

Chamada sempre tem parênteses

Para chamar uma função, os parênteses são obrigatórios, mesmo sem argumentos: imprime_linha();. Escrever apenas imprime_linha; não chama a função (é uma expressão sem efeito).

6. Declaração (protótipo) × definição

Em C, uma função precisa ser conhecida pelo compilador antes de ser usada. Há duas formas de garantir isso:

Forma 1 — definir antes de usar: basta escrever a função acima da main (foi o que fizemos até agora).

Forma 2 — usar um protótipo: um protótipo é a "assinatura" da função (tipo de retorno, nome e tipos dos parâmetros), seguida de ;, colocada no topo do arquivo. A definição completa pode então vir depois da main:

prototipo.c
#include <stdio.h>

double media(double a, double b);   // PROTÓTIPO (declaração): promete que a função existe

int main(void) {
    printf("%.2f\n", media(7.5, 9.0));
    return 0;
}

double media(double a, double b) {  // DEFINIÇÃO (a implementação de verdade)
    return (a + b) / 2.0;
}

Isso permite organizar o código com a main no topo (a "história" do programa) e os detalhes embaixo. Protótipos são também a base da modularização (dividir o programa em vários arquivos), tema da Semana 05.

Curiosidade 💡 — é isto que o #include <stdio.h> faz

O arquivo stdio.h é, essencialmente, uma coleção de protótipos (de printf, scanf, etc.). Ao incluí-lo, você "promete" ao compilador que essas funções existem; a implementação real delas é juntada na etapa de ligação (Semana 01, seção 2).

7. Escopo e tempo de vida das variáveis

O escopo de uma variável é a região do programa onde ela existe e pode ser usada. O tempo de vida é o período em que ela ocupa memória.

  • Variável local: declarada dentro de uma função (ou de um bloco { }). Só é visível ali. Nasce quando a função é chamada e morre quando a função termina. Cada chamada tem suas próprias cópias das variáveis locais.
  • Variável global: declarada fora de todas as funções. É visível no arquivo inteiro e vive durante toda a execução do programa.
#include <stdio.h>

int contador = 0;        // GLOBAL: visível em todo o arquivo

void incrementa(void) {
    int passo = 1;       // LOCAL: só existe dentro de incrementa
    contador += passo;
}

int main(void) {
    incrementa();
    incrementa();
    printf("%d\n", contador);   // 2
    // printf("%d\n", passo);   // ERRO: 'passo' não existe aqui
    return 0;
}

Duas funções podem ter variáveis locais com o mesmo nome sem qualquer conflito — são independentes, cada uma no seu escopo. É por isso que usar i como contador de laço em várias funções não causa problema.

Evite variáveis globais

Globais parecem convenientes, mas tornam o programa difícil de entender e depurar: qualquer função pode alterá-las, e fica difícil rastrear quem mudou o quê e quando. A boa prática é: passe dados por parâmetros e devolva resultados com return. Reserve globais para pouquíssimos casos (como constantes com const).

Curiosidade 💡 — a pilha de chamadas (call stack)

Onde ficam as variáveis locais? Numa região da memória chamada pilha (stack). A cada chamada de função, um "quadro" (stack frame) é empilhado com os parâmetros e as variáveis locais daquela chamada; quando a função retorna, o quadro é desempilhado (e as locais somem). Esse mecanismo é o que veremos com profundidade na Unidade 3 — e é também o que torna a recursão (Semana 06) possível.

8. Passagem de parâmetros por valor

Este é o ponto mais importante da semana. Em C, ao chamar uma função, os argumentos são passados por valor: a função recebe uma cópia do valor. Portanto, alterar um parâmetro dentro da função NÃO altera a variável original de quem chamou.

por_valor.c
#include <stdio.h>

void tenta_dobrar(int x) {
    x = x * 2;                    // altera apenas a CÓPIA local
    printf("dentro:  x = %d\n", x);
}

int main(void) {
    int n = 10;
    tenta_dobrar(n);             // passa uma cópia de n (o valor 10)
    printf("depois:  n = %d\n", n);  // n continua 10!
    return 0;
}

Saída:

dentro:  x = 20
depois:  n = 10

Visualmente, n e x são caixas diferentes; a chamada apenas copia o conteúdo de n para x:

flowchart LR
    N["main<br/>n = 10"] -->|copia o valor 10| X["tenta_dobrar<br/>x = 10 → 20"]
    X -. não afeta .-> N

Isso é, na verdade, uma proteção: uma função não consegue, por acidente, estragar as variáveis de quem a chamou. O preço é que, com o que sabemos até aqui, uma função só "devolve" resultado pelo return — e return devolve um valor.

E se eu precisar que a função altere a variável original? Ou devolver vários resultados?

Excelente pergunta — e a resposta é o gancho para a Unidade 3. Para permitir que uma função modifique a variável de quem chamou (a passagem por referência), passamos o endereço da variável, usando ponteiros. É exatamente o que o & do scanf já faz nos bastidores desde a Semana 01! Veremos isso em detalhe na Semana 11. Por ora, projete suas funções para devolver o resultado com return.

9. Boas práticas ao escrever funções

  • Uma função, uma tarefa. Se você precisa da palavra "e" para descrever o que a função faz ("lê os dados e calcula e imprime"), provavelmente são três funções.
  • Nomes que são verbos/ações: calcular_media, eh_primo, imprimir_menu.
  • Prefira funções curtas. Se uma função não cabe na tela, considere dividi-la.
  • Funções "puras" quando possível: que dependem só dos seus parâmetros e devolvem um resultado, sem mexer em variáveis globais. São as mais fáceis de testar e reutilizar.

10. Um exemplo que junta tudo

geometria.c
#include <stdio.h>

double area_retangulo(double base, double altura) {
    return base * altura;
}

double perimetro_retangulo(double base, double altura) {
    return 2 * (base + altura);
}

void imprime_relatorio(double base, double altura) {
    printf("Retangulo %.1f x %.1f\n", base, altura);
    printf("  Area:      %.2f\n", area_retangulo(base, altura));
    printf("  Perimetro: %.2f\n", perimetro_retangulo(base, altura));
}

int main(void) {
    double b, h;
    printf("Base e altura: ");
    scanf("%lf %lf", &b, &h);
    imprime_relatorio(b, h);   // uma função pode chamar outras
    return 0;
}

Repare como a main ficou curta e legível, e como imprime_relatorio reutiliza as funções de cálculo.

11. Erros comuns desta semana (resumão)

Checklist de armadilhas

  • Usar uma função antes de defini-la ou declará-la (falta o protótipo).
  • Esquecer o return numa função que deveria devolver valor.
  • Tipos incompatíveis entre o return e o tipo de retorno declarado.
  • Esperar que a função altere o argumento original (ela recebe uma cópia — passagem por valor).
  • Chamar a função sem os parênteses (f; em vez de f();).
  • Tentar usar uma variável local fora do seu escopo.
  • Abusar de variáveis globais para "compartilhar" dados entre funções.

👥 Quarta — Encontro

Chegue tendo estudado e executado os exemplos. No encontro vamos:

  1. Refatorar um programa "tudo na main", extraindo funções bem definidas.
  2. Praticar a decomposição de um problema em sub-rotinas.
  3. Visualizar, passo a passo, a passagem por valor e o escopo (usando a ideia da pilha de chamadas).

Problema-guia — caixa eletrônico (parte 1): escrever funções para um sistema simples de saque: uma função que valida se um valor é sacável, outra que calcula o número de notas de cada valor, e um menu() em void. (Sem alterar variáveis externas — só com return e printf — para reforçar a passagem por valor.)


✍️ Sexta — Estudo dirigido

Exercícios guiados (Beecrowd)

A ideia central é extrair funções. Refaça alguns exercícios das semanas anteriores movendo a lógica para funções próprias, e resolva:

Problema Função a criar
1153 Fatorial Simples long fatorial(int n)
1157 Divisores int conta_divisores(int n)
1165 Números Primos int eh_primo(int n)
1101 Sequência de Números e Soma função que soma um intervalo
1116 Dividindo Valores void divide(...)
1178 Preenchimento de Vetor III (revisita na Semana 07)

Desafios para escrever do zero

  1. int eh_primo(int n) — devolve 1 se n é primo, 0 caso contrário. Use-a num laço na main para imprimir todos os primos até 100.
  2. int maximo(int a, int b) e, reutilizando-a, int maximo3(int a, int b, int c).
  3. Conversões de temperatura: double c_para_f(double c) e double f_para_c(double f).
  4. long fatorial(int n) com laço; teste com vários valores na main.
  5. void desenha_retangulo(int largura, int altura) que imprime um retângulo de asteriscos (combina funções com laços aninhados da Semana 03).
  6. Menu funcional: uma calculadora onde cada operação (soma, subtrai, multiplica, divide) é uma função, e a main só lê a escolha e chama a função certa.

Para investigar (curiosidade prática)

  • Escreva a função tenta_dobrar da seção 8, execute e confirme que n não muda. Depois explique, com suas palavras, por quê.
  • Declare uma variável int i dentro de duas funções diferentes e mostre que elas são independentes (mudar em uma não afeta a outra).

✅ Checklist de autoavaliação

  • Explico por que dividir um programa em funções (DRY, dividir para conquistar).
  • Defino funções com parâmetros e return, e as chamo corretamente.
  • Diferencio parâmetro de argumento e uso void nos dois papéis.
  • Sei a diferença entre protótipo (declaração) e definição.
  • Explico escopo e tempo de vida de variáveis locais e globais.
  • Explico a passagem por valor e prevejo que a original não muda.
  • Consigo decompor um problema em funções pequenas e bem nomeadas.

Fim da Unidade 1 — prepare-se para a Avaliação 1

Você agora domina os fundamentos: variáveis e tipos, entrada/saída, operadores, decisões, repetição e funções. Esse é o conteúdo da Avaliação 1 (semanas S1–S4). Revise os exemplos, refaça os exercícios que ficaram difíceis e treine o hábito de decompor problemas em funções — é isso que abre caminho para a Unidade 2 (modularização, recursão e estruturas de dados).

🔗 Referências e para se aprofundar

  • Material 2026.1 — Aula 04: Funções.
  • BACKES, André. Linguagem C: completa e descomplicada (funções e escopo).
  • KERNIGHAN, B.; RITCHIE, D. The C Programming Language, cap. 4 (funções e estrutura do programa).
  • MIZRAHI, Victorine V. Treinamento em Linguagem C (funções).
  • Referência: cppreference — functions.
  • Prática: Beecrowd.