Funções são blocos de código reutilizáveis que executam uma tarefa específica. Elas permitem organizar o programa em partes menores, facilitando a leitura, manutenção e teste do código. Em C, toda função tem um tipo de retorno, um nome, uma lista de parâmetros (que pode ser vazia) e um corpo com as instruções. Nesta aula, vamos explorar os conceitos fundamentais de declaração, definição, protótipos, parâmetros, retorno e o tipo void.

Dominar funções é essencial para escrever programas modulares e eficientes. Você verá como declarar funções corretamente, como passar dados para elas e como receber resultados de volta, além de entender quando usar void tanto no retorno quanto nos parâmetros.

Declaração e definição

Em C, uma função precisa ser declarada antes de ser usada, para que o compilador conheça sua assinatura (nome, parâmetros e tipo de retorno). A definição da função contém o corpo com as instruções que serão executadas. A declaração também é chamada de protótipo.

A definição de uma função segue a sintaxe:

tipo_de_retorno nome_da_funcao(lista_de_parametros) {
    // corpo da função
    return valor; // se o tipo de retorno não for void
}

Por exemplo, uma função que soma dois inteiros pode ser definida assim:

int soma(int a, int b) {
    return a + b;
}

Nesse caso, int é o tipo de retorno, soma é o nome, e a e b são parâmetros. A função retorna a soma dos dois números.

Se a função não retorna valor, usamos void como tipo de retorno. A definição pode ser colocada antes do main ou depois, mas se for depois, é necessário um protótipo antes do uso.

Exemplo de definição antes do main:

#include <stdio.h>

int soma(int a, int b) {
    return a + b;
}

int main() {
    int resultado = soma(3, 4);
    printf("%d\n", resultado); // 7
    return 0;
}

Se a função for definida depois do main, precisamos declarar o protótipo antes:

#include <stdio.h>

// Protótipo (declaração)
int soma(int a, int b);

int main() {
    int resultado = soma(3, 4);
    printf("%d\n", resultado); // 7
    return 0;
}

// Definição depois do main
int soma(int a, int b) {
    return a + b;
}

Protótipos

Um protótipo de função informa ao compilador o nome da função, o tipo de retorno e os tipos dos parâmetros, sem incluir o corpo. Ele é essencial quando a função é chamada antes de sua definição, permitindo que o compilador verifique se a chamada está correta em termos de tipos e número de argumentos.

A sintaxe do protótipo é igual à da definição, mas termina com ponto e vírgula e sem o corpo:

tipo_de_retorno nome_da_funcao(lista_de_parametros);

Exemplo:

int soma(int a, int b); // protótipo

Os protótipos geralmente são colocados no início do arquivo, após os #include, ou em arquivos de cabeçalho (.h). Isso permite que qualquer função chame outra sem se preocupar com a ordem de definição.

É importante que o protótipo corresponda exatamente à definição, incluindo os tipos dos parâmetros. Em C, o nome dos parâmetros no protótipo é opcional, mas é uma boa prática incluí-los para documentação.

// Protótipo sem nomes (válido)
int soma(int, int);

// Protótipo com nomes (recomendado)
int soma(int a, int b);

Se o protótipo não for fornecido e a função for usada antes da definição, o compilador pode gerar um aviso ou erro, dependendo da versão do C. Em C99 e posteriores, é obrigatório ter um protótipo antes da chamada.

Parâmetros e retorno

Parâmetros são valores que a função recebe para trabalhar. Eles são definidos na lista de parâmetros entre parênteses, com tipo e nome. Quando a função é chamada, os valores passados são chamados argumentos.

Em C, os parâmetros são passados por valor, ou seja, a função recebe uma cópia do valor original. Alterações feitas nos parâmetros dentro da função não afetam as variáveis da função chamadora. Para modificar variáveis originais, é necessário usar ponteiros (veremos em aulas futuras).

O retorno é o valor que a função devolve para quem a chamou, usando a instrução return. O tipo do valor retornado deve ser compatível com o tipo de retorno declarado.

Exemplo de função com parâmetros e retorno:

#include <stdio.h>

// Função que calcula a média de dois números
float media(float x, float y) {
    return (x + y) / 2.0;
}

int main() {
    float a = 5.0, b = 7.0;
    float m = media(a, b);
    printf("Média: %.2f\n", m); // Média: 6.00
    return 0;
}

Também é possível ter funções com vários parâmetros de tipos diferentes, e até mesmo sem parâmetros (usando void na lista).

Uma função pode ter múltiplos return em diferentes ramos, mas apenas um será executado por chamada. Exemplo:

int maior(int a, int b) {
    if (a > b) {
        return a;
    } else {
        return b;
    }
}

Quando a função chega a um return, ela termina imediatamente e o valor é devolvido. Se o tipo de retorno for void, o return pode ser usado sem valor apenas para encerrar a função antecipadamente.

