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 do Blog Russ Cox. 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
ouprivate
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 aar[0:n]
- Se omitido, o segundo índice será o comprimento do
slice
,ar[n:]
igual aar[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 doslice
.cap
obtém o comprimento máximo doslice
.append
acrescenta um ou mais elementos aoslice
, e retorna umslice
.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ê imprimemap
você terá resultados diferentes. É impossível obter valores poríndice
-você precisa utilizarchave
.map
não tem um comprimento fixo. É um tipo de referência, assim como oslice
.len
também funciona paramap
. Isto retorna quantaschave
s o map tem.- É muito fácil alterar valores utilizando
map
. Basta usarnumbers["one"]=11
para alterar o valor dachave
one para11
.
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
- Seção anterior: "Hello, Go"
- Próxima seção: Declarações de controle e funções