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.