SOP-funcoes

De MediaWiki do Campus São José
Ir para navegação Ir para pesquisar

Linguagem C: funções

Mas afinal para que servem funções ? Para propósitos como:

  1. Facilitar a reutilização de um algoritmo em diferentes partes de um programa: um exemplo são as funções printf e scanf, que fazem parte da biblioteca C padrão.
  2. Reduzir a complexidade de um programa, ao dividi-lo em algoritmos menores e mais simples: a divisão de um programa em pequenos algoritmos especializados o torna mais fácil de ser escrito, de ser lido e entendido, de ser depurado (debug) e mantido, e também de ser modificado.

ola, mundo !

Uma forma simples de entender funções é vê-las como algoritmos, que podem ser usadom em diferentes partes de um programa, e mesmo em diferentes programas. Um primeiro exemplo é mostrado a seguir:

#include <stdio.h>

void ola() {
  printf("Ola, mundo !\n");
}

int main() {
  ola();
}

Ao se executar o programa acima, o resultado é aparecer na tela a mensagem "Ola, mundo !":

> gcc -o ola ola.c
> ./ola
Ola, mundo !

Se o exemplo for modificado da seguinte forma:

#include <stdio.h>

void ola() {
  printf("Ola, mundo !\n");
}

int main() {
  ola();
  ola();
  ola();
}

... sua execução dará como resultado:

> gcc -o ola ola.c
> ./ola
Ola, mundo !
Ola, mundo !
Ola, mundo !

Funções com parâmetros e declaração de funções

Mas e se fosse desejável criar uma função que pudesse mostrar diferentes mensagens na tela ? O exemplo abaixo ilustra uma forma de fazer isto:

#include <stdio.h>

void mostra_mensagem(char * mensagem) {
  printf("Sua mensagem: %s\n", mensagem);
}

int main() {
  mostra_mensagem("Ola, mundo");
  mostra_mensagem("testando ...");
  mostra_mensagem("");
  mostra_mensagem("testando de novo ...");
}

Se o programa acima for executado, dará este resultado:

> gcc -o mostra mostra.c
> ./mostra
Sua mensagem: Ola, mundo
Sua mensagem: testando ...
Sua mensagem:
Sua mensagem: testando de novo ...

A função mostra_mensagem apresenta na tela uma string que é especificada no momento em que essa função é chamada. A isto se chama parametrização: tornar um algoritmo independente de valores específicos de entrada, de forma que possa funcionar com qualquer valor de entrada compatível. O próximo exemplo aprofunda este conceito por meio de uma função que calcula o quadrado de um número:

#include <stdio.h>

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

int main() {
  printf("O quadrado de 2 é %f\n", quadrado(2));
  printf("O quadrado de 3 é %f\n", quadrado(3));
}

Este segundo exemplo é mais elaborado do que os anteriores, pois a função quadrado possui um dado de entrada (o parâmetro int x, também chamado de argumento da função), tem uma variável local (int y, que existe somente dentro da função) e devolve um valor do tipo int como resultado. A declaração da função revela esses detalhes:

Quadrado.png

Esses são os elementos básicos para declarar, escrever e usar funções.

Valor de retorno de função

Os próximos exemplos mostram outras situações em que se usam funções.

Função para testar se um número é par ou ímpar

#include <stdio.h>

int eh_par(int numero) {
  int resto;

  resto = numero % 2;
  if (resto == 0) return 1;
  else return 0;
}

int main() {
  int x;

  printf("Digite um numero: ");
  scanf("%d", &x);
  if (eh_par(x)) {
    printf("%d eh par\n", x);
  } else {
    printf("%d eh impar\n", x);
  }

}

O exemplo acima mostra se um número é par ou ímpar. A função int eh_par(int numero) retorna 1 se o valor contido em numero for par, e 0 caso contrário. Veja como essa função é chamada dentro da função main:

