Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Variáveis e tipos básicos

Em Go, toda variável tem um tipo definido na compilação e ele nunca muda. Há duas formas principais de declarar: var nome tipo = valor, que funciona em qualquer lugar, e a declaração curta nome := valor, que só existe dentro de funções e deduz o tipo pelo valor à direita. Depois de declarada, a variável recebe novos valores com =, sempre do mesmo tipo.

Go Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
Go / Fundamentos

Variáveis e tipos básicos

por Beckett Hollis
Iniciante

Entenda o conceito

Em Go, toda variável tem um tipo definido na compilação e ele nunca muda. Há duas formas principais de declarar: var nome tipo = valor, que funciona em qualquer lugar, e a declaração curta nome := valor, que só existe dentro de funções e deduz o tipo pelo valor à direita. Depois de declarada, a variável recebe novos valores com =, sempre do mesmo tipo.

Os tipos básicos do dia a dia são int (inteiros), float64 (decimais), string (texto em UTF-8) e bool (true ou false). Também existem tipos com tamanho fixo, como int64 e uint8 (que é o mesmo que byte), e rune, que representa um caractere Unicode. Uma variável declarada sem valor não fica "vazia": ela recebe o valor zero do tipo, que é 0, 0.0, "" ou false.

Go não converte tipos sozinho. Somar um int com um float64 não compila; você converte com a sintaxe T(v), como float64(quantidade). Cuidado com dois detalhes: a divisão entre inteiros descarta a parte decimal (10 / 4 dá 2), e int(7.9) corta as casas, dando 7, sem arredondar.

↳ Pontos-chave
  • var x int = 5 declara em qualquer lugar; x := 5 é a forma curta, só dentro de funções, e deduz o tipo.
  • Variável sem valor inicial recebe o valor zero do tipo: 0 para números, "" para string e false para bool.
  • Depois de declarada, a variável muda com =; usar := de novo com o mesmo nome, no mesmo escopo, não compila.
  • Tipos diferentes não se misturam: converta com T(v), como float64(qtd) * preco; int(x) corta as casas decimais.
  • O verbo %T do Printf mostra o tipo de um valor; len de uma string conta bytes, não letras.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Inventário do herói com var e valores zero

inventario.go
package main

import "fmt"

func main() {
	// var com tipo e sem valor: a variável nasce com o valor zero do tipo.
	var heroi string
	var nivel int
	var ouro float64
	var temEspada bool

	fmt.Println("Antes de começar o jogo:")
	fmt.Printf("heroi=%q nivel=%d ouro=%.1f temEspada=%t\n", heroi, nivel, ouro, temEspada)

	// Depois, cada variável recebe um valor do mesmo tipo.
	heroi = "Lia"
	nivel = 1
	ouro = 15.5
	temEspada = true

	fmt.Println("Depois da primeira fase:")
	fmt.Printf("heroi=%q nivel=%d ouro=%.1f temEspada=%t\n", heroi, nivel, ouro, temEspada)

	// var com valor inicial: o tipo pode ser escrito ou deduzido.
	var vidas int = 3
	var poder = 7.25
	fmt.Println("Vidas:", vidas, "| Poder:", poder)
}

As quatro variáveis são declaradas com var e o tipo, mas sem valor. Mesmo assim o programa compila e imprime: cada uma recebeu o valor zero do seu tipo ("", 0, 0.0 e false). Em Go não existe variável "não inicializada" com lixo de memória.

No fim aparecem as duas variações de var com valor: com o tipo escrito (var vidas int = 3) e com o tipo deduzido (var poder = 7.25, que vira float64). O verbo %t imprime booleanos e o %q deixa visível a string vazia.

02Empréstimo na biblioteca com := e %T

emprestimo.go
package main

import "fmt"

