#7 GoSchool: Funções

Seja bem vindo ao tutorial #7 da GoSchool!

Caso você tenha perdido o tutorial #6 sobre Estrutura de Dados e seus Métodos, segue o link:

Vamos abordar um dos recursos essenciais para todos os seus desenvolvimentos, as funções.

Por ser um assunto muito direto, vamos abordar sua estrutura por tópicos:

  • Parâmetros;
  • Retornos;
  • Defer;
  • Função como Parâmetro

Antes de abordarmos os tópicos, vamos entender um pouco sobre a declaração básica de uma função.

- “Show, vamos lá!”


Declaração

A declaração de uma função em Go é feita utilizando o operador func .

Possui uma estrutura obrigatória com alguns itens opcionais, apresentados a seguir:

func nomeDaFuncao (parametros) (retorno) {}
  • nomeDaFuncao - Nome da função, pelo qual será chamada e identificada - [Obrigatório];
  • parametros - Lista de parâmetros utilizados na função - [Obrigatório];
  • retorno - Parâmetros de retorno da função - [Opcional]

- “Entendi como ela é declarada, mas não entendi como utilizá-la.”

Sem problemas! Vamos chamar os Gophers!

Criamos uma função que vai verificar, entre dois Gophers, qual deles é o maior, retornando o resultado para uma variável. Note que a estrutura ficou assim:

  • maior - Nome da função;
  • Gophers - Parâmetros da nossa função;
  • Gopher - Tipo de retorno da função

Nesse caso, ele irá aplicar um série de conceitos que já vimos aqui na GoSchool, e vai identificar qual dos dois Gophers é o maior.

O resultado será:

- “Como devo executa-la?”

A execução de uma função é feita da seguinte forma:

  • Função
