457 lines
19 KiB
Markdown
457 lines
19 KiB
Markdown
# 2.2 Fundamentos em Go
|
||
|
||
Nesta seção vamos aprender como definir constantes, variáveis com tipos elementares e algumas habilidades de programação em Go.
|
||
|
||
## Definir variáveis
|
||
|
||
Existem diversas formas de sintaxe que podem ser utilizadas para definir variáveis em Go.
|
||
|
||
A palavra-chave `var` é a forma mais básica de definir variáveis. Observe que a linguagem Go coloca o tipo da variável `depois` do nome da variável.
|
||
|
||
// define uma variável com o nome “variableName” e o tipo "type"
|
||
var variableName type
|
||
|
||
Definir múltiplas variáveis.
|
||
|
||
// define três variáveis do tipo "type"
|
||
var vname1, vname2, vname3 type
|
||
|
||
Definir uma variável com um valor inicial.
|
||
|
||
// define uma variável com o nome “variableName”, tipo "type" e valor "value"
|
||
var variableName type = value
|
||
|
||
Definir múltiplas variáveis com valores iniciais.
|
||
|
||
/*
|
||
Define três variáveis de tipo "type" e inicializa seus valores.
|
||
vname1 recebe v1, vname2 recebe v2, vname3 recebe v3
|
||
*/
|
||
var vname1, vname2, vname3 type = v1, v2, v3
|
||
|
||
Você acha muito tedioso definir variáveis desta maneira? Não se preocupe porque a equipe da Go também achou que isto poderia ser um problema. Portanto, se você deseja definir variáveis com valores iniciais, pode simplesmente omitir o tipo da variável. Sendo assim, o código ficará da seguinte forma:
|
||
|
||
/*
|
||
Define três variáveis sem o tipo "type" e inicializa seus valores.
|
||
vname1 recebe v1, vname2 recebe v2, vname3 recebe v3
|
||
*/
|
||
var vname1, vname2, vname3 = v1, v2, v3
|
||
|
||
Bem, talvez isto ainda não seja simples o suficiente para você. Vamos ver como podemos melhorar.
|
||
|
||
/*
|
||
Define três variáveis sem o tipo "type", sem a palavra-chave "var" e inicializa seus valores.
|
||
vname1 recebe v1, vname2 recebe v2, vname3 recebe v3
|
||
*/
|
||
vname1, vname2, vname3 := v1, v2, v3
|
||
|
||
Agora parece muito melhor. Use `:=` para substituir `var` e `type`. Isto é chamado de uma "declaração curta" (do inglês: brief statement). Mas espere, isto possui uma limitação: esta forma só pode ser utilizada dentro de funções. Você receberá erros de compilação se tentar utilizar isto fora do corpo de uma função. Portanto, normalmente utilizamos `var` para definir variáveis globais e podemos utilizar esta declaração curta em `var()`.
|
||
|
||
`_` (blank) é um nome especial de variável. Qualquer valor que seja atribuído a isto será ignorado. Por exemplo, vamos atribuir o valor `35` a variável `b` e descartar o valor `34`.( ***Este exemplo apenas mostra como isto funciona. Ele parece inútil aqui porque frequentemente utilizamos este símbolo quando recebemos valores de retorno de uma função. *** )
|
||
|
||
_, b := 34, 35
|
||
|
||
Se você não utilizar variáveis que foram definidas no seu programa, o compilador irá gerar erros de compilação. Tente compilar o seguinte código e veja o que acontece.
|
||
|
||
package main
|
||
|
||
func main() {
|
||
var i int
|
||
}
|
||
|
||
## Constantes
|
||
|
||
Constantes são os valores que são determinados durante o tempo de compilação e você não pode alterá-los durante a execução. Em Go, você pode utilizar número, booleano ou string como tipos de constantes.
|
||
|
||
Defina as constantes da seguinte forma.
|
||
|
||
const constantName = value
|
||
// você pode atribuir o tipo da constante se for necessário
|
||
const Pi float32 = 3.1415926
|
||
|
||
Mais exemplos.
|
||
|
||
const Pi = 3.1415926
|
||
const i = 10000
|
||
const MaxThread = 10
|
||
const prefix = "astaxie_"
|
||
|
||
## Tipos elementares
|
||
|
||
### Booleano
|
||
|
||
Em Go, utilizamos `bool` para definir uma variável do tipo booleano, sendo que o valor só pode ser `true` ou `false`, e o valor padrão será `false`. ( ***Você não pode converter tipos de variáveis' entre número e booleano!*** )
|
||
|
||
// código de amostra
|
||
var isActive bool // variável global
|
||
var enabled, disabled = true, false // omite o tipo das variáveis
|
||
func test() {
|
||
var available bool // variável local
|
||
valid := false // declaração curta de variável
|
||
available = true // atribui um valor à variável
|
||
}
|
||
|
||
### Tipos numéricos
|
||
|
||
Tipos inteiros incluem tipos com sinais e sem sinais. Go possui os tipos `int` e `uint`, os quais possuem o mesmo comprimento, porém, o comprimento específico depende do sistema operacional. Eles utilizam 32 bits em sistemas operacionais 32 bits e 64 bits em sistemas operacionais de 64 bits. Go também têm tipos que possuem comprimentos específicos, incluindo `rune`, `int8`, `int16`, `int32`, `int64`, `byte`, `uint8`, `uint16`, `uint32`, `uint64`. Note que `rune` é um pseudônimo de `int32` e `byte` é um pseudônimo de `uint8`.
|
||
|
||
Uma coisa importante que você deve saber é que você não pode atribuir valores entre estes tipos, esta operação irá gerar erros de compilação.
|
||
|
||
var a int8
|
||
|
||
var b int32
|
||
|
||
c := a + b
|
||
|
||
Embora int32 tenha um comprimento maior do que int8, e possui o mesmo tipo int, você não pode atribuir valores entre eles. ( ***neste caso, c será declarado como tipo `int`*** )
|
||
|
||
Tipos float possuem os tipos `float32` e `float64` e nenhum tipo chamado `float`. O último é o tipo padrão utilizado em declarações curtas.
|
||
|
||
Isto é tudo? Não! Go também suporta números complexos. `complex128` (com uma parte de 64 bits real e uma parte de 64 bits imaginária) é o tipo padrão, mas se você precisa de um tipo menor, existe um tipo chamado `complex64` (com uma parte de 32 bits real e uma parte de 32 bits imaginária).
|
||
|
||
Sua forma é `RE+IMi`, onde `RE` é a parte real e `IM` é a parte imaginária, e o último `i` é o número imaginário. Há um exemplo de número complexo.
|
||
|
||
var c complex64 = 5+5i
|
||
// saída: (5+5i)
|
||
fmt.Printf("Value is: %v", c)
|
||
|
||
### String
|
||
|
||
Nós falamos apenas sobre como a linguagem Go utiliza o conjunto de caracteres UTF-8. As strings são representadas por aspas duplas `""` ou crases (backticks) ``` `` ```.
|
||
|
||
// código de amostra
|
||
var frenchHello string // forma básica de definir string
|
||
var emptyString string = "" // define uma string vazia
|
||
func test() {
|
||
no, yes, maybe := "no", "yes", "maybe" // declaração curta
|
||
japaneseHello := "Ohaiou"
|
||
frenchHello = "Bonjour" // forma básica de atribuir valores
|
||
}
|
||
|
||
É impossível alterar valores de string pelo índice. Você receberá erros ao compilar o seguinte código.
|
||
|
||
var s string = "hello"
|
||
s[0] = 'c'
|
||
|
||
E se eu realmente quiser alterar apenas um caractere de uma string? Tente o seguinte código.
|
||
|
||
s := "hello"
|
||
c := []byte(s) // converte string para o tipo []byte
|
||
c[0] = 'c'
|
||
s2 := string(c) // converte novamente para o tipo string
|
||
fmt.Printf("%s\n", s2)
|
||
|
||
Use o operador `+` para combinar duas strings.
|
||
|
||
s := "hello,"
|
||
m := " world"
|
||
a := s + m
|
||
fmt.Printf("%s\n", a)
|
||
|
||
e também.
|
||
|
||
s := "hello"
|
||
s = "c" + s[1:] // você não pode alterar valores da string pelo índice, mas você pode obter os valores
|
||
fmt.Printf("%s\n", s)
|
||
|
||
E se eu quiser ter uma string de múltiplas linhas?
|
||
|
||
m := `hello
|
||
world`
|
||
|
||
`` ` não irá escapar nenhum caractere da string.
|
||
|
||
### Tipos error
|
||
|
||
Go possui um tipo `error` com o propósito de lidar com mensagens de erro. Há também um pacote chamado `errors` para lidar com os erros.
|
||
|
||
err := errors.New("emit macho dwarf: elf header corrupted")
|
||
if err != nil {
|
||
fmt.Print(err)
|
||
}
|
||
|
||
### Estrutura de dados subjacente
|
||
|
||
A seguinte imagem vem de um artigo sobre [estruturas de dados em Go](http://research.swtch.com/godata) do [Blog Russ Cox](http://research.swtch.com/). Como você pode ver, Go utiliza blocos de memória para armazenar dados.
|
||
|
||

|
||
|
||
Figure 2.1 Estrutura de dados subjacente em Go
|
||
|
||
## Algumas habilidades
|
||
|
||
### Definir por grupo
|
||
|
||
Se você deseja definir múltiplas constantes, variáveis ou importar pacotes, você pode utilizar uma forma de grupo.
|
||
|
||
Forma básica.
|
||
|
||
import "fmt"
|
||
import "os"
|
||
|
||
const i = 100
|
||
const pi = 3.1415
|
||
const prefix = "Go_"
|
||
|
||
var i int
|
||
var pi float32
|
||
var prefix string
|
||
|
||
Forma de grupo.
|
||
|
||
import(
|
||
"fmt"
|
||
"os"
|
||
)
|
||
|
||
const(
|
||
i = 100
|
||
pi = 3.1415
|
||
prefix = "Go_"
|
||
)
|
||
|
||
var(
|
||
i int
|
||
pi float32
|
||
prefix string
|
||
)
|
||
|
||
A menos que você atribua, o valor da constante é `iota`, o primeiro valor de constante no grupo `const()` será `0`. Se as constantes seguintes não atribuirem valores explicitamente, seus valores serão iguais ao último. Se o valor da última constante é `iota`, os valores das constantes seguintes que não são atribuídas será `iota` também.
|
||
|
||
### Enumeração iota
|
||
|
||
Go possui uma palavra-chave chamada `iota`, esta palavra-chave é utilizada para fazer `enum`, ela começa com `0` e aumenta de 1 em 1.
|
||
|
||
const(
|
||
x = iota // x == 0
|
||
y = iota // y == 1
|
||
z = iota // z == 2
|
||
w // se não há nenhuma expressão após o nome da constate, ele usa a última expressão, ou seja, está definindo w = iota implicitamente. Portanto, w == 3, e y e x podem omitir "= iota" também.
|
||
)
|
||
|
||
const v = iota // uma vez que iota encontra a palavra-chave `const`, ela é redefinida para `0`, então v = 0.
|
||
|
||
const (
|
||
e, f, g = iota, iota, iota // e=0,f=0,g=0 valores de iota são iguais em uma linha.
|
||
)
|
||
|
||
### Algumas regras
|
||
|
||
A razão de Go ser concisa é porque ela possui alguns comportamentos padrão.
|
||
|
||
- Qualquer variável que começa com uma letra maiúscula significa que ela será exportada, caso contrário será privada.
|
||
- A mesma regra se aplica para funções e constantes, sendo que não existem as palavras-chave `public` ou `private` em Go.
|
||
|
||
## array, slice, map
|
||
|
||
### array
|
||
|
||
`array` é um array obviamente, e nós definimos ele da seguinte maneira.
|
||
|
||
var arr [n]type
|
||
|
||
em `[n]type`, `n` é o comprimento do array, enquanto `type` é o tipo dos elementos contidos no array. Assim como em outras linguagens, nós utilizamos `[]` para obter ou definir valores de elementos no array.
|
||
|
||
var arr [10]int // um array de tipo [10]int
|
||
arr[0] = 42 // array é baseado em 0
|
||
arr[1] = 13 // atribuir um valor ao elemento
|
||
fmt.Printf("The first element is %d\n", arr[0]) // obtém o valor do elemento, irá retornar 42
|
||
fmt.Printf("The last element is %d\n", arr[9]) // retorna o valor padrão do elemento da posição 10 do array, que, neste caso, é 0.
|
||
|
||
Como o comprimento faz parte do tipo do array, `[3]int` e `[4]int` são tipos diferentes, portanto, não podemos alterar o comprimento dos arrays. Quando você utiliza arrays como argumentos, as funções obtêm suas copias em vez de referências! Se você deseja utilizar referências, você pode utilizar `slice`. Falaremos sobre isto mais tarde.
|
||
|
||
É possível utilizar `:=` quando você define arrays.
|
||
|
||
a := [3]int{1, 2, 3} // define um array de inteiros com 3 elementos
|
||
|
||
b := [10]int{1, 2, 3} // define um array de inteiros com 10 elementos, dos quais os 3 primeiros são atribuídos. O restante deles recebe o valor padrão 0.
|
||
|
||
c := [...]int{4, 5, 6} // usa `…` para substituir o parâmetro de comprimento e a Go irá calcular isto para você.
|
||
|
||
Você pode querer utilizar arrays como elementos de arrays'. Vamos ver como fazer isto.
|
||
|
||
// define um array bidimensional com 2 elementos, e cada elemento possui 4 elementos
|
||
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
|
||
|
||
// A declaração pode ser escrita de forma mais concisa da seguinte forma.
|
||
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
|
||
|
||
Array estrutura de dados subjacente.
|
||
|
||

|
||
|
||
Figure 2.2 Relação de mapeamento de array multidimensional
|
||
|
||
### slice
|
||
|
||
Em várias situações, o tipo array não é uma boa escolha -por exemplo quando não sabemos o comprimento que o array terá quando o definimos. Sendo assim, precisamos de um "array dinâmico". Isto é chamado de `slice` em Go.
|
||
|
||
`slice` não é realmente um `array dinâmico`. É um tipo de referência. `slice` aponta para um `array` subjacente cuja declaração é semelhante ao `array`, porém não precisa de um comprimento preestabelecido.
|
||
|
||
// definimos um slice assim como definimos um array, mas desta vez, omitimos o comprimento
|
||
var fslice []int
|
||
|
||
Então nós definimos um `slice` e inicializamos seus dados.
|
||
|
||
slice := []byte {'a', 'b', 'c', 'd'}
|
||
|
||
`slice` pode redefinir slices ou arrays existentes. `slice` usa `array[i:j]` para "cortar", onde `i` é o índice de início e `j` é o índice final, mas observe que o valor de `array[j]` não será incluído no slice, pois o comprimento da fatia é `j-i`.
|
||
|
||
// define um array com 10 elementos cujos tipos são bytes
|
||
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||
|
||
// define dois slices com o tipo []byte
|
||
var a, b []byte
|
||
|
||
// 'a' aponta para os elementos da terceira até a quinta posição do array ar
|
||
a = ar[2:5]
|
||
// agora 'a' possui os elementos ar[2], ar[3] e ar[4]
|
||
|
||
// 'b' é outro slice do array ar
|
||
b = ar[3:5]
|
||
// agora 'b' possui os elementos ar[3] e ar[4]
|
||
|
||
Observe as diferenças entre `slice` e `array` quando você define eles. Nós utilizamos `[…]` para deixar a linguagem Go calcular o comprimento, mas utilizamos `[]` para definir um slice.
|
||
|
||
Sua estrutura de dados subjacente.
|
||
|
||

|
||
|
||
Figure 2.3 Relação enter slice e array
|
||
|
||
`slice` possui algumas operações convenientes.
|
||
|
||
- `slice` é baseado em 0, `ar[:n]` igual a `ar[0:n]`
|
||
- Se omitido, o segundo índice será o comprimento do `slice`, `ar[n:]` igual a `ar[n:len(ar)]`.
|
||
- Você pode usar `ar[:]` para "cortar" o array inteiro, as razões são explicadas nas duas primeiras declarações.
|
||
|
||
Mais exemplos referentes a `slice`
|
||
|
||
// define um array
|
||
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||
// define dois slices
|
||
var aSlice, bSlice []byte
|
||
|
||
// algumas operações convenientes
|
||
aSlice = array[:3] // igual a aSlice = array[0:3] aSlice possui os elementos a,b,c
|
||
aSlice = array[5:] // igual a aSlice = array[5:10] aSlice possui os elementos f,g,h,i,j
|
||
aSlice = array[:] // igual a aSlice = array[0:10] aSlice possui todos os elementos
|
||
|
||
// slice de um slice
|
||
aSlice = array[3:7] // aSlice possui os elementos d,e,f,g,len=4,cap=7
|
||
bSlice = aSlice[1:3] // bSlice contém aSlice[1], aSlice[2], então têm os elementos e,f
|
||
bSlice = aSlice[:3] // bSlice contém aSlice[0], aSlice[1], aSlice[2], então têm os elementos d,e,f
|
||
bSlice = aSlice[0:5] // slice pode ser expandido no intervalo de cap, agora bSlice contém d,e,f,g,h
|
||
bSlice = aSlice[:] // bSlice têm os mesmos elementos do slice aSlice, os quais são d,e,f,g
|
||
|
||
`slice` é um tipo de referência, portanto, qualquer alteração irá afetar outras variáveis que apontam para o mesmo slice ou array. Por exemplo, no caso dos slices `aSlice` e `bSlice` apresentado acima, se você alterar o valor de um elemento em `aSlice`, `bSlice` também será alterado.
|
||
|
||
`slice` é como uma estrutura por definição, e contém 3 partes.
|
||
|
||
- Um ponteiro que aponta para onde o `slice` inicia.
|
||
- O comprimento do `slice`.
|
||
- Capacidade, o comprimento do índice de início para o índice final do `slice`.
|
||
|
||
Array_a := [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
|
||
Slice_a := Array_a[2:5]
|
||
|
||
Segue a seguir a estrutura subjacente do código acima.
|
||
|
||

|
||
|
||
Figure 2.4 Informações do slice de um array
|
||
|
||
Existem algumas funções incorporadas no slice.
|
||
|
||
- `len` obtém o comprimento do `slice`.
|
||
- `cap` obtém o comprimento máximo do `slice`.
|
||
- `append` acrescenta um ou mais elementos ao `slice`, e retorna um `slice`.
|
||
- `copy` copia elementos de um slice para outro e retorna o número de elementos que foram copiados.
|
||
|
||
Atenção: `append` irá alterar o array para onde o `slice` aponta e afetará também outros slices que apontam para o mesmo array. Além disto, se não houver comprimento suficiente para o slice (`(cap-len) == 0`), `append` retorna um novo array para o slice. Quando isto acontece, outros slices que estão apontando para o array antigo não serão afetados.
|
||
|
||
### map
|
||
|
||
`map` se comporta como um dicionário em Python. Use a forma `map[keyType]valueType` para defini-lo.
|
||
|
||
Vamos ver um pouco de código. Os valores 'set' e 'get' em `map` são semelhantes ao `slice`, no entanto, o índice no `slice` só pode ser do tipo 'int' enquanto `map` pode usar muitos outros tipos, como por exemplo: `int`, `string`, ou qualquer outro que você quiser. Além disto, eles são capazes de usar `==` e `!=` para comparar valores.
|
||
|
||
// use string como o tipo chave, int como o tipo valor e `make` para inicializar.
|
||
var numbers map[string] int
|
||
// outra forma de definir map
|
||
numbers := make(map[string]int)
|
||
numbers["one"] = 1 // atribui valor por chave
|
||
numbers["ten"] = 10
|
||
numbers["three"] = 3
|
||
|
||
fmt.Println("The third number is: ", numbers["three"]) // obtém valores
|
||
// Isto imprime: The third number is: 3
|
||
|
||
Algumas notas sobre o uso de map.
|
||
|
||
- `map` é desordenado. Toda vez que você imprime `map` você terá resultados diferentes. É impossível obter valores por `índice` -você precisa utilizar `chave`.
|
||
- `map` não tem um comprimento fixo. É um tipo de referência, assim como o `slice`.
|
||
- `len` também funciona para `map`. Isto retorna quantas `chave`s o map tem.
|
||
- É muito fácil alterar valores utilizando `map`. Basta usar `numbers["one"]=11` para alterar o valor da `chave` one para `11`.
|
||
|
||
Você pode usar a forma `chave:valor` para inicializar valores em um `map`, pois `map` possui métodos embutidos para verificar se a `chave` existe.
|
||
|
||
Use `delete` para deletar um elemento em um `map`.
|
||
|
||
// Inicializa um map
|
||
rating := map[string]float32 {"C":5, "Go":4.5, "Python":4.5, "C++":2 }
|
||
// map possui dois valores de retorno. Caso a chave não exista, o segundo valor de retorno, neste caso recebido pela variável 'ok', será falso (false). Caso contrário será verdadeiro (true).
|
||
csharpRating, ok := rating["C#"]
|
||
if ok {
|
||
fmt.Println("C# is in the map and its rating is ", csharpRating)
|
||
} else {
|
||
fmt.Println("We have no rating associated with C# in the map")
|
||
}
|
||
|
||
delete(rating, "C") // deleta o elemento com a chave "c"
|
||
|
||
Como foi dito acima, `map` é um tipo de referência. Se dois `map`s apontam para o mesmo dado subjacente, qualquer alteração irá afetar ambos.
|
||
|
||
m := make(map[string]string)
|
||
m["Hello"] = "Bonjour"
|
||
m1 := m
|
||
m1["Hello"] = "Salut" // agora o valor de m["hello"] é Salut
|
||
|
||
### make, new
|
||
|
||
`make` realiza alocação de memória para modelos internos, como `map`, `slice`, e `channel`, enquanto `new` é utilizado para alocação de memória de tipos.
|
||
|
||
`new(T)` aloca a memória para o valor zero do tipo `T` e retorna seu endereço de memória, que é o valor do tipo `*T`. Por definição, isto retorna um ponteiro que aponta para o valor zero de `T`.
|
||
|
||
`new` retorna ponteiros.
|
||
|
||
A função interna `make(T, args)` possui diferentes propósitos se comparado a `new(T)`. `make` pode ser usado para `slice`, `map`, e `channel`, e retorna o tipo `T` com um valor inicial. A razão para fazer isto é porque os dados subjacentes destes três tipos devem ser inicializados antes de apontar para eles. Por exemplo, um `slice` contém um ponteiro que aponta para um `array` subjacente, comprimento e capacidade. Antes que estes dados sejam inicializados, `slice` é `nil`, então, para `slice`, `map` e `channel`, `make` inicializa seus dados subjacentes e atribui alguns valores adequados.
|
||
|
||
`make` retorna valores diferentes de zero.
|
||
|
||
A seguinte imagem mostra como `new` e `make` são diferentes.
|
||
|
||

|
||
|
||
Figure 2.5 Alocação de memória para make e new
|
||
|
||
Valor zero não significa valor vazio. Na maioria dos casos é o valor padrão das variáveis. Aqui está uma lista de alguns valores zero.
|
||
|
||
int 0
|
||
int8 0
|
||
int32 0
|
||
int64 0
|
||
uint 0x0
|
||
rune 0 // o tipo real de rune é int32
|
||
byte 0x0 // o tipo real de byte é uint8
|
||
float32 0 // comprimento é 4 bytes
|
||
float64 0 // comprimento é 8 bytes
|
||
bool false
|
||
string ""
|
||
|
||
## Links
|
||
|
||
- [Sumário](preface.md)
|
||
- Seção anterior: ["Hello, Go"](02.1.md)
|
||
- Próxima seção: [Declarações de controle e funções](02.3.md)
|