9

Funções

Funções9.1

Uma função nada mais é do que um bloco de código, composto por declarações e comandos, que pode ser nomeado e invocado de dentro de um programa. Em termos práticos, trata-se de uma sequência de instruções que recebe um nome identificador e pode ser chamada em qualquer parte do sistema, quantas vezes forem necessárias, durante a sua execução.

A linguagem C já possui inúmeras funções implementadas em suas bibliotecas padrão, as quais você tem utilizado constantemente, como as funções de entrada e saída scanf() e printf(). Como programador, você não precisa conhecer o código interno dessas funções para utilizá-las; basta compreender seu nome, sua finalidade e a forma correta de chamá-las.

Este capítulo apresenta os conceitos e detalhes técnicos fundamentais para que você possa criar suas próprias funções. Ao final deste estudo, você será capaz de:

  • Declarar e estruturar uma nova função.
  • Definir e manipular os parâmetros de entrada.
  • Estabelecer o tipo de retorno de uma função.
  • Compreender e aplicar a passagem de parâmetros por valor.
  • Dominar a passagem de parâmetros por referência.
  • Utilizar corretamente o operador de seta.
  • Implementar o conceito de recursão em suas funções.
Modularização

O uso de funções é a base da programação procedural e modular. Ele permite que você decomponha problemas complexos em módulos menores, mais simples de resolver, testar e manter.

Estrutura e Componentes das Funções9.2

Para que você possa desenvolver programas modulares e eficientes, é essencial compreender os detalhes técnicos da construção de uma função: desde a declaração rigorosa de seus parâmetros até a lógica de processamento em seu corpo e a devolução de resultados.

Parâmetros da Função9.2.1

Diferentemente da declaração de variáveis comuns, onde é possível agrupar várias delas sob um mesmo especificador (como int x, y;), na lista de parâmetros de uma função você deve obrigatoriamente especificar o tipo de cada variável individualmente.

// Declaração CORRETA de parâmetros
int soma(int x, int y){
   return x + y;
}

// Declaração ERRADA de parâmetros
int soma(int x, y){ // O compilador não saberá o tipo de 'y'
   return x + y;
}

Funções sem lista de parâmetros9.2.1.1

Existem situações em que uma função não necessita de dados de entrada para operar. Nesses casos, a linguagem C oferece duas abordagens, que possuem uma diferença técnica sutil, porém importante:

  • Lista vazia: void imprime(). O compilador não especifica parâmetros, permitindo que a função seja chamada com argumentos, embora ela não consiga acessá-los.
  • Uso do void: void imprime(void). Esta é a forma mais rigorosa; o programa acusará um erro caso você tente passar qualquer valor para a função.

Observe a diferença de comportamento no exemplo abaixo:


// Sem void
#include <stdio.h>
#include <stdlib.h>

void imprime(){
   printf("Teste de funcao\n");
}

int main(){
   imprime();       // Correto
   imprime(5);      // Compila (mas ignora o 5)
   imprime(5, 'a'); // Compila (mas ignora)
   system("pause");
   return 0;
}

// Com void
#include <stdio.h>
#include <stdlib.h>

void imprime(void){
   printf("Teste de funcao\n");
}

int main(){
   imprime();       // Correto
   imprime(5);      // ERRO de compilação
   imprime(5, 'a'); // ERRO de compilação
   system("pause");
   return 0;
}
Escopo de Parâmetros

Todo parâmetro de uma função é considerado uma variável local. Isso significa que sua validade e acesso estão restritos exclusivamente ao interior da função na qual foi declarado.

Corpo da Função9.2.2

O corpo de uma função é o local onde a tarefa pretendida é efetivamente definida e executada. Ele é composto por uma sequência de declarações (variáveis, constantes, arrays) e uma sequência de comandos (condicionais, repetições, chamadas de outras funções).

Toda a lógica que você aplicou na função main() até agora pode ser transportada para funções criadas por você. O objetivo é que cada função realize uma tarefa específica e bem definida.

Generalidade e I/O em Funções9.2.2.1

Uma boa prática de programação é manter as funções "genéricas". Isso significa evitar operações de entrada (scanf) e saída (printf) dentro de funções de processamento, deixando que a função que a chamou (geralmente a main) cuide da interação com o usuário.

Compare o cálculo de um fatorial realizado diretamente na main versus em uma função própria:

// Fatorial na main()
#include <stdio.h>
#include <stdlib.h>

