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
voidpara 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: DRY — Don'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
printfsem saber como ela imprime na tela — isso é abstração em ação. - Legibilidade e manutenção: uma
mainque chamaler_dados(),calcular()emostrar_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.
#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 xacima). É um "espaço reservado" para o valor que chegará. - Argumento é o valor real passado na chamada (o
5emquadrado(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:
#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.
#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:
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¶
#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
returnnuma função que deveria devolver valor. - Tipos incompatíveis entre o
returne 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 def();). - 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:
- Refatorar um programa "tudo na
main", extraindo funções bem definidas. - Praticar a decomposição de um problema em sub-rotinas.
- 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:
| Nº | 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¶
int eh_primo(int n)— devolve 1 sené primo, 0 caso contrário. Use-a num laço namainpara imprimir todos os primos até 100.int maximo(int a, int b)e, reutilizando-a,int maximo3(int a, int b, int c).- Conversões de temperatura:
double c_para_f(double c)edouble f_para_c(double f). long fatorial(int n)com laço; teste com vários valores namain.void desenha_retangulo(int largura, int altura)que imprime um retângulo de asteriscos (combina funções com laços aninhados da Semana 03).- Menu funcional: uma calculadora onde cada operação (
soma,subtrai,multiplica,divide) é uma função, e amainsó lê a escolha e chama a função certa.
Para investigar (curiosidade prática)¶
- Escreva a função
tenta_dobrarda seção 8, execute e confirme quennão muda. Depois explique, com suas palavras, por quê. - Declare uma variável
int identro 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
voidnos 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.