Sua dose diária de programação

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

✦   Lição

Operadores

Operadores são os símbolos que fazem contas e comparações. Os aritméticos são +, -, *, / e % (resto, só para inteiros). Com strings, + junta textos. Os dois lados de uma operação precisam ter o mesmo tipo: Go não mistura int com float64 sem conversão.

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

Operadores

por Sloane Kensington
Iniciante

Entenda o conceito

Operadores são os símbolos que fazem contas e comparações. Os aritméticos são +, -, *, / e % (resto, só para inteiros). Com strings, + junta textos. Os dois lados de uma operação precisam ter o mesmo tipo: Go não mistura int com float64 sem conversão.

Para alterar uma variável a partir do próprio valor, existem a atribuição composta (+=, -=, *=, /=, %=...) e os incrementos x++ e x--. Em Go, ++ e -- são instruções, não expressões: y := x++ não compila e não existe ++x. Os comparadores ==, !=, <, <=, > e >= devolvem bool, que pode ser guardado numa variável ou combinado com &&, || e !.

Os operadores bit a bit trabalham com os bits de um inteiro: & (e), | (ou), ^ (ou exclusivo), &^ (e não, típico do Go), << e >> (deslocamentos). Quanto à precedência, Go tem só cinco níveis: * / % << >> & &^ vêm antes de + - | ^, que vêm antes das comparações, depois && e por último ||. No mesmo nível, a conta é feita da esquerda para a direita; na dúvida, use parênteses.

↳ Pontos-chave
  • / entre inteiros descarta a parte decimal e % dá o resto; o sinal do resto segue o dividendo: -7 % 3 é -1.
  • x += 5 é o mesmo que x = x + 5; x++ e x-- são instruções isoladas, sem forma prefixada e sem valor de retorno.
  • Comparações devolvem bool: aprovado := media >= 7 é uma variável como outra qualquer.
  • &, |, ^, &^, << e >> agem nos bits; c &^= flag desliga um bit e c&flag != 0 testa se ele está ligado.
  • Precedência em cinco níveis: multiplicativos, aditivos, comparações, && e ||; no mesmo nível vale a ordem da esquerda para a direita.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01Movimento do acervo com atribuição composta

acervo.go
package main

import "fmt"

func main() {
	acervo := 120    // livros da biblioteca
	emprestados := 0 // livros fora da estante

	// acervo += 15 é o mesmo que acervo = acervo + 15.
	acervo += 15
	fmt.Println("Doação recebida. Acervo:", acervo)

	// ++ e -- somam ou subtraem 1; em Go são instruções, não expressões.
	emprestados++
	emprestados++
	emprestados++
	fmt.Println("Empréstimos do dia:", emprestados)

	emprestados--
	fmt.Println("Depois de uma devolução:", emprestados)

	// Livros danificados saem do acervo.
	acervo -= 4
	naEstante := acervo - emprestados
	fmt.Println("Na estante agora:", naEstante, "de", acervo)

	// *= e /= também existem; com int, /= faz divisão inteira.
	meta := 50
	meta *= 3 // a meta anual triplicou
	fmt.Print("Meta anual: ", meta)
	meta /= 4 // dividida entre os 4 trimestres
	fmt.Println(" | por trimestre:", meta)

	// Com strings, + junta textos e += acrescenta ao fim.
	aviso := "Biblioteca"
	aviso += " Central"
	aviso = aviso + ": " + "aberta até as 20h"
	fmt.Println(aviso)
}

Cada linha muda uma variável a partir do valor que ela já tem. acervo += 15 e acervo -= 4 evitam repetir o nome (acervo = acervo + 15), e emprestados++ e emprestados-- são o atalho para somar ou subtrair 1. Em Go eles ficam sozinhos na linha, porque não devolvem valor.

A meta mostra que *= e /= seguem as mesmas regras de * e /: como meta é int, 150 / 4 dá 37. No fim, + e += com strings montam o aviso juntando pedaços de texto.

02Boletim: precedência, comparações e resto

boletim.go
package main

import "fmt"

