14 min read

[Jornada do DevOps] #2 – Básico do Go

[Jornada do DevOps] #2 – Básico do Go
Photo by Chinmay Bhattar / Unsplash

Nesse posts meu objetivo é mostrar os estudos e aprendizados do Go. Seguindo o roadmap do Go, o primeiro passo são os itens básicos da linguagem que são eles:

  • Basic Syntax
  • Variables e declaration
  • Data types
  • Conditionals
  • Functions
  • Array, maps and slices
  • Structs

Sem enrolação, borá!

Conceitos básicos

Diferente de muitas linguagens, o GO é uma linguagem compilada e não processada.

Sobre estrutura de pastas, nada obriga seguir uma. Cada pasta se torna um package (pacote), agrupando código de acordo com a relevância, como no Python ou com as namespace no PHP.

O Go é fortemente tipada, por exemplo, não conseguimos iniciar uma variável do tipo Int e depois alterar para String; não conseguimos comparar dado de tipos diferentes; e não tem conceito de orientação a objetos.

A linguagem é bem versásil, podemos criar APIs, Web Apps, Producers, etc, etc...

A performance do Go é extremamente alta por ser compilada.

Importante:

  • Todo programa Go precisa ter um pacote principal (package main) no main.go
  • Não pode ter dois packages dentro de uma pasta (criar sub-pastas)
  • A func main(){} é usada para iniciar o programa

Criei um repositório no Github para upload dos estudos.

Basic Syntax

Depois de entender o básico no Getting Started da documentação. Agora preciso entender os outros blocos de construção básicos da linguagem de programação Go. Vamos ver?

Tokens

Um programa Go consiste em vários tokens. Um token é uma palavra-chave, um identificador, uma constante, uma string literal ou um símbolo. Por exemplo, a seguinte instrução Go consiste em seis tokens:

fmt.Println("Hello, World!")

Os tokens individuais são:

fmt
.
Println
(
   "Hello, World!"
)

Separador de linha

Em um programa Go, a chave separadora de linha é um terminador de instrução. Ou seja, instruções individuais não precisam de um separador especial como “;” em C. O compilador Go coloca internamente “;” como o terminador de instrução para indicar o fim de uma entidade lógica.

Por exemplo:

fmt.Println("Hello, World!")
fmt.Println("I am in Go Programming World!")

Comentários

Comentários são como textos de ajuda como qualquer outra linguagem tem e são ignorados pelo compilador. Eles começam com /* e terminam com os caracteres */ como mostrado abaixo:

/* my first program in Go */

Você não pode ter comentários dentro de comentários e eles não ocorrem dentro de uma string ou literais de caracteres.

Importante: não pode ter comentários dentro de comentários e eles não ocorrem dentro de uma string ou de caracteres.

Identificadores

Um identificador Go é um nome usado para identificar uma variável, função ou qualquer outro item. Um identificador começa com uma letra A a Z ou a a z ou um sublinhado _ seguido por zero ou mais letras, sublinhados e dígitos (0 a 9).

identificador = letra { letra | unicode_digit }.

Go não permite caracteres de pontuação como @, $ e % em identificadores. Go é uma linguagem de programação sensível a maiúsculas e minúsculas.

Espaço em branco em Go

Espaço em branco é o termo usado em Go para descrever espaços em branco, tabulações, caracteres de nova linha e comentários. Uma linha contendo apenas espaços em branco, possivelmente com um comentário, é conhecida como linha em branco, e o compilador Go a ignora totalmente.

Fontes do Tópico:

Variables e declaration

Go é uma linguagem de tipagem estática. Isso significa que você precisa declarar o tipo de variável ao declarar a variável, e isso não pode ser alterado.

Este ponto também torna Go rápido, porque o compilador não precisa identificar o tipo de variável em tempo de execução.

Existem três maneiras de declarar uma variável.

A primeira é a declaração com a especificação manual do tipo para a variável, a segunda e a terceira são a declaração sem especificar manualmente os tipos, mas usa a instrução após o sinal de igual definir o tipo. Para o terceiro, ele precisa ter dois pontos (:) para declarar a variável. Sem os dois pontos, o compilador lançará um erro se você não tiver declarado a variável antes.

