Operadores
por Sloane KensingtonEntenda 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.
/entre inteiros descarta a parte decimal e%dá o resto; o sinal do resto segue o dividendo:-7 % 3é-1.x += 5é o mesmo quex = x + 5;x++ex--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 &^= flagdesliga um bit ec&flag != 0testa 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
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
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
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.
- 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.
package main
import "fmt"
func main() {
casa := 0
visitante := 0
// registre as jogadas e mostre o resultado
}
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
casaevisitantecomeçam em0com:=; o tipo deduzido éint, e cada jogada só altera o valor que já existe.casa += 3evisitante += 2registram as cestas de três e de dois: é a forma curta decasa = casa + 3, sem repetir o nome da variável.- Os dois lances livres usam
casa++, porque cada um vale exatamente 1 ponto;++é a forma idiomática de somar 1. casa -= 2desfaz a cesta anulada pela arbitragem: o mesmo raciocínio do+=, só que subtraindo.- O total é a expressão
casa+visitantedireto noPrintln; não é preciso criar uma variável para um valor usado uma vez só. - A cadeia
if/else if/elsedecide 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 diriavence 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.
horariousa%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).
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)
}
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
const minutosPorDia = 24 * 60é calculada pelo compilador, e a conta mostra de onde vem o1440.paraMinutosfazh*60 + m: como*tem precedência maior que+, a multiplicação acontece primeiro e os parênteses seriam redundantes.- Em
horario,minutos %= minutosPorDiafaz o relógio dar a volta:1360 + 95 = 1455, e o resto da divisão por1440é15, ou seja,00:15. minutos/60é a divisão inteira que dá as horas, eminutos%60é o resto que dá os minutos; os dois saem do mesmo número.- 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 em00:00. totalForno += ...acumula o tempo de cada receita, e a última linha reaproveita/e%para mostrar6h40min.
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 * 60achando que éminutos % (24 * 60):%e*têm a mesma precedência e são avaliados da esquerda para a direita, então1360 % 24 * 60dá960. Use a constante ou os parênteses. - Esquecer o
%=emhorario: o pão terminaria às24:15. - Formatar com
%d:%d: o bolo começaria às15:5. O%02dgarante 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.
- Cada conquista é uma constante
uint8criada com<<, e a máscara com todas é feita com|. - Conquistas são ligadas com
|=, a revogada é desligada com&^=, e o teste usa&comparado com0. - A contagem usa
bits.OnesCount8e as faltantes são calculadas com&^sobre a máscara. - Os bits aparecem com
%08be o progresso é inteiro, calculado sem perder a precisão na divisão.
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)
}
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
- As constantes usam
1 << 0,1 << 1... para que cada conquista ocupe um bit diferente:Veloz, por exemplo, é00001000. todasjunta as cinco com|numa máscara00011111; ela é constante porque as conquistas existentes não mudam durante o jogo.mostrarimprime os bits com%08b(8 dígitos, com zeros à esquerda) e conta as conquistas combits.OnesCount8, que devolve quantos bits estão ligados.temretornac&conquista != 0. Em Go,&tem precedência maior que!=, então a expressão é lida como(c&conquista) != 0, sem parênteses.- No
main,c |= Explorador | Velozliga dois bits de uma vez; depoisc |= Colecionadorliga mais um sem mexer nos outros. c &^= Velozdesliga só o bit da conquista revogada. O operador&^(e não) zera no resultado os bits que estão ligados à direita.todas&^creaproveita o mesmo operador para descobrir o que falta: parte da máscara completa e apaga o que o jogador já tem.- 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&é umuint8, não umbool; 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 inteira4/5dá0antes da multiplicação e o progresso sai0%. - Criar uma sexta conquista com
1 << 8: não compila (constant 256 ... overflows), porque umuint8só tem os bits de 0 a 7. Para mais conquistas, useuint16ouuint32.
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%