func main() {
	prova, trabalho, participacao := 8.0, 6.0, 10.0

	// * e / são feitos antes de + e -.
	semParenteses := prova*2 + trabalho*2 + participacao/5
	// Os parênteses forçam a soma antes da divisão.
	media := (prova*2 + trabalho*2 + participacao) / 5
	fmt.Printf("Sem parênteses: %.2f\n", semParenteses)
	fmt.Printf("Média ponderada: %.2f\n", media)

	// Comparações devolvem bool e podem ser guardadas em variáveis.
	aprovado := media >= 7
	destaque := media >= 9 || prova == 10
	fmt.Println("Aprovado:", aprovado, "| Destaque:", destaque)

	// != e ! invertem o sentido do teste.
	turma := "7A"
	fmt.Println("Outra turma?", turma != "7A", "| Reprovado?", !aprovado)

	// % só existe para inteiros; o sinal do resto segue o dividendo.
	chamada := 17
	fmt.Println("Aluno", chamada, "é do grupo", chamada%4+1)
	fmt.Println("-7 % 3 =", -7%3, "| 7 % -3 =", 7%-3)

	// Comparação de strings segue a ordem dos bytes: maiúsculas vêm antes.
	fmt.Println("\"Ana\" < \"Bruno\":", "Ana" < "Bruno", "| \"ana\" < \"Bruno\":", "ana" < "Bruno")
}

As duas contas usam as mesmas notas, mas dão resultados bem diferentes. Sem parênteses, só participacao/5 é dividido, porque * e / são feitos antes de +. Com parênteses, a soma inteira é dividida por 5 e a média correta é 7.60.

As comparações viram variáveis bool (aprovado, destaque) e são combinadas com || e !. O % aparece para distribuir alunos em 4 grupos pelo número da chamada, e a linha com números negativos mostra que o resto tem o sinal do dividendo. Strings também se comparam com <, byte a byte, por isso "ana" (minúscula) vem depois de "Bruno".

03Opções do pedido com operadores bit a bit

pedido.go
package main

import "fmt"

// Cada opção do pedido ocupa um bit diferente de um uint8.
const (
	Presente uint8 = 1 << 0 // 00000001
	Expresso uint8 = 1 << 1 // 00000010
	Seguro   uint8 = 1 << 2 // 00000100
	Fragil   uint8 = 1 << 3 // 00001000
)

func main() {
	var opcoes uint8

	// | liga bits: o pedido vai embrulhado e com entrega expressa.
	opcoes |= Presente | Expresso
	fmt.Printf("Depois de |=:  %08b\n", opcoes)

	// & testa um bit: o resultado é diferente de zero se ele estiver ligado.
	fmt.Println("É presente?", opcoes&Presente != 0)
	fmt.Println("Tem seguro?", opcoes&Seguro != 0)

	// &^ (e não) desliga bits: o cliente desistiu do expresso.
	opcoes &^= Expresso
	fmt.Printf("Depois de &^=: %08b\n", opcoes)

	// ^ inverte bits: alterna a marcação de frágil.
	opcoes ^= Fragil
	fmt.Printf("Depois de ^=:  %08b\n", opcoes)

	// << e >> deslocam bits: cada casa para a esquerda dobra o valor.
	frete := 5
	fmt.Println("frete:", frete, "| <<1:", frete<<1, "| <<3:", frete<<3, "| >>1:", frete>>1)

	// Inteiros de tamanho fixo dão a volta ao passar do limite.
	var lote uint8 = 255
	lote++
	fmt.Println("uint8 255 + 1 =", lote)
}

Cada opção do pedido é um bit de um uint8, criado com o deslocamento 1 << n. O |= liga bits, o & seguido de != 0 testa se um bit está ligado (o & é avaliado antes do !=), o &^= desliga e o ^= alterna. O verbo %08b mostra os 8 bits, o que deixa cada operação visível.

As duas últimas partes mostram que << multiplica por potências de 2 e >> divide, e que um inteiro de tamanho fixo não dá erro ao passar do limite em tempo de execução: uint8 com 255 mais 1 volta para 0. Com constantes, o compilador detecta o estouro e recusa o código.


Agora é com você

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

01Placar do basqueteFácil