Você também pode declarar uma variável global declarando uma variável fora de uma função para permitir que todas as funções dentro do pacote acessem a variável global.

Além disso, você pode permitir que um pacote acesse a variável global de outro pacote colocando em maiúscula a primeira letra da variável global.

Tipos de variáveis ​​Go

Em Go, existem diferentes tipos de variáveis, por exemplo:

  • int - armazena inteiros (números inteiros), como 123 ou -123
  • float32 - armazena números de ponto flutuante, com decimais, como 19,99 ou -19,99
  • string - armazena texto, como "Hello World". Os valores de string são cercados por aspas duplas
  • bool - armazena valores com dois estados: verdadeiro ou falso

Exemplos:

package main

import "fmt"

func main() {
  a := 1    // var a int
  b := 3.14 // var b float
  c := "hi" // var c string
  d := true // var d bool
  fmt.Println(a, b, c, d)

  e := []int{1, 2, 3} // slice
  e = append(e, 4)
  fmt.Println(e, len(e), e[0], e[1:3], e[1:], e[:2])
  
  f := make(map[string]int) // map
  f["one"] = 1
  f["two"] = 2
  fmt.Println(f, len(f), f["one"], f["three"])
}

/*
>>> OUTPUT <<<
1 3.14 hi true
[1 2 3 4] 4 1 [2 3] [2 3 4] [1 2]
map[one:1 two:2] 2 1 0
*/

Declaração de variável sem valor inicial

Em Go, todas as variáveis ​​são inicializadas. Portanto, se você declarar uma variável sem valor inicial, seu valor será definido como o valor padrão de seu tipo:

package main
import ("fmt")

func main() {
  var a string
  var b int
  var c bool

  fmt.Println(a)
  fmt.Println(b)
  fmt.Println(c)
}

Neste exemplo existem 3 variáveis:

  • a
  • b
  • c

Essas variáveis ​​são declaradas, mas não foram atribuídos valores iniciais.

Ao executar o código, podemos ver que eles já possuem os valores padrão de seus respectivos tipos:

  • a é ""
  • b é 0
  • c é false

Atribuição de valor após declaração

É possível atribuir um valor a uma variável após ela ser declarada. Isso é útil para casos em que o valor não é conhecido inicialmente.

package main
import ("fmt")

func main() {
  var student1 string
  student1 = "John"
  fmt.Println(student1)
}

Regras de nomenclatura de variáveis ​​Go

Uma variável pode ter um nome curto (como x e y) ou um nome mais descritivo (idade, preço, nome do carro, etc.).

Regras de nomenclatura de variáveis ​​Go:

  • Um nome de variável deve começar com uma letra ou um caractere de sublinhado (_)
  • Um nome de variável não pode começar com um dígito
  • Um nome de variável pode conter apenas caracteres alfanuméricos e sublinhados ( a-z, A-Z,  0-9 , e  _)
  • Os nomes das variáveis ​​diferenciam maiúsculas de minúsculas (idade, idade e IDADE são três variáveis ​​diferentes)
  • Não há limite no comprimento do nome da variável
  • Um nome de variável não pode conter espaços
  • O nome da variável não pode ser nenhuma palavra-chave Go

Fontes do Tópico:

Data types

O tipo de dados é um conceito importante na programação. O tipo de dados especifica o tamanho e o tipo dos valores das variáveis.

Go é estaticamente tipado, o que significa que uma vez que um tipo de variável é definido, ele só pode armazenar dados desse tipo.

Go tem três tipos básicos de dados:

  • bool: representa um valor booleano e é verdadeiro ou falso
  • Numérico: representa tipos inteiros, valores de ponto flutuante e tipos complexos
  • string: representa um valor de string
package main
import ("fmt")

func main() {
  var a bool = true     // Boolean
  var b int = 5         // Integer
  var c float32 = 3.14  // Floating point number
  var d string = "Hi!"  // String

  fmt.Println("Boolean: ", a)
  fmt.Println("Integer: ", b)
  fmt.Println("Float:   ", c)
  fmt.Println("String:  ", d)
}

