Como um simples exercício de função fez eu quebrar a cabeça o dia inteiro
Olá, estou estudando o CS50 de Harvard 2023 e decidi fazer alguns exercícios da Semana 1 (C). O exercício se chama "Half" e você pode encontrá-lo aqui.
Algumas informações e dicas que o exercício dá
Esse exercício foi realmente desafiador e tem algumas 'metas de aprendizagem', que seria trabalhar com diferentes tipos de dados, praticar conversão de tipos, usar operações matemáticas e criar uma função com parâmetros de entrada e valor de retorno.
A descrição diz o seguinte: "Suponha que você esteja comendo fora em um restaurante com um amigo e queira dividir a conta igualmente. Você pode querer antecipar o valor devido antes que a fatura chegue com o imposto adicionado. Neste problema, você completará uma função para calcular o valor que cada um de vocês deve com base no valor da conta, no imposto e na gorjeta."
As dicas são:
- Observe que o imposto e a gorjeta são inseridos como porcentagens. Você pode querer alterá-los para valores decimais antes de calcular o total.
- Como a dica é inserida como
int
, lembre-se de que se você dividir umint
por umint
você receberá umint
! - A ordem das operações aqui é exatamente a mesma da álgebra, onde a multiplicação e a divisão são realizadas antes da adição e da subtração.
Essas dicas são boas, especificamente a segunda, pois como vamos utilizar porcentagens, não podemos trabalhar com números inteiros na hora de dividir (10 / 100 = 0).
Se você quiser tentar resolver esse exercício, acesse: CS50 no Google Chrome e faça login com o GitHub.
- Clique dentro da janela do terminal e execute
cd
. - No $prompt, digite
mkdir half
- Agora execute
cd half
- Em seguida, copie e cole
wget https://cdn.cs50.net/2022/fall/labs/1/half.c
em seu terminal para baixar o código de distribuição do laboratório. - Você deve completar a função,
half
que calcula exatamente metade da conta após a adição de impostos e gorjetas, e retorna esse valor.
O exercício
Abrindo o arquivo gerado, este é código que teremos que modificar:
// Calculate your half of a restaurant bill
// Data types, operations, type casting, return value
#include <cs50.h>
#include <stdio.h>
float half(float bill, float tax, int tip);
int main(void)
{
float bill_amount = get_float("Bill before tax and tip: ");
float tax_percent = get_float("Sale Tax Percent: ");
int tip_percent = get_int("Tip percent: ");
printf("You will owe $%.2f each!\n", half(bill_amount, tax_percent, tip_percent));
}
// TODO: Complete the function
float half(float bill, float tax, int tip)
{
return 0.0;
}
Se tentar testar o código: digite make half
e depois ./half
. Digite os valores para as perguntas e você verá que o resultado será You will owe 0.00 each!
Vamos precisar resolver alguns problemas e neste exercício deve usar função criada. Por termos que modificar a função, por enquanto, esqueceremos a main.
Primeiro, vamos entender algumas coisas nesse código, afinal está em inglês. Bill significa fatura (e eu fiquei um tempo tentando entender quem era Bill), tax é o imposto a ser pago e tip é a gorjeta. Então nós temos uma fatura que recebe um valor x, que está sem imposto e sem gorjeta. Nosso trabalho é adicionar um imposto sobre essa nossa fatura e depois a gorjeta, somente depois dividir entre "eu" e o amigo.
No código, vamos transformar as porcentagens em decimal e, lembrem-se da dica 2, que se dividir um inteiro por outro inteiro, teremos um inteiro, logo, usaremos o float
, tanto para dividir o imposto e a gorjeta. Ficará assim:
// TODO: Complete the function
float half(float bill, float tax, int tip)
{
float tax_decimal = tax_percent / 100.0;
float tip_decimal = tip_percent / 100.0;
}
O próximo passo será calcular o imposto e taxa sobre a nossa fatura e após isso dividir:
// TODO: Complete the function
float half(float bill_amount, float tax_percent, int tip_percent)
{
float tax_decimal = tax_percent / 100.0;
float tip_decimal = tip_percent / 100.0;
float bill_after_tax = bill_amount * tax_decimal + bill_amount;
float bill_after_tax_tip = bill_after_tax * tip_decimal + bill_after_tax;
float invoice_split = bill_after_tax_tip / 2;
return invoice_split;
}
Até então, fizemos tudo o que o exercício propôs: convertemos inteiro para decimal (float), adicionamos o imposto na fatura primeiro que a gorjeta (o exercício pedia para ser nessa ordem) e por fim, divimos a conta igualmente. Se tentarmos compilar e testar vai dar erro:
Foi nesse ponto que eu estagnei, não conseguia achar uma solução por conta própria, pesquisei e vi que tem vídeos explicando e respostas no Stack OverFlow e Reddit, por exemplo. Inclusive, alguns códigos que estavam diferentes do meu, mas com a base semelhante, foi bom ver que havia mais de uma lógica além da minha. Porém, ainda não tinha entendido o que estava errado, até perceber que a linha errada.
float half(float bill, float tax, int tip)
O correto, nesse caso, seria:
float half(float bill_amount, float tax_percent, int tip_percent)
Agora, se compilarmos novamente, funcionará. Inclusive, se testar os exemplos que tem na página do exercício, todos funcionarão. Nosso código no final ficará assim:
// Calculate your half of a restaurant bill
// Data types, operations, type casting, return value
#include <cs50.h>
#include <stdio.h>
float half(float bill, float tax, int tip);
int main(void)
{
float bill_amount = get_float("Bill before tax and tip: ");
float tax_percent = get_float("Sale Tax Percent: ");
int tip_percent = get_int("Tip percent: ");
printf("You will owe $%.2f each!\n", half(bill_amount, tax_percent, tip_percent));
}
// TODO: Complete the function
float half(float bill_amount, float tax_percent, int tip_percent)
{
float tax_decimal = tax_percent / 100.0;
float tip_decimal = tip_percent / 100.0;
float bill_after_tax = bill_amount * tax_decimal + bill_amount;
float bill_after_tax_tip = bill_after_tax * tip_decimal + bill_after_tax;
float invoice_split = bill_after_tax_tip / 2;
return invoice_split;
}
Dúvida
Agora uma dúvida para quem conhece a Linguagem C: observem a linha 7, ela deveria ser igual a linha 19 ou não? Afinal os parâmetros são diferentes, no meu ponto de vista: bill é diferente de bill_amount, tax é diferente de tax_percent e tip é diferente de tip_percent. Se der, me tirem essa dúvida também.
Obrigado e até a próxima!
bill é diferente de bill_amount, tax é diferente de tax_percent e tip é diferente de tip_percent.
Os nomes dos parâmetros de uma função só existem dentro da função. Fora dela, o que importa é que ela receba uma expressão que resulte em um valor que ela espera receber.
Só para dar um exemplo, eu poderia chamar a função assim:
half(100, 6.25, 18);
Ou seja, em vez de ter variáveis, eu posso muito bem chamar a função passando os valores numéricos diretamente. Claro que para o exercício não serve, mas tecnicamente falando, nada impede que eu faça isso. E ao chamar desta forma, o valor 100
será atribuído ao parâmetro bill_amount
, o valor 6.25
será atribuído ao parâmetro tax_percent
, e 18
, a tip_percent
.
Os nomes só existem dentro da função. Fora dela, quem for chamá-la, pode passar qualquer coisa, desde que o resultado seja compatível com os tipos que ela espera.
Tanto que eu também poderia fazer algo assim:
half(valor_batata + valor_cervejas + valor_hamburguer, calcular_taxa_do_dia(), ler_valor_gorjeta());
Assumindo que todas as variáveis valor_batata
, valor_cervejas
e valor_hamburguer
e as funções calcular_taxa_do_dia
e ler_valor_gorjeta
existam e seus tipos sejam compatíveis com o que a função half
espera.
Resumindo, uma coisa são os nomes dos parâmetros, que só existem dentro da função. Outra coisa são os valores dos argumentos que passamos para a função ao chamá-la. Estes podem ser quaisquer expressões (nem precisam ser variáveis), desde que os tipos sejam compatíveis com os respectivos parâmetros. Ou seja, as variáveis que vc leu no main
não precisam necessariamente ter os mesmos nomes dos parâmetros definidos na função.
Por fim, a função poderia ser simplificada, pois não vejo a necessidade de tantas variáveis intermediárias:
float half(float bill, float tax, int tip) {
float tax_multiplier = 1 + (tax / 100);
float tip_multiplier = 1.0 + (tip / 100.0);
return (bill * tax_multiplier * tip_multiplier) / 2;
}
No caso do tax_multiplier
, como tax
é um float
, toda a expressão resultará em um float
. Já tip
é um int
, então eu preciso dividir por 100.0
(o .0
no final força que toda a expressão seja float
, evitando o arredondamento que ocorreria se eu dividisse por 100
, já que este é um literal inteiro).
Ou ainda, caso seja permitido criar funções auxiliares:
// converte um valor de porcentagem para o respectivo multiplicador (ex: 6.5 vira 1.065)
float convert_perc(float perc) {
return 1.0 + (perc / 100.0);
}
float half(float bill, float tax, int tip) {
return (bill * convert_perc(tax) * convert_perc(tip)) / 2;
}
Eae, tudo bem ? O erro da foto estava sendo causado por causa dos diferentes nomes colocados na declaração do parâmetro e na utilização de uma variável que "não existe". Provavelmente era algo assim:
float half(float bill, float tax, int tip)
{
float tax_decimal = tax_percent / 100.0; // deveria ser "tax" e não "tax_percent"
...
E em relação a sua dúvida: Não há problema em fazer a declaração da função com parâmetros de nomes diferentes desde que eles tenham o mesmo tipo e que sejam utilizados os nomes certos no escopo da função. Então, a única coisa que importa para o compilador é que o formato seja (float, float, int), pouco importando se os parâmetros tenham diferentes nomes. Inclusive, se você mantivesse os nomes "bill, tax e tip" na linha 19 o código ainda funcionaria desde que, é claro, você também mudasse a chamada destes parâmetros dentro da função.
Ficaria assim:
float half(float bill, float tax, int tip) {
float tax_decimal = tax / 100.0;
float tip_decimal = tip / 100.0;
float bill_after_tax = bill * tax_decimal + bill;
float bill_after_tax_tip = bill_after_tax * tip_decimal + bill_after_tax;
float invoice_split = bill_after_tax_tip / 2;
return invoice_split;
}
Qualquer dúvida é só chamar.
> Além disso, como que bill_after_tax (e tax/tip_decimal) sabe que bill terá os valores de bill_amount? Seria por causa do float half(float bill, float tax, int tip)? Como você está criando a função e nomeando os parâmetros nesta ordem (float bill, float tax, int tip), sempre que você chamar a função, os parâmetros serão gravados nesta ordem. Por exemplo: ```c float aaa = 50.0; float bbb = 10.0; int ccc = 15; half(aaa, bbb, ccc); ``` Como mostra o exemplo acima, os valores recebidos pela função independem dos nomes dados as variáveis utilizada nas chamadas.
Espero ter ajudado
e aí !
a linha antes da main() é chamada de "assinatura da função" e não precisa nem ter nomes nos argumentos, ou seja, poderia ser escrita assim:
float half(float, float, int);
mas é uma boa prática colocar o nome do parâmetro para servir de documentação pra quem usar. sei que, provavelmente, tu ainda estás no início do aprendizado, mas em sistemas em C, as assinaturas de funções geralmente são colocadas em um arquivo separado das implementações (que é a função depois da main). assinaturas vão em arquivos .h
e implementações em arquivos .c
.
espero ter