📦 Funções em Programação
Funções são um dos pilares da programação estruturada. Elas nos permitem organizar e reutilizar código, tornando nossos programas mais modulares, legíveis e fáceis de manter.
🤔 O que são Funções?
De maneira informal, uma função é uma unidade de código autônoma que realiza uma tarefa específica. Ela também pode ser chamada de subprograma ou sub-rotina. Suas principais características são:
- Pode receber parâmetros (ou argumentos) de entrada.
- Pode executar uma lógica interna.
- Pode retornar um valor de saída.
Qual a importância de usar funções?
Utilizar funções traz enormes benefícios para o desenvolvimento de software:
- Dividir para conquistar: Permite quebrar um problema complexo em problemas menores e mais gerenciáveis.
- Organização e Legibilidade: O código fica mais limpo e mais fácil de entender.
- Reaproveitamento de código: Uma função escrita uma vez pode ser chamada (usada) várias vezes em diferentes partes do programa.
- Delegação: Facilita a atribuição de responsabilidades em projetos de equipe.
💡 Funções “soltas”, diferente de Java: em Java (que você vai ver no tópico 13), toda função vive dentro de uma classe e é chamada de “método”. Em C não existem classes — uma função é declarada livremente no arquivo, fora de qualquer outra estrutura. É um modelo mais simples e mais próximo de como o processador realmente executa código.
Dissecando uma Função em C
Vamos analisar a estrutura de uma função em C, traduzindo o exemplo media do material de estudo.
// (1) (2) (3)
double calcularMedia(double a, double b) {
// (4) Corpo da função
double soma = a + b; // Variável local da função
// (5) Retorno da função
return soma / 2.0;
}
- Tipo de Retorno:
doubleindica o tipo de dado que a função irá retornar ao final de sua execução. - Nome da Função:
calcularMediaé o identificador que usamos para chamar a função. - Parâmetros de Entrada:
(double a, double b)é a lista de parâmetros que a função espera receber. - Corpo da Função: O bloco de código entre chaves
{}onde a lógica é executada. - Comando
return: Envia o resultado do cálculo de volta para quem chamou a função.
Chamando uma Função
Para usar uma função, você a “chama” pelo nome, passando os valores esperados para os parâmetros.
#include <stdio.h>
double calcularMedia(double a, double b) {
double soma = a + b;
return soma / 2.0;
}
int main() {
// Chamando a função e armazenando o resultado
double mediaFinal = calcularMedia(8.0, 7.5);
printf("A média é: %.2f\n", mediaFinal); // Saída: A média é: 7.75
return 0;
}
💡 Por que
calcularMediaestá definida ANTES demain()? Em C, uma função precisa ser conhecida pelo compilador antes de ser chamada — ao contrário de Java, onde a ordem dos métodos dentro da classe não importa. A forma mais simples de garantir isso (usada neste tópico) é definir a função inteira acima demain(). Em programas maiores, o padrão é declarar só o protótipo da função no topo do arquivo (double calcularMedia(double a, double b);) e colocar a definição completa em qualquer lugar depois — você vai ver esse padrão no tópico 11 (Linguagem C).
🔐 Escopo de Variáveis
O escopo de uma variável define onde ela é “visível” e pode ser utilizada.
Em C, as variáveis declaradas dentro de uma função, incluindo seus parâmetros, são locais. Isso significa que elas só existem e só podem ser acessadas dentro daquela função.
- Uma função não “enxerga” as variáveis locais de outra função.
- Se duas funções diferentes tiverem variáveis com o mesmo nome, elas são completamente independentes uma da outra.
Exemplo:
#include <stdio.h>
void funcaoF() {
int x = 10; // 'x' só existe aqui dentro
printf("Dentro da funcaoF, x = %d\n", x);
}
void funcaoG() {
int x = 99; // Este é outro 'x', totalmente diferente do anterior
printf("Dentro da funcaoG, x = %d\n", x);
// printf("%d", x); // O 'x' de funcaoF não existe aqui — são variáveis independentes
}
Boas Práticas: Para que uma função seja previsível e reutilizável, ela deve, idealmente, ser uma função pura. Isso significa que seu resultado deve depender apenas de seus parâmetros de entrada, sem acessar ou modificar variáveis de fora de seu escopo (evitando “efeitos colaterais”).
📝 Documentando Funções com Doxygen
Documentar seu código é crucial. Em C, o padrão mais usado para isso é o Doxygen, que usa exatamente o mesmo formato de comentário do Javadoc (/** ... */) — a ferramenta muda, a sintaxe do comentário é a mesma.
- Assinatura: A primeira linha da definição da função.
- Descrição dos Parâmetros: O que cada parâmetro de entrada representa (usando a tag
@param). - Efeito da Função: O que a função faz e o que ela retorna (usando a tag
@return).
Exemplo com a fórmula de Heron:
#include <math.h> // Necessário para a função sqrt()
/**
* Calcula a área de um triângulo usando a fórmula de Heron.
*
* @param a Primeiro lado do triângulo
* @param b Segundo lado do triângulo
* @param c Terceiro lado do triângulo
* @return A área calculada do triângulo
*/
double areaTriangulo(double a, double b, double c) {
double p = (a + b + c) / 2.0;
return sqrt(p * (p - a) * (p - b) * (p - c));
}
📢 Funções que Não Retornam Valor (void)
Às vezes, uma função precisa executar uma ação (como imprimir algo na tela) em vez de calcular e retornar um valor. Essas funções são chamadas de procedimentos em algumas linguagens.
Em C, elas são declaradas com o tipo de retorno void — a mesma palavra-chave usada em Java para o mesmo propósito.
Exemplo: Gerar um Relatório
Problema: Ler os dados de um contrato e mostrar um relatório formatado na tela.
Solução em C com void:
#include <stdio.h>
/**
* Exibe um relatório de financiamento formatado no console.
* @param nome O nome do cliente
* @param total O valor total do financiamento
* @param entrada O valor da entrada
*/
void mostrarRelatorio(char nome[], double total, double entrada) {
double restante = total - entrada;
printf("\n--- RELATÓRIO ---\n");
printf("NOME: %s\n", nome);
printf("VALOR TOTAL: R$%.2f\n", total); printf("VALOR DE ENTRADA: R$ %.2f\n", entrada);
printf("VALOR PARCELADO: R$ %.2f\n", restante);
printf("-----------------\n");
}
int main() {
char nomeCliente[100];
double valorTotal, valorEntrada;
printf("Nome do cliente: ");
scanf("%99s", nomeCliente);
printf("Valor total financiado: ");
scanf("%lf", &valorTotal);
printf("Valor de entrada: ");
scanf("%lf", &valorEntrada);
// A chamada de uma função void é uma instrução "solta"
mostrarRelatorio(nomeCliente, valorTotal, valorEntrada);
return 0;
}
✍️ Exercícios Práticos Resolvidos
Exercício 1: Área de Retângulos
Problema: Pedir ao usuário para digitar, pelo teclado, as medidas de base e altura de dois retângulos, X e Y. Em seguida, mostrar a área de cada um e comparar qual dos dois tem a maior área.
Solução em C:
#include <stdio.h>
double areaRetangulo(double base, double altura) {
return base * altura;
}
int main() {
double baseX, alturaX, baseY, alturaY;
printf("--- Retângulo X ---\n");
printf("Digite a base do retângulo X: ");
scanf("%lf", &baseX);
printf("Digite a altura do retângulo X: ");
scanf("%lf", &alturaX);
printf("--- Retângulo Y ---\n");
printf("Digite a base do retângulo Y: ");
scanf("%lf", &baseY);
printf("Digite a altura do retângulo Y: ");
scanf("%lf", &alturaY);
double areaX = areaRetangulo(baseX, alturaX);
double areaY = areaRetangulo(baseY, alturaY);
printf("\nÁrea de X = %.2f\n", areaX);
printf("Área de Y = %.2f\n", areaY);
if (areaX > areaY) {
printf("O retângulo X tem a maior área!\n");
} else if (areaY > areaX) {
printf("O retângulo Y tem a maior área!\n");
} else {
printf("Os dois retângulos têm a mesma área!\n");
}
return 0;
}
Exemplo de Execução (digitando base=8, altura=3 para X e base=5.5, altura=4 para Y):
--- Retângulo X ---
Digite a base do retângulo X: 8
Digite a altura do retângulo X: 3
--- Retângulo Y ---
Digite a base do retângulo Y: 5.5
Digite a altura do retângulo Y: 4
Área de X = 24.00
Área de Y = 22.00
O retângulo X tem a maior área!
💡 Guarde esse retângulo X (base 8, altura 3): ele volta a aparecer mais adiante neste capítulo, na seção “Prévia do 2º Semestre” — só que lá, em vez de uma função solta, ele vira um objeto de verdade.
Exercício 2: Área de Triângulos
Problema: Pedir ao usuário para digitar, pelo teclado, as medidas dos três lados de dois triângulos, X e Y. Em seguida, mostrar o valor da área de cada um e comparar qual dos dois tem a maior área.
Solução em C:
#include <stdio.h>
#include <math.h>
// areaTriangulo() definida como na seção de Doxygen acima
double areaTriangulo(double a, double b, double c) {
double p = (a + b + c) / 2.0;
return sqrt(p * (p - a) * (p - b) * (p - c));
}
int main() {
double aX, bX, cX, aY, bY, cY;
printf("--- Triângulo X ---\n");
printf("Digite o lado A: ");
scanf("%lf", &aX);
printf("Digite o lado B: ");
scanf("%lf", &bX);
printf("Digite o lado C: ");
scanf("%lf", &cX);
printf("--- Triângulo Y ---\n");
printf("Digite o lado A: ");
scanf("%lf", &aY);
printf("Digite o lado B: ");
scanf("%lf", &bY);
printf("Digite o lado C: ");
scanf("%lf", &cY);
double areaX = areaTriangulo(aX, bX, cX);
double areaY = areaTriangulo(aY, bY, cY);
printf("\nÁrea de X = %.4f\n", areaX);
printf("Área de Y = %.4f\n", areaY);
if (areaX > areaY) {
printf("O triângulo X tem a maior área!\n");
} else if (areaY > areaX) {
printf("O triângulo Y tem a maior área!\n");
} else {
printf("Os dois triângulos têm a mesma área!\n");
}
return 0;
}
Exemplo de Execução (digitando 3, 4, 5 para X e 7.5, 4.5, 4.02 para Y):
--- Triângulo X ---
Digite o lado A: 3
Digite o lado B: 4
Digite o lado C: 5
--- Triângulo Y ---
Digite o lado A: 7.5
Digite o lado B: 4.5
Digite o lado C: 4.02
Área de X = 6.0000
Área de Y = 7.5638
O triângulo Y tem a maior área!
Exercício 3: Funções que Chamam Funções
Problema: Fazer um programa para ler cinco números inteiros e mostrar o menor dentre eles, reaproveitando a lógica ao máximo.
Solução em C:
#include <stdio.h>
/**
* Encontra o menor valor entre três números inteiros.
* @return O menor dos três números
*/
int menorDeTres(int x, int y, int z) {
if (x < y && x < z) {
return x;
} else if (y < z) {
return y;
} else {
return z;
}
}
/**
* Encontra o menor valor entre cinco números inteiros,
* reutilizando a função menorDeTres.
* @return O menor dos cinco números
*/
int menorDeCinco(int n1, int n2, int n3, int n4, int n5) {
// Encontra o menor dos três primeiros
int aux = menorDeTres(n1, n2, n3);
// Compara o resultado anterior com os dois últimos números
return menorDeTres(aux, n4, n5);
}
int main() {
int menor = menorDeCinco(9, 5, 3, 12, 7);
printf("Menor = %d\n", menor); // Saída: Menor = 3
return 0;
}
💡 Ordem das funções importa aqui:
menorDeCincochamamenorDeTres, por issomenorDeTresprecisa estar definida ANTES demenorDeCincono arquivo (que, por sua vez, está antes demain()). Se a ordem fosse invertida, o compilador acusaria erro por não conhecermenorDeTresainda — a menos que um protótipo dela fosse declarado no topo do arquivo.
🛠️ Como Executar no Code::Blocks
Você pode compilar e executar todos os exemplos de código acima no Code::Blocks (veja a instalação em Setup do Curso).
- Crie um Novo Projeto: Vá em
File > New > Project... > Console Application > C. Dê um nome ao projeto. - Abra o
main.c: O Code::Blocks já cria um arquivomain.cbásico dentro da pasta do projeto. - Cole o Código: Apague o conteúdo padrão e cole um dos exemplos acima.
- Compile e Execute: Pressione F9 (ou o botão de “Build and Run”). Uma janela de console vai abrir com a saída do programa.
🏛️ Modularização: A Evolução de um main() Limpo
Veja como as funções transformam a arquitetura de um software de um bloco monolítico em um fluxo limpo e orquestrado:
❌ Antes (Monolítico — muitas responsabilidades no main)
int main() {
// 50 linhas com leitura de dados, validações, cálculos complexos e formatação de saída
}
✅ Depois (Modular — responsabilidade única)
#include <stdio.h>
// Protótipos de funções
double ler_nota(int numero_nota);
double calcular_media(double n1, double n2);
void exibir_situacao(double media);
int main() {
double nota1 = ler_nota(1);
double nota2 = ler_nota(2);
double media = calcular_media(nota1, nota2);
exibir_situacao(media);
return 0;
}
double ler_nota(int numero_nota) {
double n;
printf("Digite a nota %d: ", numero_nota);
scanf("%lf", &n);
return n;
}
double calcular_media(double n1, double n2) {
return (n1 + n2) / 2.0;
}
void exibir_situacao(double media) {
printf("\nMédia Final: %.2f\n", media);
if (media >= 6.0) {
printf("Situação: APROVADO\n");
} else {
printf("Situação: REPROVADO\n");
}
}
🎓 Fim da Base do 1º Semestre
Parabéns! Com o domínio de Fundamentos da Computação, Lógica Booleana, Teste de Mesa, VisualG, Python com Type Hints, Portugol Studio, Linguagem C Estruturada e Modularização com Funções, você concluiu a base essencial do primeiro semestre.
Até agora, organizamos nossos sistemas separando os dados (variáveis e structs) das operações (funções) — exatamente o que fizemos na seção anterior, ao transformar um main() monolítico em funções especializadas. Mas existem perguntas maiores: e se dados e operações pudessem viver juntos, no mesmo lugar? E se, em vez de mudar o estado passo a passo, a gente só descrevesse transformações, sem nunca alterar nada?
Essas perguntas têm nome: Programação Orientada a Objetos (POO) e Programação Funcional. Em vez de esperar capítulos futuros para ver isso pela primeira vez, este capítulo já mostra como C resolve o mesmo problema (calcular a área e o perímetro de um retângulo) nos três paradigmas — e os Capítulos 05 (Python), 19 (Java) e 30 (JavaScript) fazem o mesmo exercício na própria linguagem deles, formando uma pequena matriz: 4 linguagens × 3 paradigmas, resolvendo sempre o mesmo problema.
🔀 C em 3 Paradigmas: Resolvendo o Retângulo de Formas Diferentes
🗺️ Modelando o Retângulo
classDiagram
class Retangulo {
-float base
-float altura
+calcularArea() float
+calcularPerimetro() float
}
🔁 Imperativo
Funções puras, recebendo dados e devolvendo resultado, sem nenhuma entidade “Retângulo” existindo de fato no código — exatamente o estilo que você já pratica desde o início deste capítulo.
#include <stdio.h>
float calcular_area(float base, float altura) {
return base * altura;
}
float calcular_perimetro(float base, float altura) {
return 2 * (base + altura);
}
int main(void) {
float base, altura;
printf("Digite a base do retangulo: ");
scanf("%f", &base);
printf("Digite a altura do retangulo: ");
scanf("%f", &altura);
printf("Area: %.2f\n", calcular_area(base, altura));
printf("Perimetro: %.2f\n", calcular_perimetro(base, altura));
return 0;
}
🧬 Orientado a Objetos (simulado com struct)
#include <stdio.h>
typedef struct {
float base;
float altura;
} Retangulo;
float calcular_area(Retangulo *r) {
return r->base * r->altura;
}
float calcular_perimetro(Retangulo *r) {
return 2 * (r->base + r->altura);
}
int main(void) {
Retangulo meuRetangulo;
printf("Digite a base do retangulo: ");
scanf("%f", &meuRetangulo.base);
printf("Digite a altura do retangulo: ");
scanf("%f", &meuRetangulo.altura);
printf("Area: %.2f\n", calcular_area(&meuRetangulo));
printf("Perimetro: %.2f\n", calcular_perimetro(&meuRetangulo));
return 0;
}
C não tem classes — a
structagrupa os dados, mas as funções continuam soltas, apenas recebendo um ponteiro para operar sobre eles. É o embrião de um objeto, sem encapsulamento real (nada impede acessarmeuRetangulo.basediretamente de fora). Veja como Python e Java resolvem isso de verdade nos links ao final desta seção.
🧩 Funcional (aproximado com ponteiro de função)
#include <stdio.h>
typedef struct {
float base;
float altura;
} Retangulo;
float calcular_area(Retangulo r) {
return r.base * r.altura;
}
float calcular_perimetro(Retangulo r) {
return 2 * (r.base + r.altura);
}
// "Função de alta ordem": recebe outra função como parâmetro (ponteiro de função)
void aplicar_e_exibir(Retangulo r, float (*operacao)(Retangulo), const char *rotulo) {
printf("%s: %.2f\n", rotulo, operacao(r));
}
int main(void) {
Retangulo r; // passado por VALOR (cópia) nas chamadas abaixo -- aproxima imutabilidade
printf("Digite a base do retangulo: ");
scanf("%f", &r.base);
printf("Digite a altura do retangulo: ");
scanf("%f", &r.altura);
aplicar_e_exibir(r, calcular_area, "Area");
aplicar_e_exibir(r, calcular_perimetro, "Perimetro");
return 0;
}
⚠️ Isso NÃO é programação funcional de verdade — é o teto que C consegue alcançar.
Retangulo ré passado por valor (a cópia local não afeta orde quem chamou, uma aproximação de imutabilidade), eaplicar_e_exibirrecebe uma função como parâmetro via ponteiro de função (uma aproximação de “função de alta ordem”). Mas C não tem closures, não impede mutação de verdade, e não tem funções anônimas. É exatamente por isso que linguagens como JavaScript e Haskell existem — veja o Guia Poliglota de Linguagens para o real.
🔗 Veja o mesmo problema em:
- 🐍 Python (seção “🔀 Python em 3 Paradigmas”)
- ☕ Java (seção “🔀 Java em 3 Paradigmas”)
- ⚡ JavaScript (seção “🔀 JavaScript em 3 Paradigmas”)
🗂️ Aprofundando: Organização Profissional em Arquivos Separados
Além de escolher um paradigma, projetos reais também separam a definição do Retângulo do ponto de entrada da aplicação — um tema que pertence especificamente a este capítulo sobre modularização.
graph TD
A["main.c / Main.java / main.py<br/>(Ponto de Entrada)"] -->|"#include / import"| B["retangulo.h + retangulo.c<br/>Retangulo.java<br/>retangulo.py<br/>(Módulo / Classe)"]
C:
// retangulo.h
#ifndef RETANGULO_H
#define RETANGULO_H
typedef struct {
float base;
float altura;
} Retangulo;
float calcular_area(Retangulo *r);
float calcular_perimetro(Retangulo *r);
#endif
// retangulo.c
#include "retangulo.h"
float calcular_area(Retangulo *r) {
return r->base * r->altura;
}
float calcular_perimetro(Retangulo *r) {
return 2 * (r->base + r->altura);
}
// main.c
#include <stdio.h>
#include "retangulo.h"
int main(void) {
Retangulo meuRetangulo;
meuRetangulo.base = 8.0f;
meuRetangulo.altura = 3.0f;
printf("Area: %.2f\n", calcular_area(&meuRetangulo));
printf("Perimetro: %.2f\n", calcular_perimetro(&meuRetangulo));
return 0;
}
gcc main.c retangulo.c -o programa_retangulo
./programa_retangulo
Python:
# retangulo.py
class Retangulo:
def __init__(self, base: float, altura: float):
self.base = base
self.altura = altura
def calcular_area(self) -> float:
return self.base * self.altura
def calcular_perimetro(self) -> float:
return 2 * (self.base + self.altura)
# main.py
from retangulo import Retangulo
def main():
meu_retangulo = Retangulo(8.0, 3.0)
print(f"Area: {meu_retangulo.calcular_area():.2f}")
print(f"Perimetro: {meu_retangulo.calcular_perimetro():.2f}")
if __name__ == "__main__":
main()
python main.py
Java:
// Retangulo.java
public class Retangulo {
private double base;
private double altura;
public Retangulo(double base, double altura) {
this.base = base;
this.altura = altura;
}
public double getBase() { return base; }
public void setBase(double base) { this.base = base; }
public double getAltura() { return altura; }
public void setAltura(double altura) { this.altura = altura; }
public double calcularArea() {
return base * altura;
}
public double calcularPerimetro() {
return 2 * (base + altura);
}
}
// Main.java
import java.util.Locale;
public class Main {
public static void main(String[] args) {
Retangulo meuRetangulo = new Retangulo(8.0, 3.0);
System.out.printf(Locale.US, "Area: %.2f%n", meuRetangulo.calcularArea());
System.out.printf(Locale.US, "Perimetro: %.2f%n", meuRetangulo.calcularPerimetro());
}
}
javac Retangulo.java Main.java
java Main
🎯 Perguntas de Fixação
- Por que dizemos que a
structem C é apenas o “embrião” de um objeto, e não um objeto real como em Python ou Java? - Na versão “Funcional” em C, por que passar
Retangulo rpor valor (em vez de por ponteiro) é importante para a ideia de imutabilidade — mesmo sem C impor isso de verdade? - Por que separar
Retangulodomain.cé considerado boa prática, mesmo num programa tão pequeno?
[!IMPORTANT] O que vem depois: esta foi só uma prévia em C. A partir do Capítulo 19,
Retângulovira um verdadeiro objeto em Java — com herança, polimorfismo e interfaces. E a partir do Capítulo 30, você vê o paradigma funcional em sua forma nativa, sem as limitações do C — aprofundado ainda mais no Capítulo 39 (Haskell).