Definindo algumas varíaveis a nivel de package:

package main
import ("fmt")

var (
	nome string
    n1 int
    n2 int
)

func main() {
	nome = "Gustavo"
    fmt.Println("Hello", nome, "!")
}

Aqui não tem segredo! É o tipo que definimos a varíavel.

Importante: varíaveis a nível de package, fora de função ela fica disponível para qualquer função ou package.

Fontes do Tópico:

Conditions

Acredito que as condições sejam parecidas com outras linguagens.

As instruções condicionais são usadas para executar diferentes ações com base em diferentes condições.

Uma condição pode ser true ou  false.

Go suporta os operadores de comparação usuais da matemática:

  • Menor que <
  • Menor ou igual <=
  • Maior que >
  • Maior ou igual >=
  • Igual a ==
  • Não igual a !=

Além disso, Go suporta os operadores lógicos usuais :

  • E Lógico &&
  • OU lógico ||
  • NÃO Lógico !

Você pode usar esses operadores ou suas combinações para criar condições para diferentes decisões.

Exemplos:

package main

import "fmt"

func main() {
  // if, else
  a := 5
  if a > 3 {
    a = a - 3
  } else if a == 3 {
    a = 0
  } else {
    a = a + 3
  }
  fmt.Println(a)
  
  // switch, case
  b := "NO"
  switch b {
  case "YES":
    b = "Y"
  case "NO":
    b = "N"
  default:
    b = "X"
  }
  fmt.Println(b)
}

/*
>>> OUTPUT <<<
2
N
*/

Em Go, existem instruções de fluxo de controle como outras linguagens: if, else, switch, case , mas há apenas uma instrução de loop em Go que é “ for”. Porque você pode substituir a instrução “while” pela instrução “ for” , como no exemplo abaixo.

Fontes do Tópico:

Functions

Uma função é um bloco de instruções que pode ser usado repetidamente em um programa.

Uma função não será executada automaticamente quando uma página for carregada.

Uma função será executada por uma chamada para a função.

A função Go tem 4 partes.

  1. Nome: Deve ser nomeado em camelCase / CamelCase.
  2. Argumentos: Uma função pode receber zero ou mais argumentos. Para dois ou mais argumentos consecutivos com o mesmo tipo, você pode definir o tipo na parte de trás do último argumento (como “ string” no exemplo).
  3. Tipos de retorno: uma função pode retornar zero ou mais valor. Se retornar mais de um valor, você precisa cobri-los com parênteses.
  4. Corpo: É uma lógica de uma função.

Criar uma função

Para criar (frequentemente chamado de declare) uma função, faça o seguinte:

  • Use a palavra - func chave.
  • Especifique um nome para a função, seguido de parênteses ().
  • Por fim, adicione o código que define o que a função deve fazer, entre chaves {}.
func FuncaoNome() {
  // code para executar
}

Valores de retorno de nome

package main

import "fmt"

func main() {
  fmt.Println(threeTimes("Thank You"))
}

func threeTimes(msg string) (tMsg string) {
  tMsg = msg + ", " + msg + ", " + msg
  return
}

/*
>>> OUTPUT <<<
Thank You, Thank You, Thank You
*/

Você também pode nomear valores de resultado de uma função. Portanto, você não precisa declarar variáveis ​​retornadas e definir o que a função retorna na instrução return. Nesse caso, você precisa colocar parênteses nos tipos de retorno, embora haja apenas um argumento.

Funções exportadas/não exportadas

package main

import "fmt"

func main() {
  s := Sum(10)
  f := factorial(10)
  fmt.Println(s, f)
}

func Sum(n int) int { // exported function
  sum := 0
  for i := 1; i <= n; i++ {
     sum += i
  }
  return sum
}

func factorial(n int) int { // unexported function
  fac := 1
  for i := 1; i <= n; i++ {
    fac *= 1
  }
  return fac
}

/*
>>> OUTPUT <<<
55 3628800
*/

