este módulo apresenta o uso de funções na linguagem C. Funções são o principal elemento de modularização de código nesta linguagem, são muito versáteis e extensivamente usadas.
Em C, uma função é declarada da seguinte forma geral:
<return type> function_name (<type parameter>, ...) { <function body> return <value> ; }
Um exemplo de função para calcular o fatorial de um número inteiro:
#include <stdio.h> long fatorial (int n) { int i ; long fat ; if (n<=1) // por definição return (1) ; fat = 1 ; for (i=2; i<=n; i++) fat *= i ; return (fat) ; } int main () { printf ("fat(10): %ld\n", fatorial(10)) ; return (0) ; }
Obviamente, a função fatorial
também pode ser definida de forma recursiva:
long fatorial (int n) { if (n<=1) return 1 ; return n * fatorial (n-1) ; }
Ou ainda:
long fatorial (int n) { return (n<=1 ? 1 : n * fatorial (n-1)) ; }
Cabe observar que não é obrigatório usar o valor de retorno de uma função. Por exemplo, o código abaixo é perfeitamente válido (apesar de ser inútil):
fatorial(20) ;
O valor de retorno de uma função pode ser de qualquer tipo suportado pela linguagem C (tipos numéricos, ponteiros, chars, structs, etc), exceto arrays (vetores e matrizes) e funções. Todavia, essa limitação pode ser contornada através do uso de ponteiros para dados desses tipos. Funções que não retornam nenhum valor podem ser declaradas com o tipo void
:
void hello() { printf ("Hello!\n") ; }
Em princípio, toda função em C deve ser declarada antes de ser usada. Caso o compilador encontre uma função sendo usada que não tenha sido previamente declarada, ele pressupõe que essa função retorna um int
e emite um aviso no terminal.
Em algumas situações não é possível respeitar essa regra. Por exemplo, se duas funções diferentes se chamam mutuamente, teremos:
char patati (int a, int b) { ... c = patata (x, y, z) ; // precisa declarar "patata" antes de usar ... } char patata (int a, int b, int c) { ... x = patati (n1, n2) ; ... }
Para evitar problemas, é possível declarar um “protótipo” da função antes de sua definição completa:
// protótipos das funções "patati" e "patata" char patati (int, int) ; char patata (int, int, int) ; // implementação da função "patati" (deve respeitar o protótipo) char patati (int a, int b) { ... c = patata (x, y, z) ; // ok, patata tem um protótipo definido ... } // implementação da função "patata" (deve respeitar o protótipo) char patata (int a, int b, int c) { ... x = patati (n1, n2) ; // ok, patati tem um protótipo definido ... }
Um protótipo de função pode ser visto como a “interface” da função, porque define o nome, o número e tipos dos parâmetros de entrada e o tipo do valor de retorno. Essas informações são suficientes para a compilação de qualquer código que use a função. Por isso, protótipos são muito usados em arquivos de cabeçalho (.h
).
Em C, os parâmetros das funções são transferidos sempre por valor (ou por cópia), pois os valores fornecidos na chamada da função são copiados para dentro da pilha (stack) e ficam à disposição do código interno da função. Em consequência, alterações nos parâmetros efetuadas dentro das funções não têm impacto fora dela.
Por exemplo:
#include <stdio.h> void inc (int n) { n++ ; printf ("n vale %d\n", n) ; } int main () { int a = 0 ; printf ("a vale %d\n", a) ; inc(a) ; printf ("a vale %d\n", a) ; return (0) ; }
A execução deste código resulta em:
a vale 0 n vale 1 a vale 0
Para que as ações de uma função sobre seus parâmetros sejam perceptíveis fora da função, esses parâmetros devem ser informados por referência. C não suporta nativamente a passagem de parâmetros por referência, mas se considerarmos que um ponteiro é uma referência a uma variável, basta usar parâmetros de tipo ponteiro para obter esse efeito:
#include <stdio.h> void inc (int *n) { (*n)++ ; } int main () { int a = 0 ; printf ("a vale %d\n", a) ; inc (&a) ; // informar a referência (endereço) de a printf ("a vale %d\n", a) ; return (0) ; }
E a execução fica:
a vale 0 a vale 1
Deve-se observar que a variável a
é transferida para a função inc
por referência, mas o ponteiro *n
recebe uma cópia do endereço de a
. Ou seja, a transferência de parâmetros continua sendo feita por cópia.
A passagem de vetores e matrizes como parâmetros de funções tem algumas particularidades que devem ser observadas:
Um exemplo simples de chamada de função com parâmetros vetoriais:
#define SIZE 100 void clean_vect (int n, int v[]) // ou "int v[SIZE]" { int i ; for (i = 0 ; i<n; i++) v[i] = 0 ; } int main () { int vector[SIZE] ; int num ; ... clean_vect (num, vector) ; ... }
No caso de matrizes (vetores multidimensionais), deve-se informar ao compilador os tamanhos máximos das várias dimensões, para que ele possa calcular a posição onde cada elemento da matriz foi alocado na memória. Por exemplo:
#define MAXLIN 100 #define MAXCOL 50 void clean_mat (int l, int c, int m[][MAXCOL]) // ou "int m[MAXLIN][MAXCOL]" { int i, j ; for (i = 0 ; i<l; i++) for (j = 0 ; j<c; j++) m[i][j] = 0 ; } int main () { int mat[MAXLIN][MAXCOL] ; int lin, col ; ... clean_mat (lin, col, mat) ; ... }
strlen
, strcpy
e strcat
. Depois, compare o desempenho de sua implementação em relação às funções originais da LibC (sugestão: meça o tempo necessário para ativar cada função um milhão de vezes).