Fundamentos da linguagem
Antes de estudar variáveis, tipos e lógica, é bom entender esta estrutura mínima:
package main
import "fmt"
func main() {
fmt.Println("Olá mundo!")
}
Esse pequeno arquivo já mostra três coisas importantes em Go.
package main
Informa a qual pacote esse arquivo pertence.import "fmt"
Informa que vamos usar um pacote externo ao arquivo.func main()
Define a função principal do programa.
Entendendo package
Todo arquivo Go começa com uma declaração de pacote, exemplo:
package main
Um package é uma forma de organizar código em Go, ele representa um grupo de arquivos que fazem parte da mesma unidade de código. A palavra package responde a esta pergunta: Este arquivo pertence a qual pacote?.
A resposta seria: esse arquivo pertence ao pacote main. O pacote main tem um significado especial em Go, ele indica que aquele código pode virar um programa executável, ou seja, ele será um programa que pode ser rodado diretamente no sistema, como um comando de terminal ou uma aplicação de backend.
Quando o Go encontra um pacote chamado main com uma função chamada main, ele entende que ali existe um ponto de entrada para executar o programa.
Entendendo import
O import é usado para trazer códigos externos que serão usados no arquivo. No exemplo abaixo, estamos importando o pacote fmt.
import "fmt"
O pacote fmt faz parte da biblioteca padrão do Go. O nome fmt vem de format, porque esse pacote trabalha com entrada e saída formatada. Ele é muito usado para imprimir texto no terminal.
Importando mais de um pacote
Quando queremos importar mais de um pacote, podemos usar parênteses:
package main
import (
"fmt"
"math"
)
Cuidado importante com imports
O Go não permite importar pacote e não usar, isso é para evitar de ficar deixando o código pesado com coisas desnecessárias. O código precisa ser limpo para manter a boa performance.
Entendendo a função main
A função main é o ponto de entrada do programa.
func main() {
fmt.Println("Olá")
}
Ainda vamos estudar funções com calma, então agora basta entender que main é a função que o Go chama quando o programa começa. Em um programa executável Go, a função principal precisa estar dentro do pacote main.
package main
func main() {
// O programa começa por aqui.
}
A função main não recebe argumentos diretamente e não retorna valor.
Entendendo :=
Em Go, o operador := é usado para declarar uma variável e atribuir um valor ao mesmo tempo. Ele é chamado de declaração curta de variável, porque evita escrever var quando o tipo pode ser entendido pelo próprio valor.
package main
import "fmt"
func main() {
// Cria a variável nome e atribui o valor "Ana".
nome := "Ana"
fmt.Println(nome)
}
Nesse exemplo, o Go entende que nome é do tipo string, porque o valor "Ana" é uma string. Outra forma de realizar a mesma declaração e atribuição seria:
var nome string = "Ana"
Quando usamos :=, não informamos o tipo manualmente. O compilador analisa o valor do lado direito e define o tipo da variável. Isso facilita a escrita de código Go no dia a dia.
Mas existe uma regra importante, só podemos usar := dentro de funções. Para declarar e atribuir valor a uma variável fora de uma função, devemos usar var.
package main
import "fmt"
// Declaração de variável no nível do pacote.
var nome = "Ana"
func main() {
fmt.Println(nome)
}
:= cria uma nova variável
O := deve ser usado quando estamos criando uma variável nova.
package main
import "fmt"
func main() {
// Aqui a variável idade está sendo criada.
idade := 20
fmt.Println(idade)
}
Se a variável já existe e queremos apenas trocar o valor, usamos =.
package main
import "fmt"
func main() {
idade := 20
// Aqui idade já existe.
// Por isso usamos = para atribuir outro valor.
idade = 21
fmt.Println(idade)
}
A diferença é que, o código abaixo cria a variável e já define um valor para ela.
idade := 20
// Ou, de forma mais completa:
var idade int = 20
Agora se a variável já existe, não podemos usar :=, devemos apenas usar o símbolo de atribuição que é =:
idade = 21
Quando := pode aparecer com variável já existente
Existe uma situação que pode confundir tudo que expliquei acima. O Go permite usar := quando existe pelo menos uma variável nova do lado esquerdo, exemplo:
package main
import "fmt"
func main() {
nome := "Ana"
// idade é uma variável nova.
// nome já existia, mas idade ainda não.
nome, idade := "Carlos", 21
fmt.Println(nome)
fmt.Println(idade)
}
Nesse caso, o código funciona porque idade não existe ainda, está sendo criada naquele momento. Se todas as variáveis já existirem, dá erro:
Função print
Em Go, o jeito comum de mostrar informações no console é usando o pacote fmt, ele possui funções que fazem o trabalho de imprimir algo na tela. Existe o print e o println como funções internas da linguagem, mas elas não são usadas no dia a dia, elas existem mais para situações simples e internas. Em código normal, o padrão é usar funções do pacote fmt, exemplo:
fmt.Print()
fmt.Println()
fmt.Printf()
Para usar essas funções, primeiro importamos o pacote fmt:
import "fmt"
O fmt vem de "format", porque esse pacote trabalha com textos formatados.
fmt.Println
O Println exibe uma informação no console e quebra a linha no final.
package main
import "fmt"
func main() {
// Cada Println imprime e pula para a próxima linha.
fmt.Println("linha1")
fmt.Println("ola")
fmt.Println("tudo bem?")
}
Resultado:
linha1
ola
tudo bem?
Esse é o mais parecido com o print() do Python quando queremos mostrar uma linha por vez.
Mostrando várias linhas
Em Go, dá para criar uma string de várias linhas usando crase.
package main
import "fmt"
func main() {
fmt.Println(`linha1
ola
tudo bem?`)
}
Resultado:
linha1
ola
tudo bem?
Esse recurso é parecido com as três aspas do Python.
fmt.Print
O Print exibe o conteúdo, mas não quebra linha automaticamente no final.
package main
import "fmt"
func main() {
// Print não pula linha no final.
fmt.Print("Abe")
fmt.Print("lha")
}
Resultado:
Abelha
Se quiser quebrar linha usando Print, é necessário colocar \n manualmente:
package main
import "fmt"
func main() {
// \n representa quebra de linha.
fmt.Print("linha1\n")
fmt.Print("ola\n")
fmt.Print("tudo bem?\n")
}
Resultado:
linha1
ola
tudo bem?
fmt.Printf
O Go não possui f-string como no Python. Para exibir valor de variáveis no texto, podemos usar marcadores, como:
%s: string%d: número inteiro%f: número decimal%.2f: número decimal com 2 casas%v: valor em formato padrão%T: tipo do valor
No código, fica assim:
package main
import "fmt"
func main() {
nome := "Fulano"
idade := 27
// %s é usado para string.
// %d é usado para número inteiro.
// \n quebra a linha no final.
fmt.Printf("Meu nome é %s e tenho %d anos.\n", nome, idade)
}
Resultado:
Meu nome é Fulano e tenho 27 anos.
Se não quiser especificar o tipo, pode usar %v para que o código obtenha o tipo.
fmt.Sprintf
O fmt.Sprintf é algo próximo do .format() em Python. Ele já exibe a string formatada. A diferença é que Printf imprime direto, enquanto Sprintf monta uma string e devolve essa string.
package main
import "fmt"
func main() {
nome := "Fulano"
idade := 27
// Sprintf cria uma string formatada.
// Ele não imprime sozinho.
mensagem := fmt.Sprintf("Meu nome é %s e tenho %d anos.", nome, idade)
// Agora imprimimos a string criada.
fmt.Println(mensagem)
}
Resultado:
Meu nome é Fulano e tenho 27 anos.
A ideia é que fmt.Printf exibe direto no direto no console. Já fmt.Sprintf cria uma string e guarda em uma variável. Isso é útil quando o texto precisa ser usado depois, por exemplo, em uma mensagem de erro, log ou resposta de API.
Índices na formatação
No Python, dá para usar índices com .format(), exemplo:
print("Idade: {1}, Nome: {0}".format(nome, idade))
Em Go, também dá para controlar a posição dos valores usando índices dentro do formato.
package main
import "fmt"
func main() {
nome := "Fulano"
idade := 27
// %[2]d usa o segundo valor passado depois da string.
// %[1]s usa o primeiro valor passado depois da string.
fmt.Printf("Idade: %[2]d, Nome: %[1]s\n", nome, idade)
}
Resultado:
Idade: 27, Nome: Fulano
Esse recurso existe, mas no começo é mais comum escrever na ordem normal:
fmt.Printf("Nome: %s, Idade: %d\n", nome, idade)
Concatenação com string
Em Go, string representa texto, então não dá para fazer qualquer operação matemática com string, porque texto não é número. Não faz sentido dividir uma palavra, subtrair uma letra ou multiplicar texto diretamente como no Python.
A operação mais comum com string é a concatenação.
Operador +
O operador + junta duas strings.
package main
import "fmt"
func main() {
a := "Abe"
b := "lha"
// O operador + concatena strings.
fmt.Println(a + b)
}
Resultado:
Abelha
Esse uso é parecido com Python!
Repetindo string
Para repetir uma string, usamos a função strings.Repeat.
package main
import (
"fmt"
"strings"
)
func main() {
a := "Abe"
// strings.Repeat repete a string a quantidade informada.
resultado := strings.Repeat(a, 3)
fmt.Println(resultado)
}
Resultado:
AbeAbeAbe
Aqui aparece um novo pacote:
import "strings"
Ele faz parte da biblioteca padrão do Go e tem várias funções para trabalhar com texto.
Usando vírgula com fmt.Println
Em Python, a vírgula dentro do print separa valores e adiciona espaço entre eles. Já no Go, algo parecido acontece com fmt.Println.
package main
import "fmt"
func main() {
a := "Abe"
b := "lha"
// Println imprime os valores separados por espaço.
fmt.Println(a, b)
}
Resultado:
Abe lha
A vírgula aqui não é uma operação de string. Ela apenas separa os argumentos passados para Println.
Tipos
Em Go, todo valor tem um tipo. O tipo diz que tipo de dado aquele valor representa e quais operações fazem sentido com ele.
Por exemplo, um número inteiro pode ser somado com outro número inteiro. Um texto pode ser juntado com outro texto. Um valor verdadeiro ou falso pode ser usado em uma condição.
Isso é assim porque Go é uma linguagem de tipagem estática, então o tipo das coisas é verificado antes do programa rodar. Isso ajuda a encontrar erros mais cedo.
Por que tipos existem
Tipos existem para dar regras ao programa, sem tipos, seria fácil tentar fazer operações sem sentido, como somar um texto com um número diretamente, comparar valores incompatíveis ou passar um dado errado para uma função.
O Go prefere ser explícito nessa parte, a linguagem tenta evitar "mágicas" e conversões automáticas demais. Isso pode parecer mais rígido no começo, mas deixa o código mais previsível.
Tipos inteiros
Números inteiros são números sem parte decimal. Os tipos inteiros mais comuns são:
- int
- int8
- int16
- int32
- int64
- uint
- uint8
- uint16
- uint32
- uint64
O tipo mais usado no começo é int.
package main
import "fmt"
func main() {
// int representa um número inteiro.
// É o tipo inteiro mais comum para uso geral.
idade := 17
// fmt.Println imprime o valor no terminal.
fmt.Println(idade)
}
A diferença entre int, int8, int16, int32 e int64 está na quantidade de bits usada para guardar o número. Quanto mais bits, maior o intervalo de valores aceitos. Para a maioria dos casos comuns, usamos int.
| Tipo | Menor valor | Maior valor |
|---|---|---|
int8 | -128 | 127 |
int16 | -32.768 | 32.767 |
int32 | -2.147.483.648 | 2.147.483.647 |
int64 | -9.223.372.036.854.775.808 | 9.223.372.036.854.775.807 |
Os tipos que começam com u, como uint, significam unsigned integer, ou inteiro sem sinal. Eles não aceitam número negativo. Por isso, começam em zero e conseguem guardar um valor positivo maior usando a mesma quantidade de bits.
| Tipo | Menor valor | Maior valor |
|---|---|---|
uint8 | 0 | 255 |
uint16 | 0 | 65.535 |
uint32 | 0 | 4.294.967.295 |
uint64 | 0 | 18.446.744.073.709.551.615 |
Quando usamos apenas int, não escolhemos manualmente a quantidade de bits do número. O tamanho do int é definido pelo alvo de compilação do Go, principalmente pela arquitetura indicada por GOARCH. Em arquiteturas comuns de 64 bits, como amd64 e arm64, int normalmente tem 64 bits. Em arquiteturas de 32 bits, como 386, int normalmente tem 32 bits. Mesmo assim, o tipo continua sendo int, não int64 ou int32.
O GOARCH representa a arquitetura alvo do build. A documentação do Go explica que GOOS e GOARCH identificam o ambiente de destino, não necessariamente o ambiente onde o comando está sendo executado. Isso importa porque Go permite cross-compilation, então é possível compilar em uma máquina de 64 bits gerando um binário para uma arquitetura de 32 bits.
O tipo int é o inteiro padrão da linguagem, o tamanho dele depende da arquitetura usada na compilação, podendo ter 32 ou 64 bits. Já int32 e int64 têm tamanho fixo, ou seja, int32 sempre tem 32 bits e int64 sempre tem 64 bits. Mesmo que todos representem números inteiros, int, int32 e int64 não são o mesmo tipo em Go.
Isso significa que, mesmo em um sistema onde int tenha 64 bits, ele continua sendo int, não int64. Por isso, o Go não permite fazer operações diretamente entre int e int64, mesmo os dois sendo números inteiros. Caso você tente fazer uma operação dessas, vai dar erro, porque Go não faz conversão automática entre tipos numéricos diferentes. Ele exige que a conversão seja explícita, para deixar claro qual tipo será usado na operação.
Tipos de ponto flutuante
Ponto flutuante são números com parte decimal. Os principais são:
- float32
- float64
O mais usado é
float64.
Exemplo:
package main
import "fmt"
func main() {
// float64 representa um número com parte decimal.
preco := 19.90
fmt.Println(preco)
}
O ponto flutuante é usado para valores aproximados, por isso, tenha cuidado ao usar esse tipo. Eu não recomendo usar ele para dinheiro. Em sistemas reais, valores monetários muitas vezes são representados como inteiros, usando centavos, ou com bibliotecas específicas. Isso evita erros de arredondamento.
Tipo booleano
O tipo booleano representa apenas dois valores:
- true
- false
Na maioria das linguagens, esse tipo é chamado de: bool, exemplo:
package main
import "fmt"
func main() {
// bool representa verdadeiro ou falso.
usuarioAtivo := true
fmt.Println(usuarioAtivo)
}
Esse tipo aparece muito em condições, validações e regras de decisão.
Tipo string
A string representa texto.
package main
import "fmt"
func main() {
// string representa texto.
nome := "Ana"
fmt.Println(nome)
}
Em Go, texto comum fica entre aspas duplas:
"Olá"
Também existe texto com crase, chamado de raw string literal. Ele preserva quebras de linha e não interpreta alguns caracteres especiais.
package main
import "fmt"
func main() {
// Texto com crase pode ocupar várias linhas.
mensagem := `Linha 1
Linha 2
Linha 3`
fmt.Println(mensagem)
}
Tipo rune
Uma rune representa um caractere Unicode. Um unicode é um padrão usado para representar caracteres de várias línguas e símbolos. Em Go, rune é um apelido para int32, mas no uso comum pensamos nele como um caractere.
package main
import "fmt"
func main() {
// rune representa um caractere.
letra := 'A'
fmt.Println(letra)
}
Esse exemplo imprime o número correspondente ao caractere, porque internamente o rune é um valor numérico.
Em Go, apóstrofos (conhecido como aspas simples) e aspas duplas criam valores diferentes. Quando usamos apóstrofos, estamos criando uma rune.
Quando usamos aspas duplas, estamos criando uma string. Exemplo:
'A' // rune
"A" // string
Apóstrofos são usadas para representar um caractere. Isso acontece porque rune representa um caractere pelo seu valor Unicode, já a letra A tem o valor Unicode 65.
Para imprimir como caractere, podemos formatar com %c:
package main
import "fmt"
func main() {
// rune representa um caractere Unicode.
letra := 'A'
// %c mostra o valor como caractere.
fmt.Printf("%c\n", letra)
}
Tipo byte
O byte representa um byte, ou seja, um valor de 0 a 255. Em Go, byte é um apelido para uint8. Ele aparece bastante quando trabalhamos com arquivos, rede, dados binários e conversões entre texto e bytes.
package main
import "fmt"
func main() {
// byte representa um valor entre 0 e 255.
var letra byte = 'A'
fmt.Println(letra)
}
Esse código imprime o valor numérico do caractere A, igual vimos na rune, mas armazena em uma variável do tipo byte. Para exibir o valor de byte em texto, podemos usar %c:
package main
import "fmt"
func main() {
var letra byte = 'A'
// %c mostra o byte como caractere.
fmt.Printf("%c\n", letra)
}
Valor zero
Em Go, quando uma variável é criada sem valor inicial, ela recebe um valor zero. Valor zero é o valor padrão de um tipo e varia de acordo com o tipo:
| Tipo | Valor zero |
|---|---|
| int | 0 |
| float64 | 0 |
| bool | false |
| string | "" |
Um exemplo para ver isso é:
package main
import "fmt"
func main() {
// Variáveis declaradas sem valor recebem o valor zero do tipo.
var idade int
var preco float64
var ativo bool
var nome string
fmt.Println(idade) // 0
fmt.Println(preco) // 0
fmt.Println(ativo) // false
fmt.Println(nome) // string vazia
}
Isso é diferente de linguagens onde uma variável pode começar "sem nada" ou com valor indefinido. Em Go, todo tipo tem um valor padrão.
Conversão entre tipos
O Go não costuma converter tipos automaticamente, então devemos fazer isso manualmente. Por exemplo, um int não vira float64 sozinho em uma operação.
package main
import (
"fmt"
"strconv"
)
func main() {
var idade int = 20
var numero int64 = 100
var preco float64 = 19.90
var numero int = 65
var numerotexto string = "20"
// int para int64:
idadeInt64 := int64(idade)
// int para float64:
idadeFloat64 := float64(idade)
// int64 para int:
numeroInt := int(numero)
// float64 para int (a parte decimal é descartada):
precoInt := int(preco)
// int para string, não vira "65", ele vai virar o caractere Unicode correspondente:
texto := string(numero) // Vai exibir: A
// Para converter número para texto, use strconv:
texto := strconv.Itoa(idade) // Vai exibir: "65"
// string para int (quando string tem um caractere numérico):
numero, err := strconv.Atoi(numerotexto)
// float64 para string
textopreco := strconv.FormatFloat(preco, 'f', 2, 64)
}