int main(){
   printf("Digite um numero inteiro positivo:");
   int x;
   scanf("%d", &x);
   int i, f = 1;
   for (i = 1; i <= x; i++)
       f = f * i;
   printf("O fatorial de %d eh: %d\", x, f);
   system("pause");
   return 0;
}
// Fatorial em função PRÓPRIA
#include <stdio.h>
#include <stdlib.h>

int fatorial(int n){
   int i, f = 1;
   for (i = 1; i <= n; i++)
       f = f * i;
   return f; // Retorna apenas o resultado
}

int main(){
   int x;
   scanf("%d", &x);
   int fat = fatorial(x); // I/O fica na main
   printf("O fatorial de %d eh: %d\n", x, fat);
   system("pause");
   return 0;
}

Embora não seja proibido realizar leituras e escritas dentro de funções, você deve fazê-lo apenas quando esse for o objetivo primordial da função, como em um menu de opções:

#include <stdio.h>
#include <stdlib.h>

int menu(){
   int i;
   do {
       printf("Escolha uma opção:\n");
       printf("(1) Opcao 1\n");
       printf("(2) Opcao 2\n");
       printf("(3) Opcao 3\n");
       scanf("%d", &i);
   } while ((i < 1) || (i > 3));
   return i;
}

int main(){
   int op = menu();
   printf("Vc escolheu a Opcao %d.\n", op);
   system("pause");
   return 0;
}

Retorno da Função9.2.3

O retorno é o mecanismo pelo qual uma função devolve o resultado de seu processamento para quem a invocou. O tipo_retornado estabelecido na declaração pode ser qualquer tipo válido em C, desde tipos básicos (int, char, float, void, ponteiros) até tipos definidos pelo programador (struct).

Funções sem retorno (void)9.2.3.1

Quando uma função deve apenas executar uma ação (como imprimir algo) sem devolver um valor, utiliza-se o tipo void (tipo vazio).

void imprime(int n){
   int i;
   for (i = 1; i <= n; i++)
       printf("Linha %d \n", i);
}

O comando return9.2.3.2

Se a função não for void, ela deve obrigatoriamente utilizar o comando return seguido de uma expressão compatível com o tipo de retorno declarado.

#include <stdio.h>
#include <stdlib.h>

int soma(int x, int y){
   return x + y; // Devolve a soma
}

int main(){
   int a = 10, b = 20;
   // A chamada pode ser usada diretamente em outras funções
   printf("Soma = %d\n", soma(a, b)); 
   system("pause");
   return 0;
}
Encerramento Imediato

Assim que o programa atinge um comando return, a função é encerrada instantaneamente, ignorando qualquer código que venha abaixo. Você pode usar vários return em uma função (em condicionais, por exemplo), mas é recomendado usar apenas um para manter a clareza do código.


// Vários comandos return
int maior(int x, int y){
   if(x > y)
       return x;
   else
       return y;
   printf("Jamais serei lido"); // Inalcançável
}

// Único return (Recomendado)
int maior(int x, int y){
   int z;
   if(x > y) z = x;
   else z = y;
   return z;
}

Limitações e Estruturas9.2.3.3

A linguagem C não permite que uma função retorne um array diretamente, pois ela não suporta atribuição direta entre arrays. Contudo, você pode contornar essa limitação inserindo o array dentro de uma struct, já que estruturas permitem atribuição e cópia de conteúdo.

struct vetor {
   int v[5];
};

struct vetor retorna_array(){
   struct vetor aux = {1, 2, 3, 4, 5};
   return aux; // Retorna a estrutura contendo o array
}

Passagem de Parâmetros Avançada9.3

Para desenvolver funções robustas e eficientes em C, você deve dominar como o computador manipula diferentes tipos de dados durante a execução. A escolha entre passar uma cópia do valor ou uma referência direta à memória altera o desempenho e o comportamento do seu software.

Passagem por Valor vs. Referência9.3.1

Na passagem por valor, uma cópia da variável é enviada; alterações dentro da função não afetam o original. Na passagem por referência, você envia o endereço de memória, permitindo que a função modifique a variável original fora de seu escopo.

No exemplo a seguir, a função Troca tenta permutar dois valores. Observe como apenas a versão por referência obtém sucesso em alterar as variáveis x e y da função main():

// Por valor
#include <stdio.h>
#include <stdlib.h>

void Troca(int a, int b){
   int temp;
   temp = a;
   a = b;
   b = temp;
   printf("Dentro: %d e %d\n", a, b);
}

int main(){
   int x = 2, y = 3;
   printf("Antes: %d e %d\n", x, y);
   Troca(x, y);
   printf("Depois: %d e %d\n", x, y);
   system("pause");
   return 0;
}
// Saída: Antes 2,3 | Dentro 3,2 | Depois 2,3
// Por referência
#include <stdio.h>
#include <stdlib.h>

void Troca(int *a, int *b){
   int temp;
   temp = *a;
   *a = *b;
   *b = temp;
   printf("Dentro: %d e %d\n", *a, *b);
}

int main(){
   int x = 2, y = 3;
   printf("Antes: %d e %d\n", x, y);
   Troca(&x, &y);
   printf("Depois: %d e %d\n", x, y);
   system("pause");
   return 0;
}
// Saída: Antes 2,3 | Dentro 3,2 | Depois 3,2

Passagem de Arrays como Parâmetros9.3.2

Diferente de variáveis simples, os arrays são sempre passados por referência para uma função. Isso ocorre para evitar a cópia custosa de grandes volumes de dados, o que prejudicaria o desempenho do programa.

Quando você passa um array, o que a função recebe é o endereço do seu primeiro elemento. Por convenção, você deve sempre passar um segundo parâmetro indicando o tamanho do array, já que a função não consegue deduzi-lo sozinha.

#include <stdio.h>
#include <stdlib.h>

// n é um ponteiro para o início do array
void imprime (int *n, int m){
   int i;
   for (i = 0; i < m; i++)
       printf("%d \n", n[i]); // Acesso via colchetes continua válido
}

int main(){
   int v[5] = {1, 2, 3, 4, 5};
   imprime(v, 5); // Passa apenas o nome do array
   system("pause");
   return 0;
}
Notação de Arrays

Na chamada da função, você deve passar apenas o nome do array, sem colchetes, sem índices e sem o operador &. O nome do array, por definição, já representa seu endereço na memória.

Arrays Multidimensionais (Matrizes)9.3.2.1

Para matrizes, você deve obrigatoriamente especificar o tamanho de todas as dimensões, exceto a primeira. Isso é necessário para que o compilador saiba como calcular o "salto" de memória entre as linhas, tratando a matriz como um "array de arrays".

// É necessário informar o número de colunas [2]
void imprime_matriz(int m[][2], int n){
   int i, j;
   for (i = 0; i < n; i++)
       for (j = 0; j < 2; j++)
           printf("%d \n", m[i][j]);
}

Questões9.4

1. Escreva um programa que contenha uma função chamada imprimir_linha. Essa função não deve receber argumentos e não deve retornar valor (void), apenas imprimir uma linha de 20 hífens ("--------------------") na tela. No main, chame essa função três vezes.

  • Entrada: Nenhuma.
  • Saída Esperada: Três linhas de hífens impressas.

2. Crie uma função chamada dobro que receba um número inteiro como parâmetro e retorne o dobro desse número. No main, peça um número ao usuário, chame a função e imprima o resultado retornado.

  • Entrada: 5.
  • Saída Esperada: O dobro eh: 10.

3. Desenvolva uma função chamada verificar_sinal que receba um número inteiro. A função deve retornar:

  • 1 se o número for positivo.
  • -1 se o número for negativo.
  • 0 se o número for zero. No main, leia o número, chame a função e imprima a mensagem de texto correspondente ao código retornado.
  • Entrada: -50.
  • Saída Esperada: Numero negativo.

4. Escreva uma função chamada celsius_para_fahrenheit que receba uma temperatura em graus Celsius (float) e retorne a temperatura convertida para Fahrenheit. A fórmula é $F = C \times 1.8 + 32$.

  • Entrada: 0.
  • Saída Esperada: 32.0.

5. Crie uma função chamada maior_valor que receba dois números inteiros como parâmetros e retorne o maior entre eles. Se forem iguais, retorne qualquer um deles. Teste a função no main com valores digitados pelo usuário.

  • Entrada: 10, 25.
  • Saída Esperada: O maior valor eh: 25.

6. Desenvolva uma função chamada eh_par que receba um número inteiro e retorne 1 (verdadeiro) se o número for par e 0 (falso) se for ímpar. Utilize essa função dentro de um laço no main para verificar 5 números digitados pelo usuário.

  • Entrada: 4.
  • Saída Esperada: Par.

7. (Nível Intermediário - Arrays) Escreva uma função chamada media_vetor. Essa função deve receber dois parâmetros: um vetor de inteiros e um inteiro representando o tamanho desse vetor. A função deve calcular e retornar a média aritmética dos elementos.

  • Entrada no main: Vetor {10, 20, 30}, Tamanho 3.
  • Saída Esperada: 20.0.

8. Crie uma função chamada potencia que receba a base ($x$) e o expoente ($y$) como inteiros. A função deve calcular e retornar $x^y$ sem utilizar a biblioteca math.h, ou seja, usando um laço de repetição para multiplicar a base.

  • Entrada: Base: 2, Expoente: 3.
  • Saída Esperada: 8.

9. Crie uma "Calculadora Modular". O programa deve ter funções separadas para: somar, subtrair, multiplicar e dividir. No main, crie um menu (usando switch ou if) onde o usuário escolhe a operação e insere dois números. O programa deve chamar a função correspondente e exibir o resultado.

  • Entrada: Opção: +, Num1: 10, Num2: 2.
  • Saída Esperada: Resultado: 12.

10. Crie uma função eh_primo que receba um inteiro e retorne 1 se for primo e 0 caso contrário. No main, utilize essa função para imprimir todos os números primos existentes entre 1 e 100.

  • Entrada: Nenhuma.
  • Saída Esperada: 2 3 5 7 11 ... 97.

Próximos passos9.5

Conteúdo em desenvolvimento!

Este conteúdo ainda não foi finalizado. Assim que estiver completo, este aviso será atualizado com o link correspondente.