Strings, bytes e runes
Esta aula explora o tratamento de strings em Go, abordando a codificação UTF-8, a iteração com range, e os pacotes strings e strconv. O aluno aprenderá a manipular textos corretamente, considerando caracteres multibyte e conversões de tipos.
Em Go, strings são sequências imutáveis de bytes, não de caracteres. Isso significa que uma string pode conter qualquer dado binário, mas quando lidamos com texto, geralmente usamos a codificação UTF-8, que é o padrão em Go. Nesta aula, vamos entender como Go lida com UTF-8, como percorrer strings corretamente usando range, e explorar os pacotes strings e strconv para manipulação e conversão.
Dominar esses conceitos é essencial para evitar bugs comuns, como contar caracteres incorretamente ou corromper dados ao processar texto internacionalizado. Vamos começar!
UTF-8 em Go
Go adota UTF-8 como codificação padrão para strings de texto. Isso significa que cada caractere (rune) pode ocupar de 1 a 4 bytes. Um rune é um tipo inteiro que representa um ponto de código Unicode (int32). Por exemplo, a letra 'A' (U+0041) ocupa 1 byte, enquanto o emoji '😀' (U+1F600) ocupa 4 bytes.
Para acessar os bytes individuais de uma string, podemos indexá-la como um slice de bytes. No entanto, para obter caracteres reais, devemos usar a iteração com range ou funções do pacote unicode/utf8. Exemplo:
s := "Olá, 🌍"
fmt.Println(len(s)) // 10 (bytes)
fmt.Println(len([]rune(s))) // 5 (runes)
// Iterando sobre bytes
for i := 0; i < len(s); i++ {
fmt.Printf("%x ", s[i])
}
// Output: 4f 6c c3 a1 2c 20 f0 9f 8c 8d
Perceba que 'á' é codificado como dois bytes (c3 a1) e o globo como quatro (f0 9f 8c 8d). O pacote unicode/utf8 oferece funções como DecodeRuneInString para decodificar runes manualmente.
range em strings
Quando usamos range em uma string, ele itera sobre runes (caracteres Unicode), não sobre bytes. A cada iteração, obtemos o índice inicial do rune (em bytes) e o próprio rune. Isso é seguro e eficiente para processar texto.
s := "Olá, 🌍"
for i, r := range s {
fmt.Printf("índice %d: %c (U+%04X)\n", i, r, r)
}
// Output:
// índice 0: O (U+004F)
// índice 1: l (U+006C)
// índice 2: á (U+00E1)
// índice 4: , (U+002C)
// índice 5: (U+0020)
// índice 6: 🌍 (U+1F30D)
Note que o índice pula de 2 para 4 porque 'á' ocupa 2 bytes, e de 5 para 6 porque o espaço é 1 byte. Se precisar apenas dos runes, pode ignorar o índice com for _, r := range s.
Pacote strings
O pacote strings oferece funções para manipular strings UTF-8 de forma eficiente. As principais incluem:
Contains(s, substr)- verifica se s contém substr.Count(s, substr)- conta ocorrências não sobrepostas.HasPrefix(s, prefix)/HasSuffix(s, suffix).Index(s, substr)- retorna o índice do primeiro byte da primeira ocorrência.Join(elems, sep)- concatena um slice de strings com separador.Split(s, sep)- divide uma string em slice.ToLower(s)/ToUpper(s)- conversão para minúsculas/maiúsculas (respeita Unicode).Trim(s, cutset)- remove caracteres do conjunto.
Exemplo prático:
import "strings"
s := " Olá, Mundo! "
trimmed := strings.TrimSpace(s) // "Olá, Mundo!"
parts := strings.Split(trimmed, ", ") // ["Olá", "Mundo!"]
joined := strings.Join(parts, "...") // "Olá...Mundo!"
fmt.Println(strings.Contains(joined, "Mundo")) // true
Essas funções são otimizadas e lidam corretamente com UTF-8, sem quebrar caracteres multibyte.
Pacote strconv
O pacote strconv converte entre strings e tipos numéricos (int, float, bool) e também oferece funções para escapar/unescape strings. As funções mais usadas são:
Atoi(s)- converte string para int.Itoa(i)- converte int para string.ParseFloat(s, bitSize)- converte string para float64/float32.FormatFloat(f, fmt, prec, bitSize)- formata float para string.Quote(s)- retorna uma string com aspas duplas e escapes.Unquote(s)- interpreta escapes em uma string entre aspas.
Exemplo:
import "strconv"
numStr := "42"
num, err := strconv.Atoi(numStr)
if err != nil {
fmt.Println("erro:", err)
} else {
fmt.Println(num + 1) // 43
}
f := 3.14159
fStr := strconv.FormatFloat(f, 'f', 2, 64)
fmt.Println(fStr) // "3.14"
quoted := strconv.Quote("Hello\nWorld")
fmt.Println(quoted) // "\"Hello\\nWorld\""
Cuidado: Atoi retorna erro se a string não for um número inteiro válido. Use ParseInt para bases diferentes.
Boas práticas
- Sempre use
rangepara iterar sobre caracteres de uma string, não indexação por byte. - Para contar caracteres, use
utf8.RuneCountInString(s)oulen([]rune(s)). - Ao construir strings, prefira
strings.Builderpara eficiência. - Valide conversões com
strconvsempre verificando erros.
Referências
- Strings, bytes, runes and characters in Go (Blog Oficial)
- Documentação do pacote strings
- Documentação do pacote strconv
- Documentação do pacote unicode/utf8
- Especificação da linguagem: tipos string
- UTF-8 na Wikipedia
Exercícios
Escreva uma função que receba uma string e retorne a quantidade de runes (caracteres Unicode) nela contidos. Teste com a string "Hello, 世界".
✓ Resposta:func countRunes(s string) int { return len([]rune(s)) } // Teste: fmt.Println(countRunes("Hello, 世界")) // 9Usando
range, escreva um programa que imprima cada rune de uma string junto com seu índice (em bytes). Teste com "Go é show!".✓ Resposta:s := "Go é show!" for i, r := range s { fmt.Printf("índice %d: %c\n", i, r) } // Output: // índice 0: G // índice 1: o // índice 2: (espaço) // índice 3: é // índice 5: (espaço) // índice 6: s // índice 7: h // índice 8: o // índice 9: w // índice 10: !Use o pacote
stringspara verificar se a string "Go é demais!" contém a palavra "demais" e substitua por "top". Imprima o resultado.✓ Resposta:s := "Go é demais!" if strings.Contains(s, "demais") { s = strings.Replace(s, "demais", "top", 1) } fmt.Println(s) // "Go é top!"Converta a string "123" para inteiro usando
strconv.Atoi. Em seguida, converta o inteiro 456 para string usandostrconv.Itoa. Imprima a soma dos dois números (como inteiro).✓ Resposta:num1, _ := strconv.Atoi("123") num2 := 456 soma := num1 + num2 fmt.Println(soma) // 579Escreva uma função que receba uma string e retorne uma nova string com todas as letras minúsculas e sem espaços no início e fim. Use funções dos pacotes
strings.✓ Resposta:func cleanString(s string) string { return strings.ToLower(strings.TrimSpace(s)) } // Teste: fmt.Println(cleanString(" Olá Mundo! ")) // "olá mundo!"