Percursos
/
Go
Go
/
Programa
/
Funções
Fu

Funções em Go

1 exercício

Sobre Funções

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

Parâmetros de função

Todos os parâmetros têm de ter um tipo explícito; não existe inferência de tipos para os parâmetros. Não existem valores predefinidos para os parâmetros, por isso todos os parâmetros de função são obrigatórios.

import "fmt"

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

// One parameter
func PrintHelloName(name string) {
  fmt.Println("Hello " + name)
}

Os parâmetros do mesmo tipo podem ser declarados em conjunto, 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 são frequentemente 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 devolvidos

Aos parâmetros da função seguem-se zero ou mais valores devolvidos, que também têm de ter um tipo explícito. Os valores devolvidos únicos são apresentados sem parênteses; os valores devolvidos múltiplos são envolvidos em parênteses. Os valores são devolvidos ao código que fez a chamada através da palavra-chave return. Pode haver várias instruções return numa função. A execução da função termina assim que encontra uma dessas instruções return. Se uma função tiver de devolver vários valores, estes são separados por vírgulas. Podes encontrar mais informações sobre o uso idiomático de múltiplos valores devolvidos no conceito associado.

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

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

Invocar funções

Invocar uma função consiste em indicar o nome da função e passar argumentos para cada um dos parâmetros da função, 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 devolvidos com nome e devolução sem argumentos

Tal como os parâmetros, os valores devolvidos podem ter um nome, opcionalmente. Se forem usados valores devolvidos com nome, uma instrução return sem argumentos devolve esses valores. A isto dá-se o nome de devolução sem argumentos.

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 clarificar o conceito de passagem por valor e de passagem por referência.

Primeiro, vamos clarificar a passagem por valor. No exemplo abaixo, quando passamos a variável val à função MultiplyByTwo, passamos uma cópia de val. Por isso, newVal tem o valor atualizado 4, mas a variável original val continua a ser 2. Nos bastidores, o Go faz essencialmente uma cópia do valor original, para que apenas essa cópia, ou seja, v, seja 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

Em rigor, no Go todos os argumentos são passados por valor, ou seja, é feita uma cópia do valor ou dos dados fornecidos à função. Mas se não quiseres fazer uma cópia dos dados passados a uma função e quiseres alterar esses dados dentro da função, deves usar pointers como argumentos, ou seja, a 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 passados à função em vez de operar apenas sobre uma cópia desses dados.

Por agora, basta saber que os tipos de ponteiro se reconhecem pelo * à 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 te parecer confuso, não há problema. Temos uma secção dedicada mais à frente no programa de estudos para te ajudar a dominar os ponteiros.

Exceções

Repara que slices e maps são exceções à regra acima mencionada. Quando passamos um slice ou um map como argumentos para uma função, são tratados como tipos de ponteiro, mesmo que não haja um * explícito no tipo. Isto significa que, se passarmos um slice ou um map para uma função e modificarmos os seus dados subjacentes, as alterações refletem-se no slice ou map original.

Outros tipos de funções

As funções em Go são consideradas cidadãos de primeira classe, o que as torna muito poderosas. Existem vários outros conceitos à volta das funções, como métodos e funções anónimas, que vais encontrar mais à frente no teu percurso.

Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Funções