Assim como a variável e outros identificadores, as funções podem ser exportadas e não exportadas colocando a primeira letra do nome em maiúscula.

Funções anônimas

package main

import "fmt"

func main() {
  a := 1
  b := 1
  c := func(x int) int {
    b *= 2
    return x * 2
  }(a)
  fmt.Println(a, b, c)
}

/*
>>> OUTPUT <<<
1 2 2
*/

Você pode declarar uma função dentro de outra função e executá-la imediatamente após declará-la. É chamado de Função Anônima que pode acessar o escopo de seu pai. Neste caso, a função anônima pode acessar a variável b que no escopo da função principal:

package main

import "fmt"

func main() {
  myFunc := func(x int) int {
    return x * x
  }
  fmt.Println(myFunc(2), myFunc(3))
}

/*
>>> OUTPUT <<<
4 9
*/

Como você pode ver no exemplo acima, a função pode ser atribuída a uma variável para ser reutilizável.

Identificadores em branco

package main

import "fmt"

func main() {
  x, _ := evenOnly(10)
  fmt.Println(x)
}

func evenOnly(n int) (int, error) {
  if n%2 == 0 {
    return n / 2, nil
  }
  return 0, fmt.Errorf("not even")
}

/*
>>> OUTPUT <<<
5
*/

Go é muito rigoroso para variáveis ​​não utilizadas, você não pode compilar o código com nenhuma variável não utilizada. Algumas funções retornam vários valores e alguns valores não são usados. Assim, Go fornece um “identificador em branco” para substituir variáveis ​​não utilizadas, então o código pode ser cumprido.

Chamar uma função

As funções não são executadas imediatamente. Eles são "salvos para uso posterior" e serão executados quando forem chamados.

No exemplo abaixo, criamos uma função chamada "myMessage()". A chave de abertura ( { ) indica o início do código da função e a chave de fechamento ( } ) indica o fim da função. A função exibe "Acabei de ser executado!". Para chamar a função, basta escrever seu nome seguido de dois parênteses ():

package main
import ("fmt")

func minhaMensagem() {
  fmt.Println("Vai executar!")
}

func main() {
  minhaMensagem() // chama a func
}

Regras de nomenclatura para funções Go

  • Um nome de função deve começar com uma letra
  • Um nome de função pode conter apenas caracteres alfanuméricos e sublinhados ( A-z,  0-9,  e  _ )
  • Os nomes das funções diferenciam maiúsculas de minúsculas
  • Um nome de função não pode conter espaços
  • Se o nome da função consiste em várias palavras, as técnicas introduzidas para nomeação de variáveis ​​de várias palavras podem ser usadas

Dica: Dê à função um nome que reflita o que a função faz!

Fontes do Tópico:

Array, maps and slices

Arrays

Arrays são usados ​​para armazenar vários valores do mesmo tipo em uma única variável, em vez de declarar variáveis ​​separadas para cada valor.

Em Go, existem duas maneiras de declarar um array:

Com a var palavra-chave:

var array_name = [length]datatype{values} // here length is defined

or

var array_name = [...]datatype{values} // here length is inferred

Com o := sinal:

array_name := [length]datatype{values} // here length is defined

or

array_name := [...]datatype{values} // here length is inferred


Um exemplo:

package main
import ("fmt")

func main() {
  var arr1 = [3]int{1,2,3}
  arr2 := [5]int{4,5,6,7,8}

  fmt.Println(arr1)
  fmt.Println(arr2)
}

Acessar Elementos de um Array

Você pode acessar um elemento específico da matriz referindo-se ao número do índice, como em quase todas linguagens.

Em Go, os índices de array começam em 0. Isso significa que [0] é o primeiro elemento, [1] é o segundo elemento, etc.

package main
import ("fmt")

func main() {
  prices := [3]int{10,20,30}

  fmt.Println(prices[0])
  fmt.Println(prices[2])
}

Alterar elementos de um array

Você também pode alterar o valor de um elemento de matriz específico referindo-se ao número do índice.

package main
import ("fmt")

func main() {
  prices := [3]int{10,20,30}

  prices[2] = 50
  fmt.Println(prices)
}

