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 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 chaves 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 maps 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  ""

results matching ""

    No results matching ""