if (eh_par(x)) {

A função é usada diretamente como uma condição do if .. else (equivalente ao se .. então .. senão do Portugol). Isto é possível porque na linguagem C o valor 0 é equivalente ao valor booleano FALSO, e qualquer valor diferente de 0 é VERDADEIRO. Assim, ao retornar 0 ou 1, a função eh_par está tendo um resultado equivalente a FALSO ou VERDADEIRO. Tanto que o programa poderia ser escrito assim, tendo o mesmo resultado final:

int main() {
  int x;

  printf("Digite um numero: ");
  scanf("%d", &x);
  if (! eh_par(x)) {
    printf("%d eh impar\n", x);
  } else {
    printf("%d eh par\n", x);
  }

}

Lembre que o operador ! é equivalente a NÃO lógico (negação de uma condição). Então a condição ! eh_par(x) é verdadeira se x não for par.

Função para testar se um número é primo

Um exemplo semelhante ao anterior trata da verificação se um número é primo:

#include <stdio.h>

int primo(int n) {
  int x, ok;

  x = 2;
  ok = 1;
  while ((x <= (n/2)) && ok) {
    if ((n % x) == 0) {
      ok = 0;
    } else x++;
  }
  return ok;
}

int main() {
  int n;

  printf("Numero: ");
  scanf("%d", &n);
  if (primo(n)) {
    printf("%d eh primo\n", n);
  } else {
    printf("%d nao eh primo\n", n);
  }
}

A função int primo(int n) retorna 1 se o valor passado como parâmetro no argumento int nfor primo, e 0 caso contrário. Essa função possui um algoritmo mais elaborado que os exemplos anteriores, o qual utiliza uma estrutura de repetição com while (condição) (equivalente ao Enquanto condição do Portugol). Inclusive nessa função aparece um operador aritmético novo, usado para incrementar variáveis numéricas:

x++;

Isto é equivalente a:

x = x + 1;

De forma análoga, existe um operador parecido para decremento:

x--;

... equivalente a:

x = x - 1;

Função para fatorar um número

Esse exemplo para teste se números são primos pode ser aproveitado para criar um programa que fatore um número em seus divisores primos:

  1. include <stdio.h>

int primo(int n) {

 int x, ok;
 x = 2;
 ok = 1;
 while ((x <= (n/2)) && ok) {
   if ((n % x) == 0) {
     ok = 0;
   } else x++;
 }
 return ok;

}

int main() {

 int p, x;
 printf("Numero: ");
 scanf("%d", &x);
 p = 1;
 while (x != 1) {
   do {
     p ++;
   } while(!primo(p));
   while ((x % p) == 0) {
     x = x /p;
     printf("%d\n", p);
   }
 }

}

</syntaxhighlight>

A função int primo(int n) foi reaproveitada para escrever esse novo programa, que mostra todos os fatores primos que dividem um número.

Passagem de parâmetros

O próximo exemplo mostra uma função que torna maiúscula a primeira letra de uma string:

#include <stdio.h>

void faz_primeira_letra_maiuscula(char * nome) {
  if ((nome[0] >= 'a') && (nome[0] <= 'z')) {
    nome[0] = nome[0] - 32;
  }
}

int main() {
  char nome[32];

  printf("Digite um nome: ");
  scanf("%s", nome);
  faz_primeira_letra_maiuscula(nome);
  printf("Nome: %s\n", nome);
}

A função void faz_primeira_letra_maiuscula(char * nome) converte a primeira letra do parâmetro char * nome para maiúscula. A conversão se baseia na tabela ASCII, que associa códigos numéricos a caracteres. Mas a novidade nessa função é a possibilidade de modificar um valor passado como parâmetro, o que não acontecia nas funções vistas até então. Dependendo de como foi passado um parâmetro, modificações em seu valor dentro de uma função podem ou não refletir fora dessa função.

    • Passagem por valor: o valor da variável passada como parâmetro é copiado para uma variável local da função. Com isto, qualquer modificação feita na variável local da função não afeta a variável que foi passada como parâmetro. Ex:
      int incrementa_e_mostra(int x) {
        x = x + 1;
        printf("x=%d\n", x);
      }
      
      int main() {
        int contador = 0;
      
        incrementa_e_mostra(contador);
        incrementa_e_mostra(contador);
        incrementa_e_mostra(contador);
        printf("Ao final, contador=%d\n", contador);
      }
      
      Ao executar este programa exemplo, o resultado na tela deve ser o seguinte:
      x=1
      x=1
      x=1
      Ao final, contador=0
      ... o que evidencia que o valor de contador não foi modificado dentro da função incrementa_e_mostra.
    • Passagem por referência: a própria variável é passada como parâmetro (quer dizer, a sua localização em memória, por isto se diz por referência). Assim, qualquer modificação feita dentro da função chamada refletirá na variável que foi passada como parâmetro. Ex:
      int incrementa_e_mostra(int * x) {
        *x = *x + 1;
        printf("x=%d\n", *x);
      }
      
      int main() {
        int contador = 0;
      
        incrementa_e_mostra(&contador);
        incrementa_e_mostra(&contador);
        incrementa_e_mostra(&contador);
        printf("Ao final, contador=%d\n", contador);
      }
      
      Ao se executar o exemplo acima, o resultado na tela deve ser:
      x=1
      x=2
      x=3
      Ao final, contador=3
      Fica evidente que o valor da variável contador foi modificado dentro da função incrementa_e_mostra. Repare que algumas diferenças de sintaxe existem quando se usa passagem por referência:
      • A declaração do parâmetro na função deve ter um * (asterisco) antes do nome do parâmetro (ex: incrementa_e_mostra(int * x) )
      • Dentro da função deve-se preceder com * (asterisco) o nome da variável do parâmetro (ex: *x = *x + 1;)
        • Quando a variável passada como parâmetro for do tipo struct, ao invés de precedê-la por * (asterisco), deve-se usar a seguinte sintaxe: nome_variável->campo ao invés de nome_variável.campo. Veja a função adiciona_pessoa do cadastro como exemplo.
      • Na chamada da função, deve-se preceder com & o nome da variável passada como parâmetro (ex: incrementa_e_mostra(&contador); )
      • As duas últimas regras de sintaxe acima não valem quando se passa uma variável string como parâmetro. Isto porque uma variável string por definição já é uma variável que contém um endereço de memória (quer dizer, ela já funciona como uma referência aos dados). Na verdade, isto vale para qualquer variável do tipo vetor (lembre que a string é um vetor de char). Ex:
        void faz_primeira_letra_maiuscula(char * nome) {
          if ((nome[0] >= 97) && (nome[0] <= 122)) {
            nome[0] = nome[0] - 32;
          }
        }
        
        int main() {
          char nome[32];
        
          printf("Digite um nome: ");
          scanf("%s", nome);
          faz_primeira_letra_maiuscula(nome);
          printf("Nome: %s\n", nome);
        }
        
    • No caso do exemplo do cadastro: para ver a diferença entre passagem de parâmetro por referência e por valor no caso do cadastro, veja esta versão do cadastro em que a função adiciona_pessoa faz passagem por valor.
    • Veja no curso de linguagem C da UFMG uma discussão mais aprofundada sobre passagem de parâmetros e escopo de variáveis.

Reaproveitamento e especialização de funções

Uma função pode ser especializada para fazer uma tarefa mais limitada. Veja o exemplo da inicial maíuscula:

#include <stdio.h>

void faz_letra_maiuscula(char * nome, int pos) {
  if ((nome[pos] >= 97) && (nome[pos] <= 122)) {
    nome[pos] = nome[pos] - 32;
  }
}

void faz_primeira_letra_maiuscula(char * nome) {
  faz_letra_maiuscula(nome, 0);
}

int main() {
  char nome[32];

  printf("Digite um nome: ");
  scanf("%s", nome);
  faz_primeira_letra_maiuscula(nome);
  printf("Nome: %s\n", nome);
}

Apesar de o resultado ser exatamente o mesmo do exemplo anterior, em que havia somente a função faz_primeira_letra_maiuscula, esse novo exemplo mostra como especializar uma função mais geral. A função void faz_letra_maiuscula(char * nome, int posicao) torna maiúscula a letra contida na posição pos da string 'nome. Quer dizer, faz_letra_maiuscula é capaz de tornar maiúscula qualquer letra da string contida em nome. A função void faz_primeira_letra_maiuscula(char * nome) torna maiúscula a letra inicial da string contida em nome, e para fazer isto agora foi usada a função faz_letra_maiuscula, indicando-se que se quer mudar a letra na posição 0.

Funções que usam tipos de dados definidos pelo usuário (ou struct)