void

O tipo void é usado em duas situações principais: como tipo de retorno de funções que não devolvem valor, e como lista de parâmetros quando a função não recebe nenhum argumento.

Uma função void executa ações, mas não retorna um valor. Por exemplo, uma função que imprime uma mensagem:

void imprimir_mensagem() {
    printf("Olá, mundo!\n");
}

Nesse caso, não há return com valor. Podemos usar return; para sair mais cedo, se necessário.

Quando uma função não recebe parâmetros, escrevemos void na lista de parâmetros para explicitar que não há argumentos. Isso é opcional, mas recomendado, pois em C antigo uma lista vazia podia significar que a função aceitava qualquer número de argumentos.

// Sem parâmetros (recomendado)
void funcao(void) {
    // ...
}

// Também válido, mas menos explícito
void funcao() {
    // ...
}

Além disso, o ponteiro void* é um ponteiro genérico que pode apontar para qualquer tipo de dado, mas isso será visto em aulas sobre ponteiros.

Exemplo completo usando void:

#include <stdio.h>

// Função que não retorna valor
void exibir_dobro(int numero) {
    printf("O dobro de %d é %d\n", numero, numero * 2);
}

// Função que não recebe parâmetros e não retorna valor
void separador(void) {
    printf("---\n");
}

int main() {
    exibir_dobro(5);
    separador();
    exibir_dobro(10);
    return 0;
}

O uso de void também aparece em protótipos para indicar que a função não recebe argumentos, como em int main(void).

Boas práticas e observações finais

Ao trabalhar com funções, siga estas boas práticas:

  • Use nomes descritivos para funções e parâmetros.
  • Sempre declare protótipos para funções definidas após o main ou em outros arquivos.
  • Mantenha funções pequenas e com uma única responsabilidade.
  • Evite variáveis globais; prefira passar dados por parâmetros.
  • Documente funções com comentários, explicando o que fazem, parâmetros e retorno.
  • Use const em parâmetros que não serão modificados, para maior segurança.

Nas próximas aulas, veremos como passar arrays para funções, usar ponteiros e criar funções recursivas.

Exercícios

  1. Escreva uma função chamada quadrado que recebe um inteiro e retorna seu quadrado. Inclua o protótipo antes do main e a definição depois. No main, chame a função e imprima o resultado para o número 7.
  2. ✓ Resposta:
    #include <stdio.h>
    
    // Protótipo
    int quadrado(int x);
    
    int main() {
        int resultado = quadrado(7);
        printf("O quadrado de 7 é %d\n", resultado);
        return 0;
    }
    
    // Definição
    int quadrado(int x) {
        return x * x;
    }
  3. Crie uma função void saudacao(char nome[]) que imprime "Olá, [nome]!" (use %s). No main, chame com seu nome.
  4. ✓ Resposta:
    #include <stdio.h>
    
    void saudacao(char nome[]) {
        printf("Olá, %s!\n", nome);
    }
    
    int main() {
        saudacao("Maria");
        return 0;
    }
  5. Escreva uma função que recebe dois números inteiros e retorna o maior deles. Se forem iguais, retorne qualquer um. Teste com (10, 20) e (5, 5).
  6. ✓ Resposta:
    #include <stdio.h>
    
    int maior(int a, int b) {
        if (a > b) {
            return a;
        } else {
            return b;
        }
    }
    
    int main() {
        printf("%d\n", maior(10, 20)); // 20
        printf("%d\n", maior(5, 5));  // 5
        return 0;
    }
  7. Explique por que precisamos de protótipos em C e forneça um exemplo onde a ausência de protótipo causa erro de compilação.
  8. ✓ Resposta: Protótipos são necessários para que o compilador conheça a assinatura da função antes de sua chamada, permitindo verificar tipos e número de argumentos. Sem protótipo, se a função for definida depois do main e chamada antes, o compilador pode assumir que ela retorna int e não verifica os parâmetros, gerando erros ou avisos. Exemplo que gera erro em C99:
    #include <stdio.h>
    
    int main() {
        printf("%d\n", soma(2, 3)); // erro: soma não declarada
        return 0;
    }
    
    int soma(int a, int b) {
        return a + b;
    }
    Para corrigir, adicione int soma(int a, int b); antes do main.
  9. Escreva uma função void mostra_par(int n) que imprime "par" se n for par e "ímpar" caso contrário. Use return; para sair se n for negativo. Teste com valores -3, 4 e 7.
  10. ✓ Resposta:
    #include <stdio.h>
    
    void mostra_par(int n) {
        if (n < 0) {
            return; // sai da função
        }
        if (n % 2 == 0) {
            printf("par\n");
        } else {
            printf("ímpar\n");
        }
    }
    
    int main() {
        mostra_par(-3); // não imprime nada
        mostra_par(4);  // par
        mostra_par(7);  // ímpar
        return 0;
    }

Referências