As funções correspondem a abstrações para trechos de programas. Podemos pegar
um trecho de programa e dar um nome significativo para ele. No ponto que
o programador deseja executar aquele trecho de programa ele invoca ou chama
aquele trecho. Veremos abaixo alguns dos conceitos envolvidos. A possibilidade de
poder abstrair de como implementar um trecho de programa e poder dar um nome a
este trecho é um mecanismo muito importante. Todas as linguagens de alto-nível
provêm alguma forma de abstração que segue essa descrição. Esta abstração se
apresenta com vários nomes: procedimento (procedure), subrotina (subroutine),
função (function), método (method), etc.
Os programas desta
disciplina, vistos até este ponto, apresentavam todo seu código fonte dentro de
uma unidade da Linguagem C com o nome de main (algo parecido com):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]){
/* codigo fonte */
}
Esta unidade é denominada função.
A função main() é uma função especial pois é a função que funciona como
ponto de entrada do fluxo de controle nos programas C. Quando solicitamos ao
sistema operacional a execução de um programa C, o fluxo de controle será
passado ao primeiro comando da função main() (como será visto à frente
existem mais aspectos a serem considerados).
Antes de podermos chamar ou
invocar uma função é necessário que seja feita a declaração da função.
Em algum momento antes da execução de um programa todas as funções declaradas
devem também estar definidas A
definição de uma função envolve vários conceitos que serão ilustrados de forma
incremental. Vemos abaixo uma função bastante simples (não define nenhum
parâmetro, não tem variáveis locais e não retorna nenhum valor):
#include <stdio.h> //1
#include <stdlib.h> //2
imprime(){ //3
printf("uma funcao
simples!\n"); //4
} //5
int main(int argc, char *argv[]) //6
{ imprime(); //7
system("PAUSE"); //8
return 0; //9
} //10
No programa acima definimos
uma função imprime(). Esta função não retorna valor. Uma função pode
retornar ou não um valor. Quando a função não retorna nenhum valor, como no
caso acima, podemos utilizar a palavra chave void no cabeçalho da
definição. Uma função pode retornar um valor do tipo int, double, ou um
ponteiro ou não retornar nenhum valor e neste caso podemos utilizar a palavra
chave void. A ordem de definição das funções não é importante em C.
Podemos reescrever o programa acima definindo a função imprime() depois
da função main().
Em tempo de execução, ao
ser chamada ou invocada a função imprime(), o fluxo de controle desvia
para o primeiro comando da função. Quando é executado o último comando de uma
função, o fluxo de controle volta para o ponto onde a função foi chamada. O
sistema de execução prepara uma estrutura denominada registro de ativação
ou quadro de ativação (activation frame) para controlar as
atividades de chamada, execução e retorno de uma função.
Na compilação do programa
acima, o compilador gera código para uma chamada de função correspondente à
linha 7 e gera código para a função correspondente às linhas 3, 4 e 5. Além
disso gera código para que seja construído, de forma adequada, um registro de
ativação, quando a função imprime() for invocado, ou seja, o código
correspondente a linha 7, entre outras coisas, salva no registro de ativação o
endereço de retorno. O endereço de retorno é o endereço para onde desvia o
fluxo de controle quando a função termina (no caso linha 8).
Na execução do programa
acima o fluxo de controle está inicialmente com o Runtime C e é criado um registro de ativação para
invocar a função main(). Após isso é feito o desvio para o primeiro
comando da função main(), linha 7. O código correspondente à linha 7 é
uma invocação da função imprime(). É criado um registro de ativação
com conteúdo adequado e é feito o desvio do fluxo de execução para o primeiro
comando da função imprime(), isto é, linha 4. Após a execução da linha 4 o
fluxo de controle executa o código que consulta o registro de ativação e
retorna o fluxo de controle para a linha 8. A linha 9 corresponde a um código
que retorna o fluxo de controle para o Runtime C e termina a execução do
programa. A seqüência de execução (simplificada) do programa acima é: O Runtime
C invoca a função main(). O primeiro comando da função main(), linha 7,
cria um registro de ativação que, dentre outras informações, guarda o
endereço de retorno. O código relativo à invocação da função imprime() desvia o
fluxo de controle para o código correspondente à linha 4. A linha 4 corresponde
a uma invocação da função printf(). Neste ponto é criado um registro
de ativação (o terceiro!!) para a execução da função printf(). O
controle é desviado para o código da função printf(). Quando termina a execução
da printf() o controle retorna para o código correspondente à linha 5,
destruindo o registro de ativação criado para a invocação da printf(). O
código relativo à linha 5 consiste em recuperar o endereço de retorno contido
no registro de ativação correspondente à invocação da função imprime(). O
endereço de retorno corresponde ao código da linha 8 e o código correspondente
a linha 9
Uma função pode retornar um
valor (este nome “função” sugere que deveria retornar sempre!). Para isso
devemos colocar no cabeçalho de definição da função, o tipo do valor que ela
irá retornar. Neste caso a execuçao
(não é o texto!) da função usualmente irá terminar quando for executado um
comando da forma:
return
<expressão>;
A expressão especificada no comando de retornar (return) deve ser de
tipo compatível na definição do cabeçalho da função. Quando o fluxo de controle
atinge um return é obtido o endereço de retorno do registro de ativação
corrente, este registro de ativação corrente é destruido e o fluxo de controle
retorna para o comando seguinte à invocação onde é tornado disponível o valor
retornado. Quando o tipo da função é void a função deve usar somente o comando
return sem a expressão.
Uma forma um pouco mais
completa de escrever o programa acima é:
#include <stdio.h>
#include <stdlib.h>
void imprime(){
printf("uma funcao
simples!\n");
return;
}
int main(int argc, char *argv[])
{ imprime();
system("PAUSE");
return 0;
}
Quando a função (i)não tem parâmetros (é definida com abre parênteses seguido
de fecha parênteses), (ii)não retorna um valor (é definida como void) e não tem
variáveis locais (não parecem declarações da forma int x; ou char c;), o registro de ativação serve
basicamente para guardar o endereço de retorno do fluxo de controle. A seguir
vamos introduzir novos elementos na definição de funções. Observe a nova função
imprime do programa abaixo:
#include <stdio.h>
#include <stdlib.h>
void imprime(char * m){
printf(“%s”, m);
return;
}
int main(int argc, char *argv[])
{ imprime("uma funcao simples!\n");
system("PAUSE");
return 0;
}
A nova definição de
imprime() possui agora um parâmetro do tipo ponteiro para caractere. Esta
função, para ser invocada adequadamente deve prover um parâmetro. A seqüência
do fluxo de execução é similar ao programa anterior, a principal diferença é
que o código correspondente à invocação da função imprime(), antes de fazer a
invocação irá atribuir o endereço da
cadeia de caracteres "uma funcao simples!\n" para o parâmetro m. Os
parâmetros são elementos similares a variáveis e ficam disponíveis no registro
de ativação de uma função. O programador de uma função pode usar os
parâmetros da mesma forma que podem usar uma variável declarada localmente à
função. No caso acima o valor do parâmetro é passado para a invocação da função
printf().
As variáveis definidas
dentro de uma função, da mesma forma que os parâmetros, têm seu espaço definido
dentro do registro de ativação e portanto todas as variáveis definidas dentro
de uma função (juntamente com os parâmetros) serão destruídas quando for
destruido o registro de ativação correspondente. As variáveis locais e os
parâmetros só podem ser utilizados na função onde foram definidos. Este tipo de
variável local às funções são conhecidas como variáveis “automáticas”
(“automatic variables”). Observe portanto que as variáveis locais ou
automáticas têm um ciclo de vida amarrado ao ciclo de vida dos registros de
ativação da função correspondente. A linguagem C prove um mecanismo de definição
de variáveis externas às funções. Estas variáveis podem ser utilizadas por
todas as funções. A idéia é que existe um região de memória comum a todas as
funções. As variáveis definidas fora de uma função têm um ciclo de vida
amarrado ao ciclo de vida do programa, ou seja, enquanto o programa estiver em
execução elas estão disponíveis. Mais à frente mostraremos como definir
variáveis “externas” às funções.
A definição das variáveis
locais de uma função podem ser modificadas pela palavra reservada static. Conforme já discutido as variáveis
locais são criadas e destruídas juntamente com os registros de ativação. Mas se
quisermos preservar as variáveis entre invocações das chamadas devemos
defini-las com o modificador static. Compare o funcionamento das duas funções
abaixo:
#include <stdio.h>
#include <stdlib.h>
int f1(){ int i=0; return ++i;}
int f2(){ static int i=0; return ++i;}
int main(int argc, char *argv[])
{
int i;
for(i=0; i<5; i++)
printf("%d\n",f1());
for(i=0; i<5; i++)
printf("%d\n",f2());
system("PAUSE");
return 0;
}
No caso da função f1 a cada chamada é criada a variável i e
inicializada com o valor 0 e o valor retornado é sempre 1. No caso da função f2
a definição da variável local i é modificada por static e a cada chamada a
função retorna um valor diferente.
A palavra reservada static pode também modificar a definição
de uma função. Considere o trecho de programa abaixo:
static int f1(){<corpo>}
int f2(){<corpo>}
A definição da função f2
está disponível para qualquer outro arquivo que for ligado-editado com o
arquivo que contém o trecho acima. Mas a definição da função f2 está disponível
apenas no arquivo do trecho. Uma outra maneira de dizer isso é que a linguagem
C exporta os nomes de função de maneira “default”, se quisermos impedir a
exportação de uma função temos que modificá-la com static. As variáveis globais modificadas
por static também só ficam disponíveis naquele arquivo, e não podem ser
utilizadas em outros arquivos.
A função abaixo mostra um
exemplo de passagem de parâmetros um pouco mais geral. Considere a
definição:
void f(int p1, double p2, int *p3, int p4, char *p5){
printf("int %d\n",p1);
printf("double %f\n",p2);
printf("arranjo de inteiros:\n");
int i;
for(i=0; i<p4; i++) printf(“%d “,p3[i]);
printf(“\n”);
printf(“%s\n”,p5);
}
Considere a invocação:
int a[]={1,2,3};
f(123, 12.34,a,3, "ola!");
Antes do controle desviar
para o primeiro comando da função f, será feita a avaliação das expressões
correspondentes a cada parâmetro.[É usual que o termo “argumento” seja usado
para uma expressão na invocação e que o termo “parâmetro” seja usado para o
nome correspondente na definição, outros termos são parâmetro real/parâmetro
formal, ou ainda argumento real/argumento formal.] Após ser feita a avaliação
das expressões (dos argumentos) será feita a atribuição dos valores resultantes
para cada parâmetro. Os parâmetros ficarão disponíveis no registro de ativação
que é construído para a invocação.
A linguagem C tem dois
estilos de função: estilo-antigo e estilo-novo. As funções do estilo novo devem
ter o tipo dos parâmetros explícitos e isto faz parte do tipo da função. As
funções do estilo antigo não precisam explicitar o tipo dos parâmetros. Os dois
trechos de programa abaixo ilustram os dois estilos: estilo original, estilo
ANSI C:
double f(a, b){ int a; double b; <resto do corpo de f>} //original
double f(int a, double
b){<corpo de f>} //ANSI C
No estilo novo de funções
(ANSI C) o número e o tipo dos parâmetros na definição devem sempre ser compatíveis
com o número e o tipo das expressões na invocação. Os parâmetros na definição
da função são conhecidos como parâmetros formais, os valores ou
expressões dos parâmetros na invocação da função são conhecidos como parâmetros
reais. A invocação de uma função faz com que os valores dos parâmetros
reais sejam atribuídos ao parâmetros
formais. Cada invocação de uma função dá origem a um registro de ativação
contendo os parâmetros formais inicializados com os valores obtidos na avaliação
das expressões correspondentes aos parâmetros reais. Se fizermos uma atribuição
a um parâmetro formal esta atribuição, no caso da linguagem C, não afeta o
parâmetro real correspondente:
#include <stdio.h>
#include <stdlib.h>
void f(int pf){
printf("f recebeu
valor:%d\n" ,pf);
pf=5;
printf("f atribuiu %d ao
parametro\n", pf);
}
int main(int argc, char *argv[])
{
int pr=3;
printf("parametro real antes
da chamada:%d\n",pr);
f(pr);
printf("parametro real apos
chamada:%d\n",pr);
system("PAUSE");
return 0;
}
O programa acima imprime:
parametro real antes da chamada:3
f recebeu valor:3
f atribuiu 5 ao parametro
parametro real apos chamada:3
O modelo de passagem de
parâmetro da linguagem C é denominado passagem de parâmetro por valor.
Existem outros mecanismos de amarramento (binding) de parâmetros em
outras linguagens. Os parâmetros e as variáveis declaradas dentro de uma função
só estão disponíveis no escopo desta função. O registro de ativação contém os
parâmetros e irá conter todas as variáveis locais que forem definidas, quando o
registro de ativação é destruído as variáveis locais e parâmetros deixam de
existir. Se quisermos que uma função f1 altere uma variável em f2 a chamada de
f1 em f2 terá que passar o endereço da variável (ponteiro!) e a definição de f1
tem que levar isto em conta:
#include <stdio.h>
#include <stdlib.h>
void f(int *pi){
printf("f recebeu
valor:%d\n" ,*pi);
*pi=5;
printf("f atribuiu %d ao
parametro\n", *pi);
}
int main(int argc, char *argv[])
{
int pr=3;
printf("parametro real antes
da chamada:%d\n",pr);
f(&pr);
printf("parametro real apos
chamada:%d\n",pr);
system("PAUSE");
return 0;
}
Uma função pode retornar um
valor. Para isso devemos colocar no cabeçalho de definição, ao invés da palavra
chave void, o tipo do valor que a função irá retornar. Neste caso a execuçao (não é o texto!) da função
usualmente irá terminar quando da execução de um comando da forma:
return
<expressão>;
A expressão especificada no comando de retornar (return) deve ser de
tipo compatível na definição do cabeçalho da função. A função abaixo recebe
como parâmetro um caractere hexadecimal e retorna um valor inteiro
correspondente ao caractere:
int charHex2int(char c){
switch(c){
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
return c-'0';
case 'A': case 'B': case 'C': case 'D': case 'E':
case 'F':
return c-'A'+10;
case 'a': case 'b': case 'c': case 'd': case 'e':
case 'f':
return c-'a'+10;
default: return -1;
}
}
Observe que as expressões nos comandos de retorno são de tipo int. No
local onde existe a <chamada da função>, após a execução da função, passa
a ficar disponível o valor retornado.
Observe as funções f1 e f2
abaixo:
void f1(){return;}
void f2(){}
No caso de f1 o retorno é
explícito; no caso de f2 o retorno ocorre pelo que é denominado “drop off”( o
fluxo de controle saiu do bloco!), podemos considerar que o compilador coloca
uma instrução de return ao final do bloco da função f2.
Uma função pode ter
variáveis locais. As variáveis declaradas dentro de uma função são de acesso
exclusivo daquela função. Na linguagem C não podemos definir uma função dentro
de outra função (não existem definições aninhadas de funções). A definição de
uma função define um espaço de nomes. Uma função só pode acessar dados de outra
função se esta outra função passar um ponteiro para seus dados.
Além dos (i)dados locais,
(ii) dos dados de outra função manipulados através de ponteiros uma função pode
acessar variáveis globais. A estrutura de um programa C é formada não só pelo
conjunto das funções mas também por um conjunto de variáveis definidas externamente
às funções.
considere um arquivo arq.c
contendo o seguinte trecho de programa C:
int vg=3;
se este arquivo for
compilado e editado-ligado a este main.c:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]){
extern int
vg;
int vl=5;
printf("%d
%d\n",vg, vl);
system("PAUSE");
return 0;
}
O resultado é um programa
onde a função main além de ter acesso à variável “local” vl tem acesso a
uma variável global vg. Observe o uso da palavra reservada extern como modificador da declaração de
vg. A variável vg é definida no arquivo arq.c e declarada na
função main do arquivo main.c conforme pode ser visto acima. Observe que a
semântica da linguagem C é a disponibilização ou exportação por “default”. Se
quisermos limitar o escopo de uma variável global ao arquivo onde ela foi
definida temos que utilizar o modificador static.
A linguagem C permite a
declaração de uma função em um local ou arquivo e a definição da função em
outro local ou arquivo. Considere o trecho de programa abaixo:
int f1();
int f2(){ <corpo de f2>}
A função f1() foi declarada
mas não foi definida, a função f2() foi definida (e considerada declarada!). A
declaração pode ser no estilo original ou no estilo ANSI C:
int f(); //original
int f(int p1, int
p2); ANSI C
Uma função pode invocar a
si mesma! Quando uma função invoca a si mesma em sua definição dizemos que ela
é uma função recursiva. O programa
abaixo mostra uma função para calcular o fatorial de um número em uma versão
recursiva e outra não recursiva:
#include <stdio.h>
#include <stdlib.h>
int fatNaoRecursivo(int n){
if(n<2) return 1;
int f=1;
int i;
for(i=2; i<=n; i++) f*=i;
return f;
}
int fatRecursivo(int n){
if(n<2) return 1;
return fatRecursivo(n-1)*n;
}
int main(int argc, char *argv[]){
printf("%d\n",fatNaoRecursivo(5));
printf("%d\n",fatRecursivo(5));
system("PAUSE");
return 0;
}
No programa acima vemos que
a versão não recursiva que implementa o cálculo do fatorial utiliza um comando
for para iterar as multiplicações necessárias. Para podermos entender a
versão recursiva da funçao temos que lembrar que existe um registro de
ativação para cada invocação da função. A tabela abaixo mostra algumas caracteristicas
da correlação entre registros de ativação e o parâmetro n:
|
n |
No. de registros |
valor |
|
0 |
1 |
1 |
|
1 |
1 |
1 |
|
2 |
2 |
2 |
|
3 |
3 |
6 |
Como regra geral é sempre preferível
utilizar a versão não recursiva pois as versões recursivas são, em geral, muito
pouco eficientes. Considere a série de Fibonacci: 0,1, 1, 2, 3, 5, 8, 13, 21,
34, ... Esta série inicia-se com o elemento de ordem 0 igual a 0 e o elemento
de ordem 1 igual a 1, o elemento de ordem n é igual a soma dos elementos de
ordem n-1 e ordem n-2. O programa abaixo mostra uma solução recursiva para
calcular o termo de ordem i:
#include <stdio.h>
#include <stdlib.h>
int fib(int n){
if(n<=0) return 0;
if(n==1) return 1;
return fib(n-1)+fib(n-2);
}
int main(int argc, char *argv[]){
int n=4;
printf("o valor do termo de
ordem %d"
" da serie de Fibonacci
e' %d\n",n,fib(n));
system("PAUSE");
return 0;
A tabela abaixo mostra a
correlação entre o valor de n e o número de registros de ativação de fib():
|
n |
Número de Registros |
valor retornado |
|
0 |
1 |
0 |
|
1 |
1 |
1 |
|
2 |
3 |
1 |
|
3 |
5 |
2 |
|
4 |
9 |
3 |
Ponteiros
para Funções
A linguagem C permite a definição
de variáveis do tipo “ponteiro para função”. A metáfora neste caso é que uma
variável deste tipo quando usada de “certa”maneira pode receber o “endereço”
[do código?] da função e pode “invocar” a execução do código da função. Observe
o programa abaixo:
#include <stdio.h>
#include <stdlib.h>
void f(){ printf("Eu sou a funcao f\n");}
void g(){ printf(“Eu sou a funcao g\n”);}
int main(int argc, char *argv[]){
void (*pf)();
pf=f; /*sem acucar sintático do
& */
(*pf)(); /*invoca f utilizando a variavel
pf*/
pf=g;
(*pf)(); /*invoca g utilizando a
variavel pf */
system("PAUSE");
return 0;
}
Os projetistas de C
decidiram que não podemos aplicar o operador & sobre o nome da função! ao
atribuir o nome da função ao ponteiro estamos atribuindo algum tipo de endereço
a ele (inicio do código?). a invocação da função é feita “derreferenciando” o
ponteiro (operador de indireção *) aplicando os argumentos (abre e fecha
parênteses). No exemplo acima o ponteiro pf primeiramente aponta para a função f
e depois passa a apontar para a função g.
Tipos
de variáveis na linguagem C
No material sobre variáveis
alertamos que existem vários tipos de variáveis. Em particular alertamos que as
variáveis podem ser definidas dentro ou fora das funções e que as variáveis
podem ser definidas com ou sem a palavra chave static. No material sobre variáveis
alertamos também que as variáveis definidas dentro de funções sem o modificador
static (variáveis automáticas) têm o conteúdo indefinido (lixo de memória).
A linguagem C permite a
definição de variáveis ligadas ao bloco da função ou a outros blocos aninhados
a este. No trecho de programa abaixo:
if(n>0){
int i;
for(i=0; i<n; i==) ...
o escopo da variável i é o
bloco “verdadeiro” do “se”, e pode haver uma variável i externa a este bloco!
Uma variável automática declarada e inicializada em um bloco é inicializada
cada vez que o fluxo de controle atinge este bloco. Uma variável estática é
inicializada somente na primeira vez que o fluxo de controle chega até o bloco
correspondente.
As variáveis definidas com
o modificador static (variáveis static não são destruídas e criadas a cada
invocação da função onde foram definidas) são garantidas de serem inicializadas
com zero. No programa abaixo a impressão de vne corresponde a qualquer valor e
ve corresponde a um zero:
int vne;
static int ve;
printf("%d %d\n", ve,
vne);
O trecho de programa acima
deve ser condenado pois faz uso de conteúdo de variável que não foi
inicializado ou atribuido. Na maioria das organizações de desenvolvimento de
software e a boa prática de programação recomendam que não se deve confiar na
inicialização implícita!
Para variáveis externas
(variáveis declaradas com extern) e variáveis estáticas, a inicialização só
pode ser feita utilizando expressões constantes. O seguinte trecho de programa
não compila:
int i=2;
static int j=i;
Por outro lado, no caso de
variáveis automáticas podemos utilizar expressões envolvendo valores
previamente definidos, até mesmo chamadas de funções:
int f(int x){
int y=0;
int z=x-1;
Conforme foi discutido no
material de arranjos uma “variável” do tipo arranjo não é exatamente uma
variável em termos da possibilidade de podermos manipula-la como um todo. Vamos
esclarecer, considere o trecho:
int i=8;
int j;
j=i;
Tentando fazer algo similar
a isso com arranjos iremos entender porque uma variável do tipo arranjo não é
exatamente uma variável:
int ai[2]={100,200};
int aj[2];
aj=ai;
O trecho de programa acima
não é correto porque a Linguagem C não prevê a manipulação de um arranjo como
um todo. Por outro lado podemos manipular as estruturas como um todo:
struct s{int a[2]; int j;} sa={{100,200}, 300};
struc s sb;
sb=sa;
Todo o conteúdo de sa (em
particular o conteúdo do arranjo!) é copiado de sa para sb.
A semântica de atribuição
entre parâmetros formais e parâmetros reais na invocação de funções tem
portanto um problema: Como lidar com a passagem de arranjos como parâmetro?
(Todos os tipos de variáveis podem ser alvo de atribuição exceto os arranjos!).
Os projetistas decidiram manter as restrições de atribuição entre arranjos (não
pode haver atribuição de arranjos mesmo no contexto de funções) portanto
funções não retornam arranjos (uma função pode retornar um apontador para
arranjo). Com relação aos parâmetros os projetistas da linguagem C permitiram o
“açucar sintático” de arranjos:
int *f(int a[]){...}
A função f acima
aparentemente recebe um “arranjo” como parâmetro, mas a é um ponteiro que irá
funcionar como determinado pelos projetistas com relação à equivalência entre
indireção e indexação:
<exp1>[<exp2>] equivale a *(<exp1> + <exp2>).
Esta sintaxe pode ser enganadora e pode induzir alguém a acreditar que um
arranjo é uma variável semelhantemente às variáveis do tipo apontador. Além
disso podem acontecer fatos “estranhos”:
int g(int a[]){return a[-1];}
...
int a[2]={100,200};
printf("%d\n",
g(&a[1]));
O trecho acima imprime o
valor 100, utilizando indexação com valor negativo!
No material sobre arranjos
ainda não haviamos discutido as funções. Os projetistas da linguagem C não
permitem um arranjo onde os elementos são do tipo função (tente imaginar o que
seria isso!), mas é permitido um arranjo de ponteiros para funções. A sintaxe
sem o uso do “typedef” é bem convoluta, mas com o uso de “typedef” fica
razoavelmente legível:
typedef double (*PonteiroParaFuncao)(double);
...
PonteiroParaFuncao apf[10];
apf[0]=&funcao;
printf("%f\n", (*apf[0])(2.1));
Na linguagem C a indireção
no uso de apf para invocar as funções é opcional, portanto funciona também:
printf("%f\n", (apf[0])(2.1));
Listas
de parâmetros com tamanho variável (Variable-length Argument Lists)
A linguagem C oferece
mecanismos para definir uma função que pode ser chamada com 1, 2, ou mais
argumentos. O cabeçalho da função deve ser definido da forma (observe que deve
haver 1(um) ou mais parâmetros com nome e a lista termina com a notação de
reticência [três pontos])
<tipo> <nome> ( <parametro com nome 1>, <parametro
com nome 2> ...)
O arquivo de cabeçalho
padrão <stdarg.h> contem um conjunto de definições de macros
para lidar com os argumentos. O tipo va_list pode ser usado para definir uma
espécie de “ponteiro de argumentos”. A macro va_start inicializa um “ponteiro de
argumentos” para apontar para um primeiro argumento (A linguagem C exige que o
primeiro argumento tenha nome, somente os “outros” argumentos podem ser
“anônimos”!). =A cada “chamada” va_arg retorna um argumento e incrementa o “apontador
de argumentos”. A macro va_end deve ser chamada antes do retorno e faz a
faxina que for necessária. O programa abaixo define uma função que retorna a
soma de seus argumentos. O primeiro argumento diz quantos argumentos devem ser
considerados:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
int soma(int na, ...){
va_list pa;
int i,k, acum=0;
va_start(pa, na);
for(i=0; i<na; i++){
k=va_arg(pa, int);
acum+=k;
}
va_end(pa);
return acum;
}
int main(int argc, char *argv[]){
printf("%d\n",soma(0));
printf("%d\n",soma(1,10));
printf("%d\n",soma(2,10,20));
printf("%d\n",soma(3,
10,20,30));
system("PAUSE");
return 0;
}
Exercícios:
int indMax(int *a, int tamanho)
double somatorioDouble(double *ad, int tamanho)
int iguais(int *a, int tamanho)
double prodInterno(double *a1, double *a2, int tamanho)
Exercícios extra:
Reesecreva o programa de
multiplicação de matrizes utilizando funções com nomes ilustrativos da
operação.
Reescreva os programas
relacionados ao jogo da velha e ao Nurikabe utilizando funções.