Registre as jogadas de uma partida de basquete: a casa faz uma cesta de três, uma de dois e dois lances livres; o visitante faz uma de dois, uma de três e outra de dois; no fim, a arbitragem anula a cesta de dois da casa. Mostre o placar, o total de pontos e quem vence por quanto.

↳ Requisitos
  • Cestas de dois e de três usam +=, e cada lance livre usa ++.
  • A cesta anulada é descontada com -=.
  • O total e a diferença são calculados no programa, e a diferença nunca sai negativa.
  • A saída é idêntica à esperada.
código inicial
package main

import "fmt"

func main() {
	casa := 0
	visitante := 0

	// registre as jogadas e mostre o resultado
}
saída esperada
Placar final: Casa 5 x 7 Visitante
Total de pontos: 12
Visitante vence por 2
Ver dica

Escreva uma linha por jogada, na ordem do enunciado; para a diferença, descubra antes quem está na frente.

Ver resposta
package main

import "fmt"

func main() {
	casa := 0
	visitante := 0

	// Cada jogada usa atribuição composta ou incremento.
	casa += 3      // cesta de três
	visitante += 2 // cesta de dois
	casa += 2      // cesta de dois
	visitante += 3 // cesta de três
	casa++         // lance livre
	casa++         // lance livre
	visitante += 2 // cesta de dois
	casa -= 2      // a arbitragem anulou a última cesta de dois da casa

	fmt.Printf("Placar final: Casa %d x %d Visitante\n", casa, visitante)
	fmt.Println("Total de pontos:", casa+visitante)

	// A diferença é sempre calculada do maior para o menor.
	if casa > visitante {
		fmt.Println("Casa vence por", casa-visitante)
	} else if visitante > casa {
		fmt.Println("Visitante vence por", visitante-casa)
	} else {
		fmt.Println("Empate")
	}
}

Passo a passo

  1. casa e visitante começam em 0 com :=; o tipo deduzido é int, e cada jogada só altera o valor que já existe.
  2. casa += 3 e visitante += 2 registram as cestas de três e de dois: é a forma curta de casa = casa + 3, sem repetir o nome da variável.
  3. Os dois lances livres usam casa++, porque cada um vale exatamente 1 ponto; ++ é a forma idiomática de somar 1.
  4. casa -= 2 desfaz a cesta anulada pela arbitragem: o mesmo raciocínio do +=, só que subtraindo.
  5. O total é a expressão casa+visitante direto no Println; não é preciso criar uma variável para um valor usado uma vez só.
  6. A cadeia if / else if / else decide quem lidera, e cada ramo calcula a diferença do maior para o menor, para ela nunca sair negativa.

Por que funciona

Atribuição composta e incremento são atalhos para "pegue o valor atual, faça a conta e guarde de volta". Por isso a ordem das jogadas importa pouco para o placar final, mas cada linha precisa usar o operador certo: += acumula, -= desconta e ++ soma exatamente 1.

A diferença usa o operador - comum, e as comparações > da cadeia de if escolhem a ordem da subtração. Assim a saída fica sempre positiva, sem precisar de função de valor absoluto.

Erros comuns

  • Escrever casa =+ 3: compila, mas é casa = +3 (atribuição de um número positivo). O placar vira só o valor da última jogada. O operador é +=, com o + antes do =.
  • Usar ++casa: não compila (syntax error: unexpected ++). Em Go só existe a forma pós-fixada, casa++.
  • Tentar total := casa++ para somar e guardar ao mesmo tempo: não compila (unexpected ++ at end of statement), porque ++ é uma instrução, não uma expressão que devolve valor.
  • Calcular sempre casa-visitante: com o visitante na frente, a saída diria vence por -2.

Outra forma de resolver

Também dá para guardar o valor de cada tipo de jogada em constantes e somar com elas:

const cestaDois, cestaTres, lanceLivre = 2, 3, 1
casa += cestaTres
casa += lanceLivre

Essa forma é preferível quando as regras de pontuação podem mudar ou aparecem em vários pontos do programa, porque o número fica com um nome.

Saída esperada

Placar final: Casa 5 x 7 Visitante
Total de pontos: 12
Visitante vence por 2

02Relógio do fornoMédio

