ENTENDA A IDEIA
A ideia central
Em um laboratório de análises, cada amostra ganha uma etiqueta na entrada: sangue, urina, tecido. A etiqueta não é burocracia — é o que impede a centrífuga de sangue de receber uma amostra de tecido e produzir um resultado sem sentido com cara de resultado válido. Ninguém "descobre" o tipo da amostra na hora do exame; ele foi declarado na entrada e conferido em cada etapa.
ENTENDA A IDEIA
Valores e tipos: o que a tipagem estática garante (e o que ela recusa) · parte 2
Tipos em Go funcionam assim. Todo valor tem um tipo conhecido em tempo de compilação — int, float64, string, bool — e toda operação declara quais tipos aceita. Quando os tipos não batem, o programa não compila. Compare com o outro modelo: em JavaScript, "10" - 1 vale 9, mas "10" + 1 vale "101" — o interpretador adivinha uma conversão, e cada adivinhação é uma chance de o exame errado rodar sem ninguém notar. A postura de Go é a do laboratório: nada se converte sozinho. Se você quer tratar um int como float64, escreve float64(x) — e essa conversão explícita é o registro visível de que a mudança de tipo foi intencional.
ENTENDA A IDEIA
Valores e tipos: o que a tipagem estática garante (e o que ela recusa) · parte 3
O que você ganha em troca dessa cerimônia é uma categoria inteira de bugs que deixa de existir. O NaN que atravessa dez funções até explodir num relatório, o "undefined" concatenado numa URL, o campo que era número e virou string no caminho — nada disso sobrevive à compilação em Go. O compilador vira um revisor que confere cada fronteira de tipo do programa inteiro, em milissegundos, antes de qualquer execução.
Nesta aula você conhece os quatro tipos que carregam a maior parte de qualquer programa. int para números inteiros — contagens, índices e, como você vai ver nos exercícios, dinheiro em centavos. float64 para medidas com fração — mas com uma ressalva importante sobre precisão que custa dinheiro real a quem ignora. string para texto: uma sequência imutável de bytes, por convenção em UTF-8 — imutável significa que nenhuma operação altera uma string existente; operações como a concatenação que você já usou criam sempre uma string nova. E bool para true e false — e nada além deles: em Go, if 1 {} não compila, porque inteiro não é booleano nem "verdadeiroso".
ENTENDA A IDEIA
Valores e tipos: o que a tipagem estática garante (e o que ela recusa) · parte 4
Os booleanos merecem um parágrafo próprio, porque é deles que sai o fluxo de controle das próximas aulas. Valores bool nascem de três fontes: dos literais true e false, de comparações (price > 4000, name == "Maria", cents != 0) e dos operadores lógicos && (e), || (ou) e ! (não). Uma comparação em Go é uma expressão comum que produz um bool — você pode guardá-la em uma variável (isExpensive := price > 4000), passá-la para uma função ou devolvê-la de uma, exatamente como faria com um número. E as comparações também obedecem à regra dos tipos: comparar int com float64 sem conversão é o mesmo erro de compilação que somá-los.
Uma pergunta justa neste ponto: se os tipos são tão rigorosos, como um int vira texto para aparecer em uma etiqueta, e como o texto digitado por um usuário vira número? A resposta tem duas partes. Para exibição, o pacote fmt que você já usa formata qualquer tipo com os verbos %d, %s, %f — é conversão para texto, mas declarada no template, visível. Para o caminho inverso — "1990" virar 1990 — existe o pacote strconv, que devolve dois valores: o número e um erro, porque nem toda string é um número válido. Você vai encontrá-lo na aula de stdlib; por enquanto, o que importa é o princípio: a fronteira entre texto e número em Go é sempre atravessada por uma função explícita que pode falhar, nunca por coerção automática que "dá um jeito".
A regra da conversão explícita vai aparecer três vezes nos exercícios de hoje, cada vez com uma consequência diferente: uma divisão que descarta centavos, uma divisão que os preserva e uma formatação que decide entre os dois caminhos. No fim da aula, "que tipo é esse valor?" terá virado a primeira pergunta que você faz ao ler qualquer linha de Go — que é exatamente o hábito que a linguagem quer instalar.
CÓDIGO ANOTADO
Código anotado · parte 1
O programa a seguir apresenta os quatro tipos em um cenário concreto — os dados de um pedido em uma loja — e mostra as duas regras que causam surpresa em quem chega: divisão inteira e conversão obrigatória.
Além de fmt, nada de novo nos imports. As declarações usam var nome tipo = valor — a forma completa, que deixa o tipo visível para fins didáticos. Na próxima aula você verá a forma curta que o Go infere sozinho.
package main
import "fmt" CÓDIGO ANOTADO
Código anotado · parte 2
Quatro valores, quatro tipos. itemCents guarda o preço em centavos, como inteiro — convenção universal em sistemas de pagamento, pela razão que o chunk 4 demonstra. fmt.Printf com o verbo %T imprime o tipo de um valor: ferramenta de investigação que vale conhecer cedo.
func main() {
var itemName string = "Camiseta Gopher"
var itemCents int = 4990
var weightKg float64 = 0.25
var inStock bool = true
fmt.Printf("%v é %T\n", itemName, itemName)
fmt.Printf("%v é %T\n", itemCents, itemCents)
fmt.Printf("%v é %T\n", weightKg, weightKg)
fmt.Printf("%v é %T\n", inStock, inStock) CÓDIGO ANOTADO
Código anotado · parte 3
Primeira regra: divisão entre inteiros produz inteiro, descartando a fração — e o operador % entrega o resto que a divisão descartou. Juntos, eles decompõem qualquer valor em centavos: 4990/100 dá 49 reais e 4990%100 dá 90 centavos. Nenhum float envolvido, nenhum arredondamento possível.
reais := itemCents / 100
cents := itemCents % 100
fmt.Printf("preço: R$ %d.%02d\n", reais, cents) CÓDIGO ANOTADO
Código anotado · parte 4
Segunda regra: tipos diferentes não se misturam sem conversão. Somar o peso (float64) com a quantidade (int) direto não compila; float64(quantity) cria um novo valor do tipo certo e a soma passa a ser entre iguais. A conversão é sempre visível no código — é o "assinei embaixo" da mudança de tipo.
quantity := 3
totalWeight := weightKg * float64(quantity)
fmt.Println("peso total do pacote:", totalWeight, "kg") CÓDIGO ANOTADO
Código anotado · parte 5
E o motivo de dinheiro ser int: floats de 64 bits são frações binárias, e 0.1 ou 0.2 não têm representação binária exata — assim como 1/3 não tem representação decimal exata. O erro é minúsculo, mas existe, e a comparação com 0.3 falha. Em centavos inteiros, 10+20 == 30 sem ressalva.
fmt.Println("0.1 + 0.2 == 0.3?", 0.1+0.2 == 0.3)
fmt.Println("0.1 + 0.2 =", 0.1+0.2)
fmt.Println("em centavos:", 10+20 == 30)
} EXPERIMENTE
Experimente com o código
Três previsões antes de rodar: qual o valor de 7 / 2? E de 7.0 / 2? E a linha do float64(7) / 2 imprime o mesmo que qual das duas anteriores?
Agora quebre o programa de propósito, um erro por vez, e leia cada mensagem: troque a última linha por price / 100.5 (int e float não se misturam); tente var flag bool = 1 (inteiro não é booleano); e escreva if price { fmt.Println("caro") } (condição exige bool — compare com price > 4000). Essas três mensagens de erro são o compilador aplicando exatamente as regras desta aula. Para fechar, adicione fmt.Println(price > 4000 && price < 10000) e confira que a expressão inteira imprime um booleano — comparações são valores como quaisquer outros.
DESAFIO
Converta centavos para reais
Converta centavos para reais
O sistema guarda preços em centavos (int), mas o relatório de vendas exibe reais com fração. Implemente CentsToReais(cents int) float64 que converte um valor em centavos para o valor correspondente em reais.
Entrada: um valor não negativo em centavos. Saída: o valor em reais como float64.
Exemplos:
CentsToReais(1990)→19.9;CentsToReais(1)→0.01;CentsToReais(0)→0.
Parece uma divisão por 100 — e é. A questão do exercício é onde acontece a conversão de tipo. Se seu resultado para 1990 está dando 19 em vez de 19.9, você encontrou a armadilha; releia o chunk 3 do código anotado.
Dica 1
Dividir dois inteiros em Go descarta a parte fracionária; você precisa que a divisão aconteça entre floats.
Dica 2
Converta antes de dividir: float64(cents) / 100.
A solução aparece depois do acerto ou de 3 tentativas.
Solução e explicação
package main
// CentsToReais converte um valor em centavos para reais.
func CentsToReais(cents int) float64 {
return float64(cents) / 100
}
Ver harness de testes somente leitura
package main
import (
"encoding/json"
"fmt"
"time"
)
type caseResult struct {
Case int `json:"case"`
Pass bool `json:"pass"`
Input string `json:"input,omitempty"`
Want string `json:"want,omitempty"`
Got string `json:"got,omitempty"`
Ms int64 `json:"ms,omitempty"`
}
func main() {
cases := []struct {
cents int
want float64
}{
{1990, 19.9},
{100, 1},
{1, 0.01},
{0, 0},
}
for index, test := range cases {
started := time.Now()
got := CentsToReais(test.cents)
result := caseResult{
Case: index + 1, Pass: got == test.want,
Input: fmt.Sprintf("%d", test.cents),
Want: fmt.Sprintf("%g", test.want), Got: fmt.Sprintf("%g", got),
Ms: time.Since(started).Milliseconds(),
}
line, _ := json.Marshal(result)
fmt.Printf("RESULT %s\n", line)
}
}
DESAFIO
Divida a conta sem perder centavo
Divida a conta sem perder centavo
Quatro pessoas jantaram e a conta veio em centavos. Implemente SplitBill(totalCents int, people int) (int, int) que devolve dois valores: quanto cada pessoa paga (parte inteira, em centavos) e quantos centavos sobram sem dono.
Entrada: o total em centavos (não negativo) e o número de pessoas (sempre maior que zero). Saída: o par (parte, sobra), ambos em centavos, tal que parte*people + sobra == totalCents e sobra < people.
Exemplos:
SplitBill(1000, 3)→(333, 1)— R$ 3,33 para cada, 1 centavo sobrando;SplitBill(1000, 4)→(250, 0);SplitBill(7, 10)→(0, 7)— conta menor que o grupo: ninguém paga, sobram 7.
Devolver dois valores é novidade — em Go isso é nativo: return a, b com a assinatura (int, int). A divisão inteira que era armadilha no exercício 1 é a ferramenta certa aqui.
Dica 1
A divisão inteira que atrapalhou no exercício 1 é exatamente a ferramenta certa aqui — junto com a irmã dela.
Dica 2
A parte de cada um é totalCents / people; a sobra é totalCents % people.
A solução aparece depois do acerto ou de 3 tentativas.
Solução e explicação
package main
// SplitBill divide uma conta em centavos entre pessoas.
func SplitBill(totalCents int, people int) (int, int) {
return totalCents / people, totalCents % people
}
Ver harness de testes somente leitura
package main
import (
"encoding/json"
"fmt"
"time"
)
type caseResult struct {
Case int `json:"case"`
Pass bool `json:"pass"`
Input string `json:"input,omitempty"`
Want string `json:"want,omitempty"`
Got string `json:"got,omitempty"`
Ms int64 `json:"ms,omitempty"`
}
func main() {
cases := []struct {
totalCents int
people int
wantShare int
wantRest int
}{
{1000, 3, 333, 1},
{1000, 4, 250, 0},
{7, 10, 0, 7},
{9999, 2, 4999, 1},
}
for index, test := range cases {
started := time.Now()
gotShare, gotRest := SplitBill(test.totalCents, test.people)
pass := gotShare == test.wantShare && gotRest == test.wantRest
result := caseResult{
Case: index + 1, Pass: pass,
Input: fmt.Sprintf("%d, %d", test.totalCents, test.people),
Want: fmt.Sprintf("%d, %d", test.wantShare, test.wantRest),
Got: fmt.Sprintf("%d, %d", gotShare, gotRest),
Ms: time.Since(started).Milliseconds(),
}
line, _ := json.Marshal(result)
fmt.Printf("RESULT %s\n", line)
}
}
DESAFIO
Formate a etiqueta de preço
Formate a etiqueta de preço
Junte as peças da aula: inteiros, divisão e resto, string e booleano. Implemente PriceLabel(name string, cents int, onSale bool) string que monta a etiqueta de exibição de um produto.
Entrada: o nome do produto, o preço em centavos (não negativo) e se está em promoção. Saída: a string ": R$ .", com os centavos sempre em dois dígitos; quando onSale é true, o prefixo "PROMO " antecede tudo.
Exemplos:
PriceLabel("Camiseta", 4990, false)→"Camiseta: R$ 49.90";PriceLabel("Caneca", 1950, true)→"PROMO Caneca: R$ 19.50";PriceLabel("Adesivo", 305, false)→"Adesivo: R$ 3.05"— repare no zero:3.5estaria errado.
O caso 305 é o que separa soluções corretas de quase corretas: 305 % 100 dá 5, e a etiqueta precisa exibir 05. Procure na dica o verbo de formatação que resolve isso.
Dica 1
Separe reais e centavos com / e % do exercício 2; o verbo %02d imprime um inteiro com zero à esquerda quando ele tem um dígito só.
Dica 2
Monte a etiqueta base com Sprintf e decida o prefixo PROMO com um if sobre o booleano — sem comparar onSale == true.
A solução aparece depois do acerto ou de 3 tentativas.
Solução e explicação
package main
import "fmt"
// PriceLabel formata a etiqueta de preço de um produto a partir dos centavos.
func PriceLabel(name string, cents int, onSale bool) string {
label := fmt.Sprintf("%s: R$ %d.%02d", name, cents/100, cents%100)
if onSale {
return "PROMO " + label
}
return label
}
Ver harness de testes somente leitura
package main
import (
"encoding/json"
"fmt"
"time"
)
type caseResult struct {
Case int `json:"case"`
Pass bool `json:"pass"`
Input string `json:"input,omitempty"`
Want string `json:"want,omitempty"`
Got string `json:"got,omitempty"`
Ms int64 `json:"ms,omitempty"`
}
func main() {
cases := []struct {
name string
cents int
onSale bool
want string
}{
{"Camiseta", 4990, false, "Camiseta: R$ 49.90"},
{"Caneca", 1950, true, "PROMO Caneca: R$ 19.50"},
{"Adesivo", 305, false, "Adesivo: R$ 3.05"},
{"Curso", 14900, true, "PROMO Curso: R$ 149.00"},
}
for index, test := range cases {
started := time.Now()
got := PriceLabel(test.name, test.cents, test.onSale)
result := caseResult{
Case: index + 1, Pass: got == test.want,
Input: fmt.Sprintf("%q, %d, %t", test.name, test.cents, test.onSale),
Want: test.want, Got: got,
Ms: time.Since(started).Milliseconds(),
}
line, _ := json.Marshal(result)
fmt.Printf("RESULT %s\n", line)
}
}
VOCÊ CHEGOU AO RESUMO
O que você leva desta aula
- Todo valor em Go tem um tipo fixo conhecido na compilação; os quatro do dia a dia são
int,float64,string(imutável) ebool— e condições exigemboolde verdade, sem "truthy". - Nada se converte sozinho: misturar tipos numéricos exige conversão explícita como
float64(x), e a posição da conversão muda o resultado — converta o operando, não o resultado da conta. - Divisão entre inteiros descarta a fração e
%entrega o resto; o par/e%decompõe valores discretos sem erro possível. - Floats seguem IEEE 754:
0.1 + 0.2 != 0.3em Go como em toda linguagem; dinheiro se representa em centavos inteiros e só vira decimal na exibição, com%d.%02d. %TnoPrintfrevela o tipo de qualquer valor — sua ferramenta de investigação nas próximas aulas.
Na próxima aula, imersao/04-variaveis, você vê as formas de declarar variáveis — var, := e a inferência de tipos que dispensa escrever o que o compilador já sabe — e os zero values: o valor que toda variável de Go tem antes de você atribuir qualquer coisa.