Inicialização do array

Se um array ou um de seus elementos não foi inicializado no código, é atribuído o valor padrão de seu tipo.

package main
import ("fmt")

func main() {
  arr1 := [5]int{} //not initialized
  arr2 := [5]int{1,2} //partially initialized
  arr3 := [5]int{1,2,3,4,5} //fully initialized

  fmt.Println(arr1)
  fmt.Println(arr2)
  fmt.Println(arr3)
}

Inicializar apenas elementos específicos

É possível inicializar apenas elementos específicos em uma matriz.

package main
import ("fmt")

func main() {
  arr1 := [5]int{1:10,2:40}

  fmt.Println(arr1)
}

Encontrar o comprimento de uma matriz

A len()função é usada para encontrar o comprimento de uma matriz:

package main
import ("fmt")

func main() {
  arr1 := [4]string{"Volvo", "BMW", "Ford", "Mazda"}
  arr2 := [...]int{1,2,3,4,5,6}

  fmt.Println(len(arr1))
  fmt.Println(len(arr2))
}

Maps

Os mapas são usados ​​para armazenar valores de dados em pares chave:valor.

Cada elemento em um mapa é um par chave:valor.

Um mapa é uma coleção não ordenada e mutável que não permite duplicatas.

O comprimento de um mapa é o número de seus elementos. Você pode encontrá-lo usando a len() função.

O valor padrão de um mapa é nulo.

Os mapas contêm referências a uma tabela de hash subjacente.

Go tem várias maneiras de criar mapas.

Criando maps usando vare:=

package main
import ("fmt")

func main() {
  var a = map[string]string{"brand": "Ford", "model": "Mustang", "year": "1964"}
  b := map[string]int{"Oslo": 1, "Bergen": 2, "Trondheim": 3, "Stavanger": 4}

  fmt.Printf("a\t%v\n", a)
  fmt.Printf("b\t%v\n", b)
}

Criando maps usando função make():

package main
import ("fmt")

func main() {
  var a = make(map[string]string) // The map is empty now
  a["brand"] = "Ford"
  a["model"] = "Mustang"
  a["year"] = "1964"
                                 // a is no longer empty
  b := make(map[string]int)
  b["Oslo"] = 1
  b["Bergen"] = 2
  b["Trondheim"] = 3
  b["Stavanger"] = 4

  fmt.Printf("a\t%v\n", a)
  fmt.Printf("b\t%v\n", b)
}

Criando um map vazio

package main
import ("fmt")

func main() {
  var a = make(map[string]string)
  var b map[string]string

  fmt.Println(a == nil)
  fmt.Println(b == nil)
}

Tipos de chave permitidos:

A chave de mapa pode ser de qualquer tipo de dados para o qual o operador de igualdade ( == ) esteja definido. Esses incluem:

  • Booleanos
  • Números
  • Cordas
  • Matrizes
  • Ponteiros
  • Estruturas
  • Interfaces (desde que o tipo dinâmico suporte igualdade)

Os tipos de chave inválidos são:

  • Fatias
  • Mapas
  • Funções

Esses tipos são inválidos porque o operador de igualdade ( == ) não está definido para eles.

Tipos de valores permitidos: os valores do mapa podem ser de qualquer tipo.

Acessando elementos do map

package main
import ("fmt")

func main() {
  var a = make(map[string]string)
  a["brand"] = "Ford"
  a["model"] = "Mustang"
  a["year"] = "1964"

  fmt.Printf(a["brand"])
}

Atualizando elemento do map:

map_name[key] = value

Removendo elemento do map:

delete(map_name, key)

Iterando em maps

package main
import ("fmt")

func main() {
  a := map[string]int{"one": 1, "two": 2, "three": 3, "four": 4}

  for k, v := range a {
    fmt.Printf("%v : %v, ", k, v)
  }
}

Slices

As "fatias" são semelhantes às matrizes, mas são mais poderosas e flexíveis.

Assim como as matrizes, as fatias também são usadas para armazenar vários valores do mesmo tipo em uma única variável.