func main() {
	// := declara e atribui; o tipo vem do valor à direita.
	titulo := "Dom Casmurro"
	exemplares := 4
	multaDiaria := 0.75
	disponivel := exemplares > 0

	// %T mostra o tipo que o compilador deduziu.
	fmt.Printf("%-12s %-14v %T\n", "titulo", titulo, titulo)
	fmt.Printf("%-12s %-14v %T\n", "exemplares", exemplares, exemplares)
	fmt.Printf("%-12s %-14v %T\n", "multaDiaria", multaDiaria, multaDiaria)
	fmt.Printf("%-12s %-14v %T\n", "disponivel", disponivel, disponivel)

	// Várias variáveis de uma vez, na mesma linha.
	leitor, diasAtraso := "Paulo", 6

	// Reatribuição usa = (a variável já existe).
	exemplares = exemplares - 1

	fmt.Println()
	fmt.Printf("%s pegou %q; restam %d exemplares.\n", leitor, titulo, exemplares)
	fmt.Printf("Atraso de %d dias: multa de R$ %.2f\n", diasAtraso, float64(diasAtraso)*multaDiaria)
}

A declaração curta := é a forma mais comum dentro de funções: o tipo vem do valor. A tabela usa %T para mostrar o que o compilador deduziu: texto vira string, número sem ponto vira int, número com ponto vira float64 e uma comparação vira bool.

Repare que exemplares = exemplares - 1 usa =, porque a variável já existe. E a multa só pode ser calculada convertendo diasAtraso para float64, já que Go não multiplica int por float64 diretamente.

03Estoque da loja: divisão inteira, conversões e bytes

estoque.go
package main

import "fmt"

func main() {
	// Tipos com tamanho definido: uint8 vai de 0 a 255.
	var prateleira uint8 = 12
	var codigo int64 = 789123456789

	estoque := 47       // int
	porCaixa := 6       // int
	precoCaixa := 54.90 // float64

	// Divisão entre inteiros descarta a parte decimal; % dá o resto.
	caixas := estoque / porCaixa
	avulsas := estoque % porCaixa
	fmt.Printf("Prateleira %d, código %d\n", prateleira, codigo)
	fmt.Printf("%d unidades = %d caixas + %d avulsas\n", estoque, caixas, avulsas)

	// Para misturar int e float64, converta com T(v).
	valorCaixas := float64(caixas) * precoCaixa
	fmt.Printf("Valor das caixas: R$ %.2f\n", valorCaixas)

	// int(x) corta as casas decimais, sem arredondar.
	precoUnitario := precoCaixa / float64(porCaixa)
	fmt.Printf("Preço unitário: %.4f -> int: %d\n", precoUnitario, int(precoUnitario))

	// len conta bytes; letras acentuadas ocupam mais de um byte.
	nome := "Açúcar"
	fmt.Println(nome, "tem", len(nome), "bytes e", len([]rune(nome)), "letras")
}

Este exemplo junta os pontos que mais causam erro. estoque / porCaixa com dois inteiros dá 7, sem casas, e % devolve o resto (5). Para multiplicar as caixas pelo preço é preciso float64(caixas), e int(precoUnitario) mostra que a conversão para inteiro corta o 9.15 em 9, sem arredondar.

A última linha mostra que string em Go guarda bytes em UTF-8: Açúcar tem 6 letras, mas ç e ú ocupam 2 bytes cada, então len devolve 8. Para contar letras, converta para []rune. Também aparecem os tipos com tamanho fixo uint8 e int64.


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Ficha da personagem com %TFácil

Monte a ficha de uma personagem de jogo com cinco atributos (nome, classe, nível, vida e se está em missão) e imprima cada valor seguido do seu tipo, como na saída esperada.

↳ Requisitos
  • Usa as três formas de declarar: var com tipo, var sem tipo e :=.
  • Há pelo menos uma variável de cada tipo: string, int, float64 e bool.
  • Cada linha é impressa com fmt.Printf usando %v para o valor e %T para o tipo.
  • A saída é idêntica à esperada.
código inicial
package main

import "fmt"

func main() {
	var nome string = "Tora"
	// declare classe, nivel, vida e emMissao

	fmt.Printf("nome: %v (%T)\n", nome, nome)
	// imprima os outros atributos
}
saída esperada
nome: Tora (string)
classe: arqueira (string)
nivel: 7 (int)
vida: 82.5 (float64)
emMissao: true (bool)
Ver dica