func teste() { //LOGICA }
  • Execução
func teste() { //LOGICA }
func main() {
  teste()
}

Note que declaramos nossa função teste fora da função main , isso porque nesse exemplo, vamos executar essa função logo que o código for executado.

Funções devem ser declaradas de forma individual e não compartilhada, podendo então, ser utilizadas por outras funções.

Vamos explicar as “partes” de um função para que tudo fique mais claro!

Começando por parâmetros.


1. Parâmetros

Os parâmetros são “valores” utilizados na lógica de uma função.

Uma conta de soma, por exemplo:

  • 5 + 3 = 8

Os números 5 e 3, são os parâmetros utilizados para que a lógica operacional da soma possa ser executada.

Vamos entender melhor como utilizar parâmetros através dos tópicos:

  • Indefinido;
  • Definido Simples;
  • Definido Composto;
  • Definido Composto Variável

- Indefinido

Uma função com parâmetros indefinidos é responsável por executar algum tipo de ação sem a necessidade prévia de valores para sua lógica operacional.

Um exemplo de função com parâmetros implícitos:

  • Função
func dizerOi() {
   fmt.Println("Oi")
}
  • Execução
dizerOi()
//OUTPUT : Oi

- Definido Simples

Uma função definida simples possui apenas um parâmetro necessário para sua lógica operacional.

No momento que declaramos um parâmetro, precisamos definir seu tipo, já que nesses casos, a linguagem exige uma declaração explícita do mesmo.

Sua estrutura é:

func nomeDaFunc (nomeDoParametro tipo)

Um exemplo:

  • Função
func dizNome(nome string){
   fmt.Printf("Olá %s", nome)
}
  • Execução
dizNome("Guilherme")
/OUTPUT: Olá Guilherme

Note que para utilizarmos um parâmetro do tipo texto, foi necessário definir o tipo string após a declaração do nome.

Após criarmos a função, devemos obrigatoriamente preencher a variável solicitada pela função no momento de sua execução.

- Definido Composto

Diferente da função definida simples, a composta possui mais de um parâmetro de declaração.

Na definição de parâmetros também devemos especificar o tipo.

func nomeDaFuncao (param1 tipo, param2 tipo, param3 tipo)
  • Parâmetros de tipos diferentes:
func nomeDaFuncao (param1 int, param2 string, param3 float32)
  • Parâmetros de tipos iguais:
func nomeDaFuncao(param1, param2, param3 string)

Note quando o tipo é igual para todos, podemos defini-lo no final da declaração. Algo que simplifica e muito, a escrita de funções.

Um exemplo:

  • Função
func declararIdade(nome string, anoNascimento int) {
    idade := 2018 - anoNascimento
    fmt.Printf("%s tem %d anos", nome, idade)
}
  • Execução
declararIdade("Guilherme", 1997)
//OUTPUT: Guilherme tem 21 anos

Processo bem simples!

- Definido Composto Variável

Como apresentado anteriormente, parâmetros do mesmo tipo podem ser escritos de forma continua, tendo o seu tipo definido no término da declaração.

func nomeDaFuncao(param1, param2, param3 tipo)

Porém, nem sempre o número de parâmetros pode ser definido.

- “Como assim?”

Pense que você quer efetuar uma soma de diversos números. A quantidade de números é desconhecida, mas queremos executar essa operação da mesma forma.

A estrutura então, deve utilizar spread(... ),um operador lógico de distribuição :

func nomeDaFuncao(parametros... int)

Note que estamos declarando diversos parâmetros do tipo inteiro.

Voltando ao nosso exemplo, temos:

  • Função
func soma(numeros... int) {
   var resultado int
   for _, num := range numeros {
      resultado += num      
   }
   fmt.Println(resultado)
}

- “Range, o que é isso?”

Como não havíamos falado de array e slice antes das estruturas de controle, não conseguimos explicar o range .

De forma rápida e direta, o range é utilizado para percorrer um array ou slice retornando seu índice e o seu valor em um for.

slice := []string{"a", "b", "c"}
for indice, valor := range slice {
   fmt.Printf("%d - %s\n", indice, valor)
}
//OUTPUT:
0 - a
1 - b
2 - c

Agora que entendemos range

  • Execução
soma(1, 2, 3, 4, 5, 6)
//OUTPUT: 21

Note que passamos um número indeterminado de parâmetros, e mesmo assim, o sistema conseguiu executar a operação.

AGORA SIM!

Parâmetros entendidos, podemos falar sobre os retornos de uma função.


2. Retornos

O retorno de uma função é o que garante a vasta utilização de funções em todo e qualquer desenvolvimento.

Os retornos são opcionais, ou seja, podem ou não existir em uma função.

Sua declaração é feita da seguinte forma:

func nomeDaFuncao (parametros) (retorno)

Além disso, utilizamos dentro da função o operador return para informar o valor que será retornado.

Vamos entender um pouco melhor cada um dos tipos de retorno.

Eles podem ser:

  • Indefinido;
  • Definido Simples;
  • Definido Composto;
  • Definido Nomeado

- Indefinido

Funções que por padrão não retornam nenhum tipo de valor.

Todas as apresentadas até aqui, são funções de retorno indefinido.

Um exemplo simples é:

  • Função
func dizerOi() {
   fmt.Println("Oi")
}
  • Execução
dizerOi()
//OUTPUT : Oi

Note que ele executou uma ação, mas não retornou nenhum tipo de valor.

- Definido Simples

O retorno definido simples é composto por um único valor retornado.

Sua declaração é feita apresentando o tipo de retorno que será feito.

- “Não entendi nada.”

Vamos supor:

Preciso de uma função que capture dois números e me retorne o valor de sua divisão.

Sabemos que:

  • 2 parâmetros int;
  • 1 retorno int;

Dessa forma:

  • Função
func divisao(num1, num2 int) int {
    divisao := num1 / num2

return divisao
}
  • Execução
resultado := divisao(10, 2)
fmt.Println(resultado)
//OUTPUT: 5

Note que dessa vez, colocamos nossa função em uma variável chamada resultado , já que ela possui um parâmetro de retorno.

SIM! TODO PARÂMETRO DE RETORNO DEVE SER ARMAZENADO EM UMA VARIÁVEL OU CONSUMIDO COMO UM NOVO PARÂMETRO.

- Definido Composto

O retorno definido composto garante o retorno de múltiplos valores, de diversos tipos.

Diferente da declaração de parâmetros, independente se os tipos forem iguais, é necessário especificar o retorno de cada um deles.

- “Calma aí, explique melhor.”

Vamos utilizar o seguinte exemplo:

Preciso de uma função que receba o nome, sobrenome e ano de nascimento, que me retorne o nome completo e a idade.

Beleza, possuímos:

  • 3 parâmetros ( 2 textos, 1 inteiro);
  • 2 retornos (1 texto, 1 inteiro)

Nosso exemplo deve ficar assim:

  • Função
func retornaDados(nome, sobrenome string, anoNascimento int)(string,int){
    nomeCompleto := nome + " " + sobrenome
    idade := 2018 - anoNascimento
    return nomeCompleto, idade
}
  • Execução
nome, idade := retornaDados("Guilherme", "Caruso", 1997)
fmt.Printf("Nome Completo: %s\n Idade: %d", nome, idade
//OUTPUT: 
Nome Completo: Guilherme Caruso
Idade: 21

Note que como possuímos dois retornos, precisamos armazená-los em diferentes variáveis.

O número de variáveis é exatamente o mesmo que o número de retornos.

- “Sou obrigado a utilizar todos os valores de retorno?”

Não!

Porém, para que a linguagem não apresente um erro, é necessário utilizar o operador _ no lugar da variável que não deseja utilizar.

Para que isso fique claro, vamos repetir o exemplo anterior, sem utilizar o retorno de idade

nome, _ := retornaDados("Guilherme", "Caruso", 1997)
fmt.Printf("Nome Completo: %s", nome)
//OUTPUT: Nome Completo: Guilherme Caruso

Utilizamos o _ para informar que nesse momento não queremos utilizar uma das variáveis de retorno .

Simples como falar!

Por fim, podemos falar de uma forma ainda mais simples de utilização dos retornos. Estou falando de retornos nomeados.

- Definido Nomeado

Esse método funciona da mesma forma que todos os retornos já apresentados, contudo, a variável de retorno é iniciada no momento da declaração da função.

- “Consegue melhorar essa explicação?”

CLARO!

Vamos utilizar o exemplo anterior, ok?

Preciso de uma função que receba o nome, sobrenome e ano de nascimento, que me retorne o nome completo e a idade.
  • 3 parâmetros ( 2 textos, 1 inteiro);
  • 2 retornos (1 texto, 1 inteiro)

A declaração fica:

  • Função
func retornaDados(nome, sobrenome string, anoNascimento int)(nomeCompleto string, idade int) {
   nomeCompleto = nome + " " + sobrenome
   idade = 2018 - anoNascimento
   return
}

Sim, eu sei, ficou grandão ehhehe.

Note que na declaração de retorno, especificamos as variáveis e seus respectivos tipos.

Após a execução da lógica operacional, precisamos declarar com o operador de igualdade ( = ) o valor a ser atribuído para a nova variável, finalizando o processo com o return já que o sistema reconhece de forma automática quais são as variáveis de retorno.

  • Execução
nome, idade := retornaDados("Guilherme", "Caruso", 1997)
fmt.Printf("Nome Completo: %s\n Idade: %d", nome, idade
//OUTPUT: 
Nome Completo: Guilherme Caruso
Idade: 21

A partir daqui, já estamos dominando tudo o que uma função é capaz de fazer. Agora, vamos abordar algumas funcionalidades que podem, e serão de extrema utilidade durante todos os seus desenvolvimentos.


3. Defer

Esse tópico será descrito de forma rápida, já que nos próximos tutoriais vamos abordar com muito mais detalhes sua utilização. Nesse momento, quero mostrar sua aplicação básica, para que seja possível começarmos a controlar o fluxo de ações dentro de um função.

ATENÇÂO: Aqui vamos fazer uma utilização básica do defer . Nos próximos tutorais entrei com mais detalhes.

- “Muito aviso e pouca ação!”

Bem, de forma direta, o operador defer garante que uma determinada função ou ação seja executada no “último” momento da função “pai”.

Não entendeu? Já sabe… Gophers!

Vamos imaginar que um Gopher esta percorrendo um mapa carregando uma caixa. O conteúdo dela foi definido no inicio do percurso, porém, apenas quando o nosso querido Gopher chegar no final do trajeto, o conteúdo dela será apresentado.

Viu? Fácil!

O defer funciona da mesma forma.

Você define uma ação no início da lógica operacional, porém, ela só será executada no último momento da função.

Vamos ao exemplo!

Quero uma função que faça uma saudação e me informe quando chegar ao fim.
  • Função
func mostrarFim(nome string) {
    defer fmt.Println("Fim da função!")
    fmt.Printf("Olá %s, esse é um exemplo de função com defer\n", nome)
}
  • Execução
mostrarFim("Guilherme")
//OUTPUT: 
Olá Guilherme, esse é um exemplo de função com defer
Fim da função!

Note que, mesmo declarando a ação com defer no inicio do escopo lógico, a mesma só foi executada no término da função.

O uso de defer é bem comum em Go, portanto, se trombar com ele por ai, lembre-se da GoSchool 😄.


4. Função como Parâmetro

Para fecharmos esse capítulo com chave de ouro, mostrarei uma aplicação rápida de como podemos utilizar funções como parâmetros de outras funções.

SIM! ISSO É POSSÍVEL!

Aproveitando a onda do defer , vamos fazer com que uma função passada como parâmetro seja chamada no término da execução de outra, ok?

  • Funções
func printaMsg() {
   fmt.Println("Fim da função!")
}
func mostrarFim(nome string, funcao func()) {
   defer funcao()
   fmt.Printf("Olá %s, esse é um exemplo de função com defer e função como parâmetro \n", nome)
}

Declaramos duas funções: printaMsg e mostraFim

Na função mostraFim, passamos o parâmetro funcao com o “tipo” func(), indicando que ali deve ser passada uma função.

  • Execução
mostrarFim("Guilherme", printaMsg)
//OUTPUT:
Olá Guilherme,esse é um exemplo de função com defer e função como parâmetro
Fim da função!

FIM!


Conclusão

Chegamos ao fim de mais um tutorial da Gommunity.

Espero que, a partir de agora, a utilização das funções nunca seja um problema, mas sim, uma solução para qualquer tipo de desenvolvimento.

Nesse tutorial vimos:

  • Parâmetros;
  • Retornos;
  • Defer;
  • Função como Parâmetro

Espero que tenha aproveitado, e não se esqueça de ajudar a Gommunity divulgando nosso conteúdo 💙.

Nos vemos no próximo tutorial!