Escreva paraMinutos(h, m int) int, que converte um horário em minutos desde a meia-noite, e horario(minutos int) string, que faz o caminho inverso no formato HH:MM, dando a volta depois das 23:59. Use as duas para mostrar quando termina cada receita e o tempo total de forno.

↳ Requisitos
  • horario usa % para voltar ao começo do dia e / e % para separar horas e minutos.
  • Os minutos por dia ficam numa constante calculada como 24 * 60.
  • O tempo total é acumulado com += e mostrado em minutos e em horas e minutos.
  • Horas e minutos são impressos com dois dígitos (%02d).
código inicial
package main

import "fmt"

const minutosPorDia = 24 * 60

func paraMinutos(h, m int) int {
	return 0
}

func horario(minutos int) string {
	return ""
}

func main() {
	totalForno := 0

	pao := paraMinutos(22, 40)
	fmt.Printf("%-8s %s + %3d min -> %s\n", "Pão", horario(pao), 95, horario(pao+95))
	totalForno += 95

	// bolo 15:05 (50 min), pernil 20:30 (210 min), lasanha 11:50 (45 min)
}
saída esperada
Pão      22:40 +  95 min -> 00:15
Bolo     15:05 +  50 min -> 15:55
Pernil   20:30 + 210 min -> 00:00
Lasanha  11:50 +  45 min -> 12:35
Forno ligado: 400 min (6h40min)
Ver dica

Um dia tem 1440 minutos: o resto da divisão por esse número é sempre um horário válido do mesmo dia.

Ver resposta
package main

import "fmt"

const minutosPorDia = 24 * 60

// paraMinutos converte um horário em minutos desde a meia-noite.
func paraMinutos(h, m int) int {
	return h*60 + m // * vem antes de +, sem parênteses
}

// horario formata minutos como HH:MM, dando a volta na meia-noite.
func horario(minutos int) string {
	minutos %= minutosPorDia
	return fmt.Sprintf("%02d:%02d", minutos/60, minutos%60)
}

func main() {
	totalForno := 0

	pao := paraMinutos(22, 40)
	fmt.Printf("%-8s %s + %3d min -> %s\n", "Pão", horario(pao), 95, horario(pao+95))
	totalForno += 95

	bolo := paraMinutos(15, 5)
	fmt.Printf("%-8s %s + %3d min -> %s\n", "Bolo", horario(bolo), 50, horario(bolo+50))
	totalForno += 50

	pernil := paraMinutos(20, 30)
	fmt.Printf("%-8s %s + %3d min -> %s\n", "Pernil", horario(pernil), 210, horario(pernil+210))
	totalForno += 210

	lasanha := paraMinutos(11, 50)
	fmt.Printf("%-8s %s + %3d min -> %s\n", "Lasanha", horario(lasanha), 45, horario(lasanha+45))
	totalForno += 45

	fmt.Printf("Forno ligado: %d min (%dh%02dmin)\n", totalForno, totalForno/60, totalForno%60)
}

Passo a passo

  1. const minutosPorDia = 24 * 60 é calculada pelo compilador, e a conta mostra de onde vem o 1440.
  2. paraMinutos faz h*60 + m: como * tem precedência maior que +, a multiplicação acontece primeiro e os parênteses seriam redundantes.
  3. Em horario, minutos %= minutosPorDia faz o relógio dar a volta: 1360 + 95 = 1455, e o resto da divisão por 1440 é 15, ou seja, 00:15.
  4. minutos/60 é a divisão inteira que dá as horas, e minutos%60 é o resto que dá os minutos; os dois saem do mesmo número.
  5. No main, o horário de término é só horario(pao + 95): soma em minutos e deixa a formatação cuidar da meia-noite. O pernil termina exatamente em 00:00.
  6. totalForno += ... acumula o tempo de cada receita, e a última linha reaproveita / e % para mostrar 6h40min.

Por que funciona

Guardar o horário como um único inteiro (minutos desde a meia-noite) transforma as contas de relógio em aritmética simples: somar uma duração é usar +, e o % resolve o "dar a volta" sem nenhum if. Quando o valor passa de 1439, o resto da divisão por 1440 volta para o começo do dia.