O tipo deduzido depende de como o valor é escrito: 82.5 e 82 não viram o mesmo tipo.

Ver resposta
package main

import "fmt"

func main() {
	// Cada atributo do personagem com o tipo que combina com ele.
	var nome string = "Tora"
	var classe = "arqueira"
	nivel := 7
	vida := 82.5
	emMissao := true

	// %v mostra o valor e %T mostra o tipo.
	fmt.Printf("nome: %v (%T)\n", nome, nome)
	fmt.Printf("classe: %v (%T)\n", classe, classe)
	fmt.Printf("nivel: %v (%T)\n", nivel, nivel)
	fmt.Printf("vida: %v (%T)\n", vida, vida)
	fmt.Printf("emMissao: %v (%T)\n", emMissao, emMissao)
}

Passo a passo

  1. var nome string = "Tora" é a forma completa: palavra var, nome, tipo e valor.
  2. var classe = "arqueira" omite o tipo; o Go deduz string pelo valor entre aspas.
  3. nivel := 7 usa a declaração curta; um número sem ponto vira int.
  4. vida := 82.5 tem ponto decimal, então vira float64, o tipo decimal padrão do Go.
  5. emMissao := true vira bool, o tipo que só aceita true ou false.
  6. Cada fmt.Printf passa a mesma variável duas vezes: uma para o %v (valor) e outra para o %T (tipo).

Por que funciona

As três formas de declaração produzem variáveis iguais em tudo: o que muda é só quanto você escreve. Quando o tipo não aparece, o compilador o deduz pelo valor literal, e o %T revela essa decisão na saída. Por isso 82.5 aparece como float64 e 7 como int.

O %v imprime qualquer valor no formato padrão, então serve para os cinco tipos sem trocar de verbo.

Erros comuns

  • Escrever vida := 82 achando que vira decimal: o tipo deduzido é int e a saída mostra 82 (int). Escreva 82.5 (ou 82.0).
  • Usar nivel := 7 fora de uma função, no nível do pacote: dá erro de compilação (non-declaration statement outside function body). Lá fora só vale var.
  • Declarar uma variável e não usá-la, como var apelido = "Tô" sem imprimir: o compilador recusa com declared and not used.
  • Passar a variável só uma vez, como fmt.Printf("nivel: %v (%T)\n", nivel): o segundo verbo fica sem valor e aparece %!T(MISSING).

Outra forma de resolver

Um bloco var ( ... ) agrupa as declarações e deixa a ficha mais organizada:

var (
	nome     = "Tora"
	classe   = "arqueira"
	nivel    = 7
	vida     = 82.5
	emMissao = true
)

Ele é útil para várias variáveis relacionadas, inclusive no nível do pacote, onde := não é permitido.

Saída esperada

nome: Tora (string)
classe: arqueira (string)
nivel: 7 (int)
vida: 82.5 (float64)
emMissao: true (bool)

02Total do carrinho com cupomMédio

Calcule o total de um carrinho com 3 unidades de um produto de R$ 12,90 e um cupom de 10% de desconto. A quantidade e o percentual do cupom são inteiros; o preço é decimal.

↳ Requisitos
  • quantidade e cupom são int; precoUnitario é float64.
  • Os inteiros são convertidos com float64(...) antes de entrar nas contas com o preço.
  • Subtotal, desconto e total são calculados no programa e impressos com %.2f.
  • O símbolo de porcentagem aparece na saída usando %%.
código inicial
package main

import "fmt"

func main() {
	quantidade := 3
	precoUnitario := 12.90
	cupom := 10

	// calcule subtotal, desconto e total
}
saída esperada
Itens: 3 x R$ 12.90
Subtotal: R$ 38.70
Cupom de 10%: -R$ 3.87
Total: R$ 34.83
Ver dica

Se o compilador reclamar de mismatched types int and float64, é sinal de que falta uma conversão.

Ver resposta
package main

import "fmt"

