• Nenhum resultado encontrado

Declaração (ou Protótipo) de Função

Tópico Avançado 5

Sintaxe 5.3: Declaração (ou Protótipo) de Função

return_type function_name(parameter1, parameter2,..., parametern);

Exemplo:

double abs(double x); Finalidade:

As declarações de funções comuns tais como sqrtestão contidas em arquivos de cabeçalho. Se você der uma olhada dentro de cmath, vai encontrar a declaração de sqrte de outras funções matemáticas.

Alguns programadores gostam de listar todas as declarações de funções no topo do arquivo e após escrever a maine em seguida as demais funções. Por exemplo, o arquivo futval.cpp pode ser organizado como segue:

#include <iostream>

#include <cmath>

using namespace std;

/* declaração de future_value */

double future_value(double initial_balance, double p, int n);

int main()

{

...

/* uso de future_value */

double balance = future_value(1000, rate, 5);

...

}

/* definição de future_value */

double future_value(double initial_balance, double p, int n)

{

double b = initial balance * pow(1 + p / 100, n);

return b;

}

Essa organização tem uma vantagem: torna o código fácil de ler. Você primeiro lê a função de mais alto nível main, depois as função auxiliares como a future_value. Existe, entretanto, um problema. Sempre que você altera o nome de uma função ou um dos tipos de parâmetros, você de- ve corrigir em ambos os lugares: na declaração e na definição.

Para programas curtos, como os deste livro, isto é uma questão menor e você pode seguramen- te escolher qualquer destas abordagens. Para programas mais longos, é útil separar declarações de definições. O Capítulo 6 contém mais informações sobre como particionar programas grandes em múltiplos arquivos e como colocar declarações em arquivos de cabeçalho. Como você verá no Ca- pítulo 6, funções-membro de classes são primeiro declaradas na definição da classe e então defini- das em algum lugar.

5.6

Efeitos colaterais

Examine a função future_value, que retorna um número. Por que esta função também não

imprime, ao mesmo tempo, o valor?

double future_value(double initial_balance, double p, int n) {

double b = initial_balance * pow(1 + p / 100, n); cout << "O saldo é agora " << b << "\n";

return b; }

É um princípio geral de projeto que uma função não deve deixar rastro de sua existência, exce- to pelo retorno de um valor. Se uma função imprime uma mensagem, se tornará sem valor em um ambiente que não possui stream de saída, a exemplo de programas gráficos ou o controlador de um terminal bancário.

Uma prática particularmente repreensível é emitir mensagens de erro dentro de uma função. Você nunca deve fazer isto:

double future_value(double initial_balance, double p, int n) {

if (p < 0) {

cout << "Valor incorreto de p."; /* Mau estilo */ return 0;

}

double b = initial_balance * pow(1 + p / 100, n); return b;

}

Imprimir uma mensagem de erro limita severamente a reutilização da função future_va- lue. Ela somente poderá ser usada dentro de programas que podem imprimir através de cout, eli- minando assim programas gráficos. Ela pode ser usada somente em aplicações nas quais um usuá- rio realmente lê a saída, eliminando o processamento em background. Além disso, ela só pode ser usada quando o usuário pode entender uma mensagem de erro na língua inglesa, eliminando a maioria de nossos potenciais consumidores. Naturalmente, nossos programas devem conter algu- mas mensagens, mas você pode agrupar todas as atividades de entrada e saída — por exemplo, em main, se o seu programa é curto. Deixe que as funções façam computações e não emissão de mensagens de erro.

Um efeito externo observável de uma função é denominado de efeito colateral. Exibir caracte- res na tela, atualizar variáveis fora da função, e terminar o programa, são exemplos de efeitos co- laterais.

Em particular, uma função que não tem efeitos colaterais pode ser executada muitas vezes sem surpresas. Sempre que forem fornecidas as mesmas entradas, ela vai confiavelmente produzir as mesmas saídas. Esta é uma propriedade desejável para funções e, na verdade, a maioria das fun- ções não possuem efeitos colaterais.

5.7

Procedimentos

Suponha que você necessita imprimir um objeto do tipo Time: Time now;

cout << now.get_hours() << ":"

<< setw(2) << setfill('0') << now.get_minutes() << ":" << setw(2) << now.get_seconds() << setfill(' ');

Um exemplo de impressão é 9:05:30. Os manipuladores setwe setfillservem para for- necer um zero na frente se os minutos e segundos são formados por apenas um dígito.