A precedência faz o resto do trabalho: h*60 + m e minutos%60 são lidos como se espera, sem parênteses. A função horario concentra a formatação num só lugar.

Erros comuns

  • Escrever minutos % 24 * 60 achando que é minutos % (24 * 60): % e * têm a mesma precedência e são avaliados da esquerda para a direita, então 1360 % 24 * 60 dá 960. Use a constante ou os parênteses.
  • Esquecer o %= em horario: o pão terminaria às 24:15.
  • Formatar com %d:%d: o bolo começaria às 15:5. O %02d garante dois dígitos.
  • Guardar as horas como float64 (22.40): % não funciona com números de ponto flutuante (operator % not defined), e 22,40 horas não são 22h40.

Outra forma de resolver

A biblioteca padrão tem o pacote time, que faz essa conta com tipos próprios:

inicio := time.Date(2026, 1, 1, 22, 40, 0, 0, time.UTC)
fim := inicio.Add(95 * time.Minute)
fmt.Println(fim.Format("15:04")) // 00:15

É preferível com datas de verdade e fusos horários; para um relógio de cozinha, / e % bastam.

Saída esperada

Pão      22:40 +  95 min -> 00:15
Bolo     15:05 +  50 min -> 15:55
Pernil   20:30 + 210 min -> 00:00
Lasanha  11:50 +  45 min -> 12:35
Forno ligado: 400 min (6h40min)

03Conquistas do jogador em bitsDesafio

Guarde as cinco conquistas de um jogador (Explorador, Colecionador, Invicto, Veloz e Mestre) nos bits de um único uint8. Ligue, revogue e teste conquistas, mostre os bits a cada passo, o que ainda falta e o progresso em porcentagem.

↳ Requisitos
  • Cada conquista é uma constante uint8 criada com <<, e a máscara com todas é feita com |.
  • Conquistas são ligadas com |=, a revogada é desligada com &^=, e o teste usa & comparado com 0.
  • A contagem usa bits.OnesCount8 e as faltantes são calculadas com &^ sobre a máscara.
  • Os bits aparecem com %08b e o progresso é inteiro, calculado sem perder a precisão na divisão.
código inicial
package main

import (
	"fmt"
	"math/bits"
)

const (
	Explorador   uint8 = 1 << 0
	Colecionador uint8 = 1 << 1
	// Invicto, Veloz e Mestre
)

func mostrar(rotulo string, c uint8) {
	fmt.Printf("%-16s %08b (%d de %d)\n", rotulo, c, bits.OnesCount8(c), 5)
}

func main() {
	var c uint8
	// início: Explorador e Veloz; depois +Colecionador, -Veloz, +Invicto +Mestre
	mostrar("início", c)
}
saída esperada
início           00001001 (2 de 5)
+colecionador    00001011 (3 de 5)
-veloz           00000011 (2 de 5)
+invicto +mestre 00010111 (4 de 5)
Tem Veloz? false
Tem Mestre? true
faltam           00001000
Progresso: 80%
Ver dica

Pense em cada operador como uma regra aplicada a todos os bits ao mesmo tempo: qual deles mantém um bit ligado só quando ele está ligado nos dois lados?

Ver resposta
package main

import (
	"fmt"
	"math/bits"
)

// Cada conquista é um bit do uint8.
const (
	Explorador   uint8 = 1 << 0
	Colecionador uint8 = 1 << 1
	Invicto      uint8 = 1 << 2
	Veloz        uint8 = 1 << 3
	Mestre       uint8 = 1 << 4
)

// todas junta as cinco conquistas numa única máscara.
const todas = Explorador | Colecionador | Invicto | Veloz | Mestre

// mostrar imprime os bits e quantas conquistas estão ligadas.
func mostrar(rotulo string, c uint8) {
	fmt.Printf("%-16s %08b (%d de %d)\n", rotulo, c, bits.OnesCount8(c), bits.OnesCount8(todas))
}

// tem diz se a conquista está ligada; & é feito antes de !=.
func tem(c, conquista uint8) bool {
	return c&conquista != 0
}

