Files
2016-12-21 19:05:13 -02:00

517 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2.3 Declarações de controle e funções
Nesta seção nós vamos falar sobre declarações de controle e operações de funções em Go.
## Declarações de Controle
A maior invenção na programação é o controle de fluxo. Por causa dele, você é capaz de utilizar declarações de controle simples que podem ser usadas para representar lógicas complexas. Existem três categorias de controle de fluxo: condicional, controle de ciclo e salto incondicional.
### if
`if` provavelmente será a palavra-chave mais utilizada nos seus programas. Se ele atende as condições, então ele faz alguma coisa e caso contrário faz alguma outra coisa.
`if` não precisa de parênteses em Go.
if x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than or equal to 10")
}
A coisa mais útil sobre o `if` em Go é que ele pode ter uma instrução de inicialização antes da instrução condicional. O escopo das variáveis definidas nesta instrução de inicialização só estão disponíveis dentro do bloco de definição do `if`.
// inicializa x e então confere se x é maior que 10
if x := computedValue(); x > 10 {
fmt.Println("x is greater than 10")
} else {
fmt.Println("x is less than 10")
}
// o código seguinte não compilará
fmt.Println(x)
Use if-else para múltiplas condições.
if integer == 3 {
fmt.Println("The integer is equal to 3")
} else if integer < 3 {
fmt.Println("The integer is less than 3")
} else {
fmt.Println("The integer is greater than 3")
}
### goto
Go possui uma palavra-chave chamada `goto`, mas cuidado ao utilizar ela. `goto` reencaminha o fluxo de controle para um `label` definido anteriormente dentro do mesmo bloco de código.
func myFunc() {
i := 0
Here: // label termina com ":"
fmt.Println(i)
i++
goto Here // pule para o label "Here"
}
O nome do label é case sensitive.
### for
`for` é a lógica de controle mais poderosa em Go. Ele pode ler dados em loops e operações iterativas, assim como o `while`.
for expression1; expression2; expression3 {
//...
}
`expression1`, `expression2` e `expression3` são todas expressões, onde `expression1` e `expression3` são definições de variáveis ou atribuições, e `expression2` é uma declaração condicional. `expression1` será executada uma vez antes do loop, e `expression3` será executada depois de cada iteração do loop.
Exemplos são mais úteis que palavras.
package main
import "fmt"
func main(){
sum := 0;
for index:=0; index < 10 ; index++ {
sum += index
}
fmt.Println("sum is equal to ", sum)
}
// Mostra sum is equal to 45
Algumas vezes nós precisamos de várias atribuições, porém Go não possui o operador `,`, então nós usamos atribuições paralelas como `i, j = i + 1, j - 1`.
Nós podemos omitir as expressões `expression1` e `expression3` se elas não forem necessárias.
sum := 1
for ; sum < 1000; {
sum += sum
}
Podemos omitir também o `;`. Isto lhe parece familiar? Sim, é idêntico ao `while`.
sum := 1
for sum < 1000 {
sum += sum
}
Existem duas operações importantes em loops que são `break` e `continue`. `break` "pula" fora do loop, e `continue` ignora o loop atual e inicia o próximo. Se você tiver loops aninhados use `break` juntamente com labels.
for index := 10; index>0; index-- {
if index == 5{
break // ou continue
}
fmt.Println(index)
}
// break mostra 10、9、8、7、6
// continue mostra 10、9、8、7、6、4、3、2、1
`for` pode ler dados de um `slice` ou `map` quando for utilizado junto com `range`.
for k,v:=range map {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
}
Como Go suporta múltiplos valores de retorno e gera um erro de compilação caso você não use os valores que foram definidos, você pode querer utilizar `_` para descartar certos valores de retorno.
for _, v := range map{
fmt.Println("map's val:", v)
}
### switch
As vezes você pode achar que está usando muitas instruções `if-else` para implementar uma lógica, o que pode dificultar a leitura e manutenção no futuro. Este é o momento perfeito para utilizar a instrução `switch` para resolver este problema.
switch sExpr {
case expr1:
some instructions
case expr2:
some other instructions
case expr3:
some other instructions
default:
other code
}
Os tipos de `sExpr`, `expr1`, `expr2`, e `expr3` devem ser o mesmo. `switch` é muito flexível. As condições não precisam ser constantes e são executadas de cima para baixo até que encontre uma condição válida. Se não houver nenhuma instrução após a palavra-chave `switch`, então ela corresponderá a `true`.
i := 10
switch i {
case 1:
fmt.Println("i is equal to 1")
case 2, 3, 4:
fmt.Println("i is equal to 2, 3 or 4")
case 10:
fmt.Println("i is equal to 10")
default:
fmt.Println("All I know is that i is an integer")
}
Na quinta linha, colocamos vários valores em um `case`, e não precisamos adicionar a palavra-chave `break` no final do bloco do `case`. Ele irá sair do bloco do switch quando encontrar uma condição verdadeira. Se você deseja continuar verificando mais casos, você precisará utilizar a instrução `fallthrough`.
integer := 6
switch integer {
case 4:
fmt.Println("integer <= 4")
fallthrough
case 5:
fmt.Println("integer <= 5")
fallthrough
case 6:
fmt.Println("integer <= 6")
fallthrough
case 7:
fmt.Println("integer <= 7")
fallthrough
case 8:
fmt.Println("integer <= 8")
fallthrough
default:
fmt.Println("default case")
}
Este programa mostra a seguinte informação.
integer <= 6
integer <= 7
integer <= 8
default case
## Funções
Use a palavra-chave `func` para definir uma função.
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
// corpo da função
// retorno de múltiplos valores
return value1, value2
}
Nós podemos extrapolar as seguintes informações do exemplo acima.
- Use a palavra-chave `func` para definir uma função `funcName`.
- Funções tem zero, um ou mais argumentos. O tipo do argumento vem depois do nome do argumento e vários argumentos são separados por `,`.
- Funções podem retornar múltiplos valores.
- Existem dois valores de retorno com os nomes `output1` e `output2`, você pode omitir estes nomes e usar apenas os tipos deles.
- Se existe apenas um valor de retorno e você omitir o nome, você não precisa usar colchetes nos valores de retorno.
- Se a função não possui valores de retorno, você pode omitir os parâmetros de retorno completamente.
- Se a função possui valores de retorno, você precisa utilizar a instrução `return` em algum lugar no corpo da função.
Vamos ver um exemplo prático. (calcular o valor máximo)
package main
import "fmt"
// retorna o maior valor entre a e b
func max(a, b int) int {
if a > b {
return a
}
return b
}
func main() {
x := 3
y := 4
z := 5
max_xy := max(x, y) // chama a função max(x, y)
max_xz := max(x, z) // chama a função max(x, z)
fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // chama a função max aqui
}
No exemplo acima existem dois argumentos do tipo `int` na função `max`, sendo assim o tipo do primeiro argumento pode ser omitido. Por exemplo, pode-se utilizar `a, b int` em vez de `a int, b int`. As mesmas regras se aplicam para argumentos adicionais. Observe aqui que `max` só tem um valor de retorno, então nós só precisamos escrever o tipo do valor de retorno. Esta é a forma curta de escrevê-lo.
### Múltiplos valores de retorno
Uma coisa em que Go é melhor que C é que ela suporta múltiplos valores de retorno.
Usaremos o seguinte exemplo.
package main
import "fmt"
// Retorna os resultados de A + B e A * B
func SumAndProduct(A, B int) (int, int) {
return A+B, A*B
}
func main() {
x := 3
y := 4
xPLUSy, xTIMESy := SumAndProduct(x, y)
fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}
O exemplo acima retorna dois valores sem nomes - você também tem a opção de nomeá-los se preferir. Se nomearmos os valores de retorno, poderemos utilizar apenas a instrução `return` para retornar os valores já que eles foram inicializados na função automaticamente. Observe que se suas funções forem utilizadas fora do pacote, o que significa que o nome das funções começam com uma letra maiúscula, é melhor escrever instruções completas para o `return`. Isto torna o seu código mais legível.
func SumAndProduct(A, B int) (add int, Multiplied int) {
add = A+B
Multiplied = A*B
return
}
### Argumentos variáveis
Go suporta argumentos variáveis, o que significa que você pode dar um número incerto de argumentos para funções.
func myfunc(arg ...int) {}
A instrução `arg …int` significa que esta é uma função que possui argumentos variáveis. Observe que estes argumentos são do tipo `int`. No corpo da função o `arg` se torna um `slice` de `int`.
for _, n := range arg {
fmt.Printf("And the number is: %d\n", n)
}
### Passar por valor e ponteiros
Quando passamos um argumento para uma função que foi chamada, a função recebe na verdade uma cópia da nossa variáveis original, sendo assim, as alterações não afetarão a variável original.
Vejamos um exemplo para provar o que eu estou dizendo.
package main
import "fmt"
// Função simples que adiciona 1 a variável a
func add1(a int) int {
a = a+1 // alteramos o valor de a
return a // retornamos o novo valor de a
}
func main() {
x := 3
fmt.Println("x = ", x) // deve mostrar "x = 3"
x1 := add1(x) // chama add1(x)
fmt.Println("x+1 = ", x1) // deve mostrar "x+1 = 4"
fmt.Println("x = ", x) // deve mostrar "x = 3"
}
Você consegue ver isso? Mesmo que nós chamamos `add1` com `x`, o valor original de `x` não é alterado.
A razão é muito simples: quando chamamos `add1`, nós passamos uma cópia de `x` para a função, e não o próprio `x`.
Agora você pode se perguntar, como eu posso passar o `x` original para a função.
Precisamos usar ponteiros aqui. Sabemos que as variáveis são armazenadas em memória e que elas possuem endereços de memória. Então, se queremos alterar o valor de uma variável, precisamos alterar o valor armazenado no endereço de memória. Portanto, a função `add1` precisa saber o endereço de memória de `x` para poder alterar o seu valor. Para isto, passamos `&x` para a função, e alteramos o tipo do argumento para o tipo ponteiro `*int`. Esteja ciente de que nós passamos uma cópia do ponteiro, não uma cópia do valor.
package main
import "fmt"
// Função simples que adiciona 1 a variável a
func add1(a *int) int {
*a = *a+1 // alteramos o valor de a
return *a // retornamos o novo valor de a
}
func main() {
x := 3
fmt.Println("x = ", x) // deve mostrar "x = 3"
x1 := add1(&x) // call add1(&x) pass memory address of x
fmt.Println("x+1 = ", x1) // deve mostrar "x+1 = 4"
fmt.Println("x = ", x) // deve mostrar "x = 4"
}
Agora podemos alterar o valor de `x` dentro da função. Por que usamos ponteiros? Quais são as vantagens?
- Permite-nos usar mais funções para operar em uma variável.
- Baixo custo passando endereços de memória (8 bytes), a cópia não é uma maneira eficiente, tanto em termo de tempo como de espaço, para passar variáveis.
- `string`, `slice`, `map` são tipos de referências, sendo assim, por padrão eles utilizam ponteiros ao passar para uma função. (Atenção: Se você precisa alterar o comprimento de um `slice`, você precisa passar ponteiros explicitamente)
### defer
Go possui uma palavra-chave bem projetada chamada `defer`. Você pode ter muitas declarações `defer` em uma função. Elas serão executadas em ordem inversa quando o programa executa até o final das funções. No caso onde o programa abre alguns arquivos de recurso, estes arquivos precisam ser fechados antes que a função possa retornar com erros. Vamos ver alguns exemplos.
func ReadWrite() bool {
file.Open("file")
// Faça alguma tarefa
if failureX {
file.Close()
return false
}
if failureY {
file.Close()
return false
}
file.Close()
return true
}
Vimos algum código sendo repetido várias vezes. `defer` resolve este problema muito bem. Ela não só ajuda você a escrever um código limpo, como também torna seu código mais legível.
func ReadWrite() bool {
file.Open("file")
defer file.Close()
if failureX {
return false
}
if failureY {
return false
}
return true
}
Se houver mais de uma instrução `defer`, elas serão executadas em ordem inversa. O exemplo a seguir irá mostrar `4 3 2 1 0`.
for i := 0; i < 5; i++ {
defer fmt.Printf("%d ", i)
}
### Funções como tipos e valores
Funções também são variáveis em Go, podemos utilizar `type` para defini-las. Funções que possuem a mesma assinatura podem ser vistas como sendo do mesmo tipo.
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])
Qual é a vantagem deste recurso? A resposta é que isto nos permite passar funções como valores.
package main
import "fmt"
type testInt func(int) bool // define o tipo de uma função como variável
func isOdd(integer int) bool {
if integer%2 == 0 {
return false
}
return true
}
func isEven(integer int) bool {
if integer%2 == 0 {
return true
}
return false
}
// passa a função `f` como um argumento para outra função
func filter(slice []int, f testInt) []int {
var result []int
for _, value := range slice {
if f(value) {
result = append(result, value)
}
}
return result
}
func main(){
slice := []int {1, 2, 3, 4, 5, 7}
fmt.Println("slice = ", slice)
odd := filter(slice, isOdd) // usa a função como valor
fmt.Println("Odd elements of slice are: ", odd)
even := filter(slice, isEven)
fmt.Println("Even elements of slice are: ", even)
}
Isto é muito útil quando utilizamos interfaces. Como você pode ver `testInt` é uma variável que tem tipo de função, e os valores de retorno e argumentos de `filter` são os mesmos de `testInt`. Portanto, podemos ter lógica complexa em nossos programas mantendo a flexibilidade em nosso código.
### Panic e Recover
Go não possui a estrutura `try-catch` assim como Java. Em vez de lançar exceções, Go usa `panic` e `recover` para lidar com erros. No entanto, embora poderoso, você não deve utilizar a instrução `panic` muito.
Panic é uma função interna para quebrar o fluxo normal de programas e entrar em estado de pânico. Quando uma função `F` chama `panic`, `F` não continuará executando, mas suas funções `defer` continuarão a ser executadas. Então `F` volta ao ponto de interrupção que causou o estado de pânico. O programa não terminará até que todas essas funções retornem com panic para o primeiro nível da `goroutine`. `panic` pode ser gerado executando a instrução `panic` no programa, e alguns erros também causam `panic`, como, por exemplo, a tentativa de acessar uma posição inválida em um array.
Recover é uma função interna utilizada para recuperar goroutines de estados de pânico. Chamar `recover` nas funções `defer` é útil porque as funções normais não serão executadas quando o programa estiver em estado de pânico. Ele recebe os valores de `panic` se o programa está em estado de pânico, e recebe `nil` se o programa não está em estado de pânico.
O seguinte exemplo mostra como utilizar `panic`.
var user = os.Getenv("USER")
func init() {
if user == "" {
panic("no value for $USER")
}
}
O seguinte exemplo mostra como verificar `panic`.
func throwsPanic(f func()) (b bool) {
defer func() {
if x := recover(); x != nil {
b = true
}
}()
f() // se f causar pânico, ele irá recuperar
return
}
### Função `main` e função `init`
Go possui duas retenções que são chamadas de `main` e `init`, onde `init` pode ser usada em todos os pacotes e `main` só pode ser usada no pacote `main`. Estas duas funções não são capazes de ter argumentos ou valores de retorno. Mesmo que possamos escrever muitas funções `init` em um pacote, recomendo fortemente escrever apenas uma função `init` para cada pacote.
Programas em Go irão chamar as funções `init()` e `main()` automaticamente, então você não precisa se preocupar em chamá-las. Para cada pacote, a função `init` é opcional, mas o `package main` tem uma e apenas uma função `main`.
Programas inicializam e começam a execução a partir do pacote `main`. Se o pacote `main` importa outros pacotes, eles serão importados em tempo de compilação. Se um pacote é importado muitas vezes, ele será compilado apenas uma vez. Depois de importar pacotes, os programas irão inicializar as constantes e variáveis dentro dos pacotes importados, e então executar a função `init` se ela existir, e assim por diante. Depois de todos os outros pacotes serem inicializados, os programas irão inicializar as constantes e variáveis do pacote `main` e então executar a função `init` dentro do pacote, se ela existir. A figura a seguir mostra o processo.
![](images/2.3.init.png?raw=true)
Figure 2.6 Fluxo de inicialização de programas em Go
### import
Usamos `import` muito frequentemente em programas Go da seguinte forma.
import(
"fmt"
)
Então, usamos funções deste pacote da seguinte maneira.
fmt.Println("hello world")
`fmt` é da biblioteca padrão Go, que está localizada em $GOROOT/pkg. Go suporta pacotes de terceiros de duas maneiras.
1. Caminho relativo
import "./model" // carrega o pacote no mesmo diretório, eu não recomendo utilizar esta forma.
2. Caminho absoluto
import "shorturl/model" // carrega o pacote no caminho "$GOPATH/pkg/shorturl/model"
Existem alguns operadores especiais quando importamos pacotes, e iniciantes na linguagem sempre se confundem com estes operadores.
1. Operador: ponto.
Às vezes vemos pessoas usando a seguinte forma para importar pacotes.
import(
. "fmt"
)
O operador ponto significa que você pode omitir o nome do pacote quando você chamar funções dentro deste pacote. Assim sendo, `fmt.Printf("Hello world")` torna-se `Printf("Hello world")`.
2. Operador: pseudônimo (alias)
Ele altera o nome do pacote que importamos quando chamamos funções que pertencem a este pacote.
import(
f "fmt"
)
Assim sendo, `fmt.Printf("Hello world")` torna-se `f.Printf("Hello world")`.
3. Operador: `_`.
Este operador é difícil de entender sem alguém explicando para você.
import (
"database/sql"
_ "github.com/ziutek/mymysql/godrv"
)
O operador `_` na verdade significa que só queremos importar este pacote e executar sua função `init`, e não temos certeza se queremos usar as funções pertencentes a este pacote.
## Links
- [Sumário](preface.md)
- Seção anterior: [Fundamentos em Go](02.2.md)
- Próxima seção: [struct](02.4.md)