func main() {
	// Dados do carrinho: quantidade e cupom são inteiros, preço é decimal.
	quantidade := 3
	precoUnitario := 12.90
	cupom := 10 // percentual de desconto

	// float64(...) converte os inteiros antes de misturar com o preço.
	subtotal := float64(quantidade) * precoUnitario
	desconto := subtotal * float64(cupom) / 100
	total := subtotal - desconto

	fmt.Printf("Itens: %d x R$ %.2f\n", quantidade, precoUnitario)
	fmt.Printf("Subtotal: R$ %.2f\n", subtotal)
	fmt.Printf("Cupom de %d%%: -R$ %.2f\n", cupom, desconto)
	fmt.Printf("Total: R$ %.2f\n", total)
}

Passo a passo

  1. quantidade := 3 e cupom := 10 viram int, porque são números sem ponto; precoUnitario := 12.90 vira float64.
  2. float64(quantidade) * precoUnitario converte a quantidade antes de multiplicar, já que Go não mistura int com float64.
  3. subtotal * float64(cupom) / 100 calcula o desconto; o cupom também precisa de conversão.
  4. total := subtotal - desconto já opera só com float64, então não precisa converter nada.
  5. %.2f mostra os valores em reais com duas casas, e %d imprime os inteiros.
  6. Em Cupom de %d%%, o %% imprime o próprio símbolo de porcentagem.

Por que funciona

Go exige que os dois lados de uma operação tenham o mesmo tipo. A conversão float64(v) cria um valor decimal a partir do inteiro, e a partir daí toda a conta acontece em float64, sem perder as casas decimais.

Manter quantidade e cupom como int é a escolha natural: não existe meia unidade nem cupom de 10,5%. A conversão fica só no ponto em que eles encontram o preço.

Erros comuns

  • Escrever subtotal := quantidade * precoUnitario: não compila, com o erro invalid operation: mismatched types int and float64. Converta com float64(quantidade).
  • Calcular float64(cupom / 100): a divisão inteira acontece antes, 10 / 100 dá 0, e o desconto sai zero. Converta antes de dividir.
  • Usar %d para o subtotal: o programa compila, mas imprime %!d(float64=38.7). Use %.2f para float64.
  • Escrever % sozinho em Cupom de %d% : o Printf tenta ler um verbo e a saída fica com %! no meio. Dobre para %%.

Outra forma de resolver

Dá para converter o cupom em fator de multiplicação logo no início:

fator := 1 - float64(cupom)/100
total := float64(quantidade) * precoUnitario * fator

Fica mais curto quando só o total importa; a versão com variáveis separadas é melhor quando o cupom precisa mostrar subtotal e desconto.

Saída esperada

Itens: 3 x R$ 12.90
Subtotal: R$ 38.70
Cupom de 10%: -R$ 3.87
Total: R$ 34.83

03Receita escalonada sem meio ovoDesafio

Uma receita rende 4 porções com 3 ovos e 250 g de farinha. Ajuste as quantidades para 10 porções, mostrando o fator de escala, a farinha, a conta exata de ovos, quantos ovos comprar (arredondando para cima) e por que int() e a divisão inteira dariam o resultado errado.

↳ Requisitos
  • O fator de escala é calculado em float64, convertendo as porções antes da divisão.
  • Os ovos a comprar são um int obtido com math.Ceil e conversão explícita.
  • O programa mostra também o resultado de int() direto e da divisão inteira 10/4, para comparação.
  • A última linha usa %T para mostrar os tipos do fator e dos ovos.
código inicial
package main

import (
	"fmt"
	"math"
)

func main() {
	const porcoesBase = 4
	ovosBase := 3
	farinhaBase := 250.0
	porcoesNovas := 10

	// calcule o fator e as novas quantidades
	_ = math.Ceil
}
saída esperada
Fator de escala: 2.50
Farinha: 625 g
Ovos (conta exata): 7.5
Ovos (comprar): 8
Se usasse int(): 7
Divisão inteira 10/4: 2
Tipos: fator=float64, ovos=int
Ver dica

math.Ceil recebe e devolve float64; para ter um inteiro no fim, a conversão vem por fora da chamada.

Ver resposta
package main