func main() {
	var c uint8

	c |= Explorador | Veloz
	mostrar("início", c)

	c |= Colecionador
	mostrar("+colecionador", c)

	c &^= Veloz // revogada: o jogador usou um atalho proibido
	mostrar("-veloz", c)

	c |= Invicto | Mestre
	mostrar("+invicto +mestre", c)

	fmt.Println("Tem Veloz?", tem(c, Veloz))
	fmt.Println("Tem Mestre?", tem(c, Mestre))
	fmt.Printf("%-16s %08b\n", "faltam", todas&^c)
	fmt.Printf("Progresso: %d%%\n", bits.OnesCount8(c)*100/bits.OnesCount8(todas))
}

Passo a passo

  1. As constantes usam 1 << 0, 1 << 1... para que cada conquista ocupe um bit diferente: Veloz, por exemplo, é 00001000.
  2. todas junta as cinco com | numa máscara 00011111; ela é constante porque as conquistas existentes não mudam durante o jogo.
  3. mostrar imprime os bits com %08b (8 dígitos, com zeros à esquerda) e conta as conquistas com bits.OnesCount8, que devolve quantos bits estão ligados.
  4. tem retorna c&conquista != 0. Em Go, & tem precedência maior que !=, então a expressão é lida como (c&conquista) != 0, sem parênteses.
  5. No main, c |= Explorador | Veloz liga dois bits de uma vez; depois c |= Colecionador liga mais um sem mexer nos outros.
  6. c &^= Veloz desliga só o bit da conquista revogada. O operador &^ (e não) zera no resultado os bits que estão ligados à direita.
  7. todas&^c reaproveita o mesmo operador para descobrir o que falta: parte da máscara completa e apaga o que o jogador já tem.
  8. O progresso faz a multiplicação antes da divisão (4*100/5), porque os dois operadores têm a mesma precedência e são avaliados da esquerda para a direita.

Por que funciona

Um uint8 tem 8 bits, e cada bit funciona como um interruptor independente. Os operadores bit a bit agem em todos os bits ao mesmo tempo: | liga os que estão ligados em qualquer um dos lados, & mantém só os que estão ligados nos dois e &^ apaga do lado esquerdo os que estão ligados no direito. Com máscaras de um bit só, cada operação mexe exatamente na conquista desejada.

Isso deixa o estado inteiro do jogador num único número pequeno, fácil de guardar e de comparar. É a mesma técnica usada em permissões de arquivos e opções de bibliotecas, e o Go oferece o operador &^ justamente para o caso comum de desligar bits.

Erros comuns

  • Escrever if c&Veloz { ... }: não compila (non-boolean condition in if statement). O resultado de & é um uint8, não um bool; compare com != 0.
  • Desligar com c ^= Veloz: funciona só se a conquista estiver ligada. Se ela já estiver desligada, o ^ a liga de novo. Para desligar sempre, use &^=.
  • Calcular o progresso como bits.OnesCount8(c)/bits.OnesCount8(todas)*100: a divisão inteira 4/5 dá 0 antes da multiplicação e o progresso sai 0%.
  • Criar uma sexta conquista com 1 << 8: não compila (constant 256 ... overflows), porque um uint8 só tem os bits de 0 a 7. Para mais conquistas, use uint16 ou uint32.

Outra forma de resolver

O Go tem o identificador iota, que numera as constantes de um bloco automaticamente (0, 1, 2...):

const (
	Explorador uint8 = 1 << iota
	Colecionador
	Invicto
	Veloz
	Mestre
)

Cada linha repete a expressão 1 << iota com o próximo número. É preferível quando a lista de bits é longa ou cresce com frequência, porque não há como errar a sequência. Para desligar bits, outra forma equivalente a c &^= Veloz é c &= ^Veloz (o ^ unário inverte todos os bits da máscara), mas o &^= é mais direto de ler.

Saída esperada

início           00001001 (2 de 5)
+colecionador    00001011 (3 de 5)
-veloz           00000011 (2 de 5)
+invicto +mestre 00010111 (4 de 5)
Tem Veloz? false
Tem Mestre? true
faltam           00001000
Progresso: 80%
← Anterior na trilha de GoDecisões com condicionais

Últimas lições

Ver o arquivo completo →