Trilhas
/
Go
Go
/
Exercícios
/
Mestre da lasanha
Mestre da lasanha

Mestre da lasanha

Exercício de aprendizagem

Introdução

Uma função permite agrupar código em uma unidade reutilizável. Ela é composta pela palavra-chave func, pelo nome da função e por uma lista separada por vírgulas de zero ou mais parâmetros e tipos entre parênteses.

Parâmetros de função

Todos os parâmetros precisam ter tipo explícito; não existe inferência de tipo para parâmetros. Não há valores padrão para parâmetros, então todos os parâmetros de função são obrigatórios.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// Two parameters
func PrintGreetingName(greeting string, name string) {
  fmt.Println(greeting + " " + name)
}

Parâmetros do mesmo tipo podem ser declarados juntos, seguidos de uma única declaração de tipo.

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

Parâmetros vs. argumentos

Vamos ver rapidamente dois termos que costumam ser confundidos: parameters e arguments. Os parâmetros de função são os nomes definidos na assinatura da função, como greeting e name na função PrintGreetingName acima. Os argumentos de função são os valores concretos passados aos parâmetros da função quando invocamos a função. Por exemplo, no exemplo abaixo, "Hello" e "Katrina" são os argumentos passados aos parâmetros greeting e name:

PrintGreetingName("Hello", "Katrina")

Valores de retorno

Os parâmetros da função são seguidos por zero ou mais valores de retorno, que também precisam ter tipo explícito. Valores de retorno únicos ficam sem parênteses, e múltiplos valores de retorno ficam entre parênteses. Os valores são retornados ao código que fez a chamada por meio da palavra-chave return. Pode haver várias instruções return em uma função. A execução da função termina assim que ela encontra uma dessas instruções return. Se uma função precisar retornar vários valores, eles são separados por vírgulas.

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

Chamando funções

Para invocar uma função, você especifica o nome dela e passa argumentos para cada um de seus parâmetros entre parênteses.

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

Valores de retorno nomeados e retorno nu

Assim como os parâmetros, os valores de retorno podem ter nomes, opcionalmente. Se valores de retorno nomeados forem usados, uma instrução return sem argumentos retornará esses valores. Isso é conhecido como retorno 'nu'.

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

Passagem por valor vs. passagem por referência

Também é importante esclarecer o conceito de passagem por valor e de passagem por referência.

Primeiro, vamos esclarecer a passagem por valor. No exemplo abaixo, quando passamos a variável val para a função MultiplyByTwo, passamos uma cópia de val. Por causa disso, newVal tem o valor atualizado 4, mas a variável original val continua sendo 2. Por baixo dos panos, o Go basicamente faz uma cópia do valor original, de modo que apenas essa cópia, também chamada de v, é modificada pela função.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

A rigor, todos os argumentos são passados por valor em Go, ou seja, é feita uma cópia do valor ou dos dados fornecidos à função. Mas se você não quiser fazer uma cópia dos dados passados para uma função e quiser alterar esses dados dentro dela, então deve usar pointers como argumentos, o que também é chamado de passagem por referência.

Ponteiros

Usamos um pointer para conseguir a passagem por referência. Ao passar argumentos pointer para uma função, podemos modificar os dados subjacentes que foram passados para ela em vez de operar apenas sobre uma cópia desses dados.

Por enquanto, basta saber que os tipos ponteiro podem ser reconhecidos pelo * na frente do tipo na assinatura da função.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

Se o conceito de pointer parecer confuso, não se preocupe. Temos uma seção dedicada mais adiante no programa de estudos para ajudar você a dominar ponteiros.

Exceções

Observe que slices e maps são exceções à regra mencionada acima. Quando passamos um slice ou um map como argumentos para uma função, eles são tratados como tipos ponteiro, mesmo que não haja um * explícito no tipo. Isso significa que, se passarmos um slice ou map para uma função e modificarmos os dados subjacentes, as mudanças serão refletidas no slice ou map original.

Instruções

Neste exercício, você vai escrever mais um pouco de código relacionado ao preparo e ao cozimento da sua lasanha maravilhosa, tirada do seu livro de receitas favorito.

Você tem quatro tarefas. A primeira tem a ver com o cozimento em si, e as outras três com a preparação perfeita.

1. Estime o tempo de preparo

Para a próxima lasanha que você preparar, você quer ter certeza de que reservou tempo suficiente para aproveitar o cozimento. Você já planejou quais camadas a sua lasanha vai ter. Agora você quer estimar quanto tempo o preparo vai levar com base nisso.

Implemente uma função PreparationTime que aceita uma slice de camadas como []string e o tempo médio de preparo por camada em minutos como um int. A função deve retornar a estimativa do tempo total de preparo com base no número de camadas, como um int. Go não tem valores padrão para funções. Se o tempo médio de preparo for passado como 0 (o valor inicial padrão de um int), então o valor padrão 2 deve ser usado.

layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12

2. Calcule as quantidades de macarrão e molho necessárias

Além de reservar o tempo, você também quer ter certeza de que tem molho e macarrão suficientes para cozinhar a lasanha dos seus sonhos. Para cada camada de macarrão na sua lasanha, você vai precisar de 50 gramas de macarrão. Para cada camada de molho na sua lasanha, você vai precisar de 0,2 litro de molho.

Defina a função Quantities que recebe uma slice de camadas como parâmetro, do tipo []string. A função vai então determinar a quantidade de macarrão e molho necessária para preparar sua refeição. O resultado deve ser retornado como dois valores: noodles como um int e sauce como um float64.

Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4

3. Adicione o ingrediente secreto

Um tempo atrás, você visitou um amigo e comeu lasanha na casa dele. Estava incrível e tinha algo especial. O amigo te mandou a lista de ingredientes e disse que o último item da lista é o "ingrediente secreto" que deixou a refeição tão especial. Agora você também quer adicionar esse ingrediente secreto à sua receita.

Escreva uma função AddSecretIngredient que aceita duas slices de ingredientes do tipo []string como parâmetros. O primeiro parâmetro é a lista que seu amigo te mandou, o segundo é a lista de ingredientes da sua própria receita. O último elemento da sua lista de ingredientes é sempre "?". A função deve substituí-lo pelo último item da lista do seu amigo. Observação: AddSecretIngredient não retorna nada. Você deve modificar diretamente a lista dos seus ingredientes. A lista com os ingredientes do seu amigo não deve ser modificada. Além disso, como a slice é passada para uma função como ponteiro, as alterações feitas nos dois argumentos []string passados para AddSecretIngredient acontecem diretamente.

friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}

AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. Escale a receita

As quantidades listadas no seu livro de receitas só rendem lasanha suficiente para duas porções. Como você quer cozinhar para mais pessoas da próxima vez, você quer calcular as quantidades para diferentes números de porções.

Implemente uma função ScaleRecipe que recebe dois parâmetros.

  • Uma slice de valores float64 necessários para 2 porções.
  • O número de porções que você quer preparar.

A função deve retornar uma slice de float64 com as quantidades necessárias para o número de porções desejado. Mas você quer manter a receita original. Isso significa que o argumento quantities não deve ser modificado nesta função.

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
Editar via GitHub O link abre em uma nova janela ou aba
Go Exercism

Tudo pronto para começar Mestre da lasanha?

Crie sua conta no Exercism para aprender e dominar Go com 34 conceitos166 exercícios e mentoria humana de verdade, tudo de graça.