import (
	"fmt"
	"math"
)

func main() {
	// Receita original: rende 4 porções.
	const porcoesBase = 4
	ovosBase := 3
	farinhaBase := 250.0 // gramas

	porcoesNovas := 10

	// Fator de escala em float64; com inteiros, 10 / 4 daria 2.
	fator := float64(porcoesNovas) / float64(porcoesBase)
	farinha := farinhaBase * fator
	ovosExatos := float64(ovosBase) * fator

	// Ovo não se divide: arredonda para cima e volta a ser int.
	ovos := int(math.Ceil(ovosExatos))

	fmt.Printf("Fator de escala: %.2f\n", fator)
	fmt.Printf("Farinha: %.0f g\n", farinha)
	fmt.Printf("Ovos (conta exata): %.1f\n", ovosExatos)
	fmt.Printf("Ovos (comprar): %d\n", ovos)
	fmt.Printf("Se usasse int(): %d\n", int(ovosExatos))
	fmt.Printf("Divisão inteira 10/4: %d\n", porcoesNovas/porcoesBase)
	fmt.Printf("Tipos: fator=%T, ovos=%T\n", fator, ovos)
}

Passo a passo

  1. const porcoesBase = 4 é uma constante sem tipo; ela se adapta ao contexto, mas aqui é convertida explicitamente para deixar a intenção clara.
  2. farinhaBase := 250.0 tem ponto decimal para nascer float64; ovosBase e porcoesNovas são int, porque ovos e porções são contados inteiros.
  3. float64(porcoesNovas) / float64(porcoesBase) converte antes de dividir e dá 2.5; sem a conversão, seria 2.
  4. farinhaBase * fator já está todo em float64; %.0f mostra os gramas sem casas decimais.
  5. float64(ovosBase) * fator dá 7.5, a conta exata, que ainda é float64.
  6. int(math.Ceil(ovosExatos)) arredonda para cima (8.0) e só depois converte para int, porque ninguém compra meio ovo.
  7. As linhas de comparação mostram que int(7.5) corta para 7 e que 10/4 entre inteiros dá 2.
  8. %T confirma no fim que o fator é float64 e os ovos a comprar são int.

Por que funciona

O programa separa duas grandezas: medidas contínuas, como o fator e a farinha, ficam em float64; quantidades contáveis, como ovos, terminam em int. A ponte entre elas é sempre uma conversão explícita, e o lugar dela muda o resultado: converter antes da divisão preserva o .5, converter depois descarta.

Como int() corta as casas e não arredonda, quem decide a regra de arredondamento é o pacote math. math.Ceil sobe para o próximo inteiro, que é o que a receita precisa para não faltar ingrediente.

Erros comuns

  • Calcular fator := float64(porcoesNovas / porcoesBase): a divisão inteira acontece primeiro e o fator vira 2.00, deixando a receita com 500 g de farinha. Converta cada operando antes de dividir.
  • Usar ovos := int(ovosExatos) achando que arredonda: o resultado é 7 e falta um ovo. A conversão só corta; use math.Ceil (ou math.Round, se a regra for o mais próximo).
  • Escrever ovos := math.Ceil(ovosExatos) e imprimir com %d: o valor é float64 e sai %!d(float64=8). Converta com int(...).
  • Declarar farinhaBase := 250 sem o ponto: ela vira int e farinhaBase * fator não compila (mismatched types int and float64).

Outra forma de resolver

Com aritmética inteira também dá para arredondar para cima, sem float64 e sem o pacote math:

ovos := (ovosBase*porcoesNovas + porcoesBase - 1) / porcoesBase

A conta (30 + 3) / 4 dá 8. Ela é preferível quando todos os valores são inteiros e você quer evitar qualquer imprecisão de ponto flutuante; a versão com math.Ceil é mais fácil de ler.

Saída esperada

Fator de escala: 2.50
Farinha: 625 g
Ovos (conta exata): 7.5
Ovos (comprar): 8
Se usasse int(): 7
Divisão inteira 10/4: 2
Tipos: fator=float64, ovos=int

Últimas lições

Ver o arquivo completo →