No entanto, ao contrário das matrizes, o comprimento de uma fatia pode aumentar e diminuir conforme você achar melhor.

Em Go, existem várias maneiras de criar uma fatia:

  • Usando o formato de tipo de dados [] { valores }
  • Criar uma fatia de uma matriz
  • Usando a função make()

Criar slice:

slice_name := []datatype{values}

Maneira comum:

myslice := []int

Para inicializar a fatia durante a declaração:

myslice := []int{1,2,3}

O código acima declara uma fatia de inteiros de comprimento 3 e também a capacidade de 3.

Em Go, existem duas funções que podem ser usadas para retornar o comprimento e a capacidade de uma fatia:

  • len() função - retorna o comprimento da fatia (o número de elementos na fatia)
  • cap() função - retorna a capacidade da fatia (o número de elementos que a fatia pode aumentar ou diminuir)

Exemplo:

package main
import ("fmt")

func main() {
  myslice1 := []int{}
  fmt.Println(len(myslice1))
  fmt.Println(cap(myslice1))
  fmt.Println(myslice1)

  myslice2 := []string{"Go", "Slices", "Are", "Powerful"}
  fmt.Println(len(myslice2))
  fmt.Println(cap(myslice2))
  fmt.Println(myslice2)
}

Fontes do Tópico:

Structs

Um struct (abreviação de estrutura) é usado para criar uma coleção de membros de diferentes tipos de dados em uma única variável.

Enquanto as matrizes são usadas para armazenar vários valores do mesmo tipo de dados em uma única variável, as estruturas são usadas para armazenar vários valores de diferentes tipos de dados em uma única variável.

Um struct pode ser útil para agrupar dados para criar registros.

Para declarar uma estrutura em Go, use as palavras-chave type e struct:

type Person struct {
  name string
  age int
  job string
  salary int
}

Acessar membros da estrutura

Para acessar qualquer membro de uma estrutura, use o operador ponto (.) entre o nome da variável da estrutura e o membro da estrutura:

package main
import ("fmt")

type Person struct {
  name string
  age int
  job string
  salary int
}

func main() {
  var pers1 Person
  var pers2 Person

  // Pers1 specification
  pers1.name = "Hege"
  pers1.age = 45
  pers1.job = "Teacher"
  pers1.salary = 6000

  // Pers2 specification
  pers2.name = "Cecilie"
  pers2.age = 24
  pers2.job = "Marketing"
  pers2.salary = 4500

  // Access and print Pers1 info
  fmt.Println("Name: ", pers1.name)
  fmt.Println("Age: ", pers1.age)
  fmt.Println("Job: ", pers1.job)
  fmt.Println("Salary: ", pers1.salary)

  // Access and print Pers2 info
  fmt.Println("Name: ", pers2.name)
  fmt.Println("Age: ", pers2.age)
  fmt.Println("Job: ", pers2.job)
  fmt.Println("Salary: ", pers2.salary)
}

Passar Estrutura como Argumentos de Função

package main
import ("fmt")

type Person struct {
  name string
  age int
  job string
  salary int
}

func main() {
  var pers1 Person
  var pers2 Person

  // Pers1 specification
  pers1.name = "Hege"
  pers1.age = 45
  pers1.job = "Teacher"
  pers1.salary = 6000

  // Pers2 specification
  pers2.name = "Cecilie"
  pers2.age = 24
  pers2.job = "Marketing"
  pers2.salary = 4500

  // Print Pers1 info by calling a function
  printPerson(pers1)

  // Print Pers2 info by calling a function
  printPerson(pers2)
}

func printPerson(pers Person) {
  fmt.Println("Name: ", pers.name)
  fmt.Println("Age: ", pers.age)
  fmt.Println("Job: ", pers.job)
  fmt.Println("Salary: ", pers.salary)
}


Fontes do Tópico:

Conclusão:

Tem muita coisa para aprender, somente nessa seção do Go.

Os aprendizados estou publicando no repositório dos estudos de Go.

No próximo post vamos entrar mais afundo (Going Deeper) na linguagem, seguindo o roadmap.