Naturalmente, esta é uma tarefa muito comum, que bem pode ocorrer novamente: cout << liftoff.get_hours() << ":"

<< setw(2) << setfill('0') << liftoff.get_minutes() << ":" << setw(2) << liftoff.get_seconds() << setfill(' ');

Arquivo printime.cpp 1 #include <iostream> 2 #include <iomanip> 3 4 using namespace std; 5 6 #include "ccc_time.h" 7 8 /**

9 Imprime um horário no formato h:mm:ss.

10 @param t o horário a ser impresso

11 */

12 void print_time(Time t)

13 {

14 cout << t.get_hours() << ":"

15 << setw(2) << setfill(‘0’) << t.get_minutes() << ":"

16 << setw(2) << t.get_seconds() << setfill(‘ ‘);

17 } 18 19 int main() 20 { 21 Time liftoff(7, 0, 15); 22 Time now; 23 cout << "Decolagem: "; 24 print_time(liftoff); 25 cout << "\n"; 26 27 cout << "Agora: "; 28 print_time(now); 29 cout << "\n"; 30 31 return 0; 32 }

Note que esse código não calcula nenhum valor. Ele executa algumas ações e então retorna ao invocador. Uma função sem um valor de retorno é denominada de procedimento. A omissão do valor de retorno é indicada pela palavra-chave void. Procedimentos são chamados da mesma for- ma que funções, mas não existe valor de retorno a ser usado em uma expressão:

print_time(now);

Visto que um procedimento não retorna um valor, ele deve ter algum efeito colateral; senão, não valeria a pena ser chamado. Esse procedimento possui o efeito colateral de imprimir o horário. Idealmente, uma função calcula um único valor e não possui efeitos observáveis. Chamar uma função múltiplas vezes com o mesmo parâmetro retorna o mesmo valor cada vez e não deixa ne- nhum outro rastro. Idealmente, um procedimento possui somente efeito colateral, tal como confi- gurar variáveis ou realizar saída e não retorna nenhum valor.

Algumas vezes esses ideais são obscurecidos por necessidades da realidade. Comumente, pro- cedimentos retornam um valor de status. Por exemplo, um procedimento print_paycheck pode retornar um boolpara indicar que a impressão teve sucesso sem atolamento de papel. En- tretanto, a computação deste valor de retorno não é o principal objetivo de chamar a operação — você não iria imprimir um contra-cheque apenas para saber se ainda tem papel na impressora. Por- tanto, você ainda faria de print_paycheckum procedimento, e não uma função, mesmo se ele retornasse um valor.

5.8

Parâmetros por referência

Vamos escrever um procedimento que eleva o salário de um empregado em ppor cento. Employee harry;

...

raise_salary(harry, 5); /* Agora Harry ganha 5% mais */

Aqui está uma primeira tentativa:

void raise_salary(Employee e, double by) /* Não funciona */ {

double new_salary = e.get_salary() * (1 + by / 100); e.set_salary(new_salary);

}

Mas isso não funciona. Vamos inspecionar o procedimento. Assim que o procedimento inicia, a variável de parâmetro erecebe o mesmo valor que harry, e byrecebe 5. Então eé modifica- da, mas esta modificação não tem efeito em harry, por que eé uma variável separada. Quando o procedimento termina, eé esquecido e harrynão recebeu o aumento.

Um parâmetro tal como eoubyé chamado parâmetro por valor, porque é uma variável que é inicializada com um valor suprido pelo invocador. Todos os parâmetros nas funções e procedimen- tos que escrevemos têm usado parâmetros por valor. Nesta situação, todavia, nós realmente não queremos que epossua o mesmo valor que harry. Nós queremos que ese refira à variável real harry(ou joeou qualquer empregado que seja fornecido na chamada). O salário desta variável deve ser atualizado. Existe um segundo tipo de parâmetro, denominado de parâmetro por referên-

cia, justamente com este comportamento. Querermos tornar eum parâmetro por referência, de modo que enão é uma nova variável mas uma referência a uma variável existente, e qualquer alte- ração em eé realmente uma alteração na variável à qual ese refere nesta chamada particular. A Fi- gura 7 mostra a diferença entre parâmetros por valor e por referência.

A sintaxe para um parâmetro por referência é críptica, como mostrado na Sintaxe 5.4.