Jiraspiom

Blog Pessoal

Lição 07

Lição 7 – Funções

Funções

Uma função é um bloco de instruções que é executado apenas quando é chamado em algum ponto do programa. Ao usar funções, podemos estruturar os programas de uma maneira mais modular, acessando todo o potencial que a programação estruturada em C++ pode oferecer.

O formato padrão de funções é:

Tipo_dado nome_funcao (parametro1, parametro2, …){
bloco_de_código_da_funcao
}


O Tipo é o tipo (int, float, etc…) do dado que será retornado pela função ao lugar que a chamou. O nome da função é o nome que será usado para caracterizar a função e chamá-la quando for necessário. Os parâmetros podem ser quantos forem necessários. São tipos de dados seguidos pelo identificador, como uma declaração de variável comum (int x, por exemplo), e que agem na função como variáveis locais comuns. Eles permitem passar argumentos para a função quando é chamada.
Exemplo:

#include <iostream>
using namespace std;

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

int main ()
{
int y;
y = soma(200,100);
cout << “O resultado é ” << y;
return 0;
}


Que mostra na tela:

O resultado é 300.


Um programa em C++ sempre inicia sua execução no método main. Logo, neste exemplo, começamos declarando y, e dizendo que seu valor é igual ao valor que a função soma com os parâmetros 200 e 100 retornará. Ao chamar a função soma, o controle é desviado para esta função, que soma os dois numeros recebidos como parâmetros e retornam o resultado para onde ela foi chamada. o valor de y resultante na função main então se altera para 300 e é impresso na tela com o cout.

Escopo de Variáveis

O escopo de variáveis (ou local do programa onde as variáveis são válidas) declaradas numa função é a própria função e por isso, elas não podem ser utilizadas fora delas. No exemplo anterior, o escopo de x é a função soma. Se tentássemos usá-la fora da função, teríamos um erro indicando que a variável não pode ser reconhecida pelo compilador. Da mesma maneira, z não poderia ser utilizada na função soma.

Resumidamente, o escopo das variáveis segue o seguinte:

Variávels Locais
Seu escopo é limitado ao mesmo nível de bloco em que são declaradas.

Variáveis Globais
Variáveis que são visíveis em qualquer ponto do código, dentro e fora de todas as funções. Devem, para isso, ser declaradas fora das funções diretamente no corpo do programa.
Exemplos:

#include <iostream>
using namespace std;

int subtracao (int a, int b)
{
int res;
res=a-b;
return (res);
}

int main ()
{
int x=5, y=3, z;
z = subtracao(7,2);
cout << “O primeiro resultado é ” << z << ‘\n’;
cout << “O segundo resultado é ” << subtracao(7,2) << ‘\n’;
cout << “O terceiro resultado é ” << subtracao(x,y) << ‘\n’;
z= 4 + subtracao(x,y);
cout << “O quarto resultado é ” << z << ‘\n’;
return 0;
}

,o que mostra na tela:

O primeiro resultado é 5
O segundo resultado é 5
O terceiro resultado é 2
O quarto resultado é 6

Funções sem tipo

Se verificarmos o formato geral de funções, verificamos que elas se iniciam com um tipo, que é o tipo da função por si mesma, o tipo do valor que ela retorna ao ponto que a chamou. Mas devemos saber que podemos não querer retornar valor algum. Imagine que somente queremos que uma função retorne algo na tela. Não queremos, então, que ela retorne valor algum e usamos o void para demonstrar isto ao compilador.
Exemplo de uso do void:

#include <iostream>
using namespace std;

void mensagem()
{
cout << “Mensagem à toa!”;
}

int main ()
{
mensagem();
return 0;
}


, que retorna na tela:

Mensagem à toa!

OBS: 1) O void pode ainda ser utilizado para explicitar que não queremos que a função receba parâmetros quando é chamada. A função anterior poderia ser declarada assim:

void mensagem (void)
{
cout << “Mensagem à toa!”;
}


2) Mesmo que a função não contenha parâmetros, é imprescindível o uso dos parênteses na sua declaração:

void mensagem ()
{
cout << “blablabla”;
}

Parâmetros passados por Valor e por Referência

Argumentos passados por valor significa que, ao serem passados para a função, são “cópias” de seus valores, e não as variáveis propriamente ditas. Na prática, o que acontece é que são passados para a função apenas os VALORES das variáveis. Com isso, não importa se estes valores forem alterados ao longo da função, as variáveis originais continuarão tendo o mesmo valor fora dela.
Parte do Exemplo anterior:

int x=3, y=4;
int z;
z= adicao(x,y);


Aqui, quando a função é chamada, o valor de a e b se tornam 3 e 4 respectivamente, mas qualquer modificação a a ou b dentro da função não alterará o valor de x e y fora dela, já que não foram passadas à função e sim apenas seus valores (ou cópias deles).

Já no caso dos argumentos passados por referência a função não recebe apenas um valor, mas sim o ENDEREÇO de uma variável global. Portanto, qualquer modificação que for realizada no conteúdo deste parâmetro afetará também a variável global que está associada a ele. Durante a execução do subprograma, ou função, os parâmetros passados por referência são análogos às variáveis globais.
Exemplo:

#include <iostream>
using namespace std;

void dobro(int& a, int& b, int& c)
{
a*=2;
b*=2;
c*=2;
}

int main ()
{
int x=1, y=3, z=7;
dobro(x, y, z);
cout << “x=” << x << “, y=” << y << “, z=” << z;
return 0;
}


,o que retorna na tela:

x=2, y=6, z=14


Note que na declaração de cada parâmetro da função é usado o símbolo &. Ele é quem especifica que os parâmetros serão passados por referência. Perceba também que depois de o programa ter chamado a função, os parâmetros x, y e z têm seus valores alterados, situação esta que foi criada dentro da função por terem sido passados como por referência.

Existe um exemplo interessante que mostra que, ao passarmos os valores por referência, permitimos que a função retorne mais de um valor. neste caso o valor original (passado por valor), o valor anterior a ele e o valor posterior a ele. Veja:

#include <iostream>
using namespace std;

void ant_post(int x, int& ant, int& post)
{
ant = x-1;
post = x+1;
}

int main ()
{
int x=100, y, z;
ant_post(x, y, z);
cout << “Valor Anterior=” << y << “, Valor Posterior=” << z;
return 0;
}


que, como o valor de x é 100, retorna na tela:

Valor Anterior=99, Valor Posterior=101

Valores Padrões nos Parâmetros

Ao declararmos uma função podemos especificar um valor padrão para cada parâmetro. Este valor será usado se o argumento correspondente for deixado em branco na hora de chamar a função. Para isto, simplesmente usamos o operador de atribuição e um valor para os argumentos na declaração da função. Se um valor para aquele parâmetro não for passado quando a função é chamada, o valor padrão é usado. Contudo, se um valor para o parâmetro for especificado na chamada da função, este valor padrão é ignorado e o valor passado à função é usado.
Exemplo:

#include <iostream>
using namespace std;

int divisao(int a, int b=2)
{
int x;
x=a/b;
return (x);
}

int main ()
{
cout << divisao(12);
cout << endl;
cout << divisao(20,4);
return 0;
}

que resulta em:

6
5

No caso anterior, o valor padrão b=2 só é utilizado no caso de divisao(12), pois não existe um segundo argumento especificado.

Em C++, podemos usar mesmo nome para mais de uma função desde que tenham um número de parâmetros diferentes ou tipos diferentes nos parâmetros.
Exemplo:

#include <iostream>
using namespace std;

int calcular(int a, int b)
{
return (a*b);
}

float calcular(float a, float b)
{
return (a/b);
}

int main ()
{
int x=5,y=2;
float n=5.0,m=2.0;
cout << calcular(x,y);
cout << “\n”;
cout << calcular(n,m);
cout << “\n”;
return 0;
}


que retorna como resultado na tela:

10
2.5

Neste caso, declaramos 2 funções com o mesmo nome (calcular) mas com tipos diferentes (int e float).

Especificador Inline

Usado para dizer ao compilador que a substituição inline deve ser usada, ao invés do mecanismo de chamada da função usual. Não muda o comportamento da função, mas é usado para sugerir ao compilador que o código gerado pelo corpo da função seja inserido em cada ponto de chamada da função, ao invés de ser inserido apenas uma vez e ser feita uma chamada normal a ela, o que geralmente envolve um tempo de execução adicional.

Seu formato:

inline tipo nome_da_funcao ( argumentos … )
{ bloco_de_instrucoes }

A única diferença é o uso do especificador inline no início da DECLARAÇÃO da função, não sendo necessário nenhuma mudança na chamada da função. A maioria dos compiladores já otimiza o código incluíndo funções inline quando é mais conveniente. O especificador só indica que para determinada função o inline é preferido.

Recursividade

É a propriedade que as funções têm de chamarem a si mesmas. Suas utilidades são inúmeras, como calcular fatoriais de números, ordenações, etc.
Exemplo
Como o fatorial de um número é calculado usando:

n! = n * (n-1) * (n-2) * (n-3) … * 1

em um programa teríamos:

#include <iostream>
using namespace std;

long fatorial (long x)
{
if (x > 1)
return (x * fatorial (x-1));
else
return (1);
}

int main ()
{
long num;
cout << “Insira um valor: “;
cin >> num;
cout << num << “! = ” << fatorial (num);
return 0;
}

que mostra na tela:

Insira um valor: 10
10! = 3628800



Percebemos que no código anterior a função chama a si mesma, mas apenas se o valor passado como argumento for maior que 1. Caso contrário, a função executa um loop infinito recursivo em que, assim que atingir o valor 0, continuaria multiplicando por todos os números negativos (o que provavelmente geraria um erro de overflow na execução). Neste exemplo, por termos usado long, os resultados não seriam válidos para valores muito maiores que 10! ou 15!, dependendo do sistema em que forem compilados.

Declarações de Funções

Até aqui, definimos todas as funções antes da primeira aparição de chamadas a elas no código. Acontece que se fizermos o teste de inverter o main{} e declararmos as funções após ele, teríamos erros de compilação, já que elas devem já ter sido declaradas para poderem ser chamadas no main. No entanto, existe uma maneira de evitar termos que escrever o código inteiro da função antes de chamá-la. Basta declararmos sua existência, ao invés da definição completa. Esta declaração tem a forma:

tipo nome_da_funcao (tipo argumento1, tipo argumento2, …);


que é idêntica à definição de uma função excluíndo o corpo dela. Apesar de deixar o código mais legível os nomes dos parâmetros não são obrigatórios. As seguintes declarações dos protótipos de uma função são válidas:

int funcao1 (int a, int b);
int funcao1 (int, int);


Exemplo:

#include <iostream>
using namespace std;

void impar(int x);
void par(int x);

int main ()
{
int i;
do {
cout << “Diga um valor (digite 0 para sair): “;
cin >> i;
impar(i);
} while (i!=0);
return 0;
}

void impar(int x)
{
if ((x%2)!=0) cout << “Número é ímpar.\n”;
else par(x);
}

void par(int x)
{
if ((x%2)==0) cout << “Número é par.\n”;
else impar(x);
}


o que mostra na tela:

Diga um valor (digite 0 para sair):3
Número é ímpar.
Diga um valor (digite 0 para sair):1000
Número é par.
Diga um valor (digite 0 para sair):15
Número é ímpar.
Diga um valor (digite 0 para sair):0
Número é par.

Percebemos neste exemplo que as funções par e impar só são definidas com seu corpo inteiro após o método main, mas foram declaradas inicialmente com seus protótipos. Esta prática mostrou-se eficiente para a limpeza do código, já que isso acaba facilitando a localização das funções existentes no programa.


Anúncios
%d blogueiros gostam disto: