Processamento de Strings
Lição 19: Processamento de Strings
🎯 Analogia da Vida
Imagine que você é um bibliotecário. Todos os dias você lida com uma grande quantidade de trabalho com texto:
- Pesquisar: Verificar se o título de um livro contém uma determinada palavra-chave →
strings.Contains - Substituir: Substituir etiquetas antigas por novas →
strings.Replace - Separar: Separar uma lista de tags delimitada por vírgulas →
strings.Split - Juntar: Combinar várias palavras-chave em uma consulta de pesquisa →
strings.Join - Recortar: Remover espaços extras do início e do final de um título de livro →
strings.Trim
O processamento de strings é o "trabalho com texto" nos programas — quase todo programa precisa lidar com texto.
Conceitos Centrais
Go fornece várias bibliotecas padrão para manipulação de strings:
| Pacote | Propósito | Funções Comuns |
|---|---|---|
strings |
Pesquisa, substituição, divisão, junção de strings, etc. | Contains, Replace, Split, Join, Trim |
strconv |
Conversão entre strings e outros tipos | Atoi, Itoa, ParseBool, FormatFloat |
unicode/utf8 |
Operações relacionadas à codificação UTF-8 | RuneCountInString, ValidString |
strings.Builder |
Concatenação eficiente de muitas strings | WriteString, String |
Pontos-chave:
- Strings em Go são imutáveis — qualquer modificação cria uma nova string
- Strings em Go são sequências de bytes codificadas em UTF-8 internamente
len(str)retorna o número de bytes, não de caracteresruneé o tipo do Go para representar pontos de código Unicode (essencialmenteint32)
Sintaxe Básica e Uso
1. Pacote strings
package main
import (
"fmt"
"strings"
)
func main() {
str := "Hello, Go语言!"
// Pesquisar
fmt.Println(strings.Contains(str, "Go")) // true
fmt.Println(strings.HasPrefix(str, "Hello")) // true
fmt.Println(strings.HasSuffix(str, "!")) // true
fmt.Println(strings.Index(str, "Go")) // 7
// Substituir
result := strings.Replace(str, "Go", "Golang", 1)
fmt.Println(result) // "Hello, Golang语言!"
// Substituir todas as ocorrências
s := "aabbcc"
fmt.Println(strings.ReplaceAll(s, "a", "x")) // "xxbbcc"
// Dividir e juntar
csv := "apple,banana,cherry"
fruits := strings.Split(csv, ",")
fmt.Println(fruits) // [apple banana cherry]
joined := strings.Join(fruits, " | ")
fmt.Println(joined) // "apple | banana | cherry"
// Recortar
padded := " Hello World "
fmt.Println(strings.TrimSpace(padded)) // "Hello World"
fmt.Println(strings.Trim("##Hello##", "#")) // "Hello"
fmt.Println(strings.TrimLeft("##Hello##", "#")) // "Hello##"
// Conversão de maiúsculas/minúsculas
fmt.Println(strings.ToUpper("hello")) // "HELLO"
fmt.Println(strings.ToLower("HELLO")) // "hello"
// Repetir
fmt.Println(strings.Repeat("Go", 3)) // "GoGoGo"
// Contar
fmt.Println(strings.Count("banana", "an")) // 2
}
strings.Split é uma string vazia, a string é dividida em um slice de caracteres individuais.
2. Pacote strconv
package main
import (
"fmt"
"strconv"
)
func main() {
// String → Inteiro
num, err := strconv.Atoi("42")
if err != nil {
fmt.Println("Conversão falhou:", err)
}
fmt.Println(num) // 42
// Inteiro → String
str := strconv.Itoa(42)
fmt.Println(str) // "42"
// String → Booleano
b, err := strconv.ParseBool("true")
fmt.Println(b, err) // true <nil>
// String → Float
f, err := strconv.ParseFloat("3.14", 64)
fmt.Println(f, err) // 3.14 <nil>
// Float → String
// 'f' significa formato normal, -1 significa dígitos mínimos, 64 significa float64
s := strconv.FormatFloat(3.14, 'f', -1, 64)
fmt.Println(s) // "3.14"
// Saída formatada (semelhante ao sprintf do C)
formatted := strconv.FormatInt(255, 16) // Hexadecimal
fmt.Println(formatted) // "ff"
}
strconv.Atoi é equivalente a strconv.ParseInt(s, 10, 0), retornando o tipo int dependente da plataforma.
3. Pacote unicode/utf8
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
str := "Go语言编程"
// len() retorna o número de bytes
fmt.Println(len(str)) // 14 (cada caractere chinês ocupa 3 bytes)
// utf8.RuneCountInString() retorna o número de caracteres
fmt.Println(utf8.RuneCountInString(str)) // 7
// Verificar se é um UTF-8 válido
fmt.Println(utf8.ValidString(str)) // true
fmt.Println(utf8.ValidString("abc")) // true
// Iterar sobre cada rune na string
for i, r := range str {
fmt.Printf("Índice:%d Caractere:%c Unicode:%U\n", i, r, r)
}
}
range para iterar sobre uma string, Go itera automaticamente por rune (caractere Unicode), não por byte.
4. strings.Builder (Concatenação Eficiente)
package main
import (
"fmt"
"strings"
)
func main() {
// ❌ Ineficiente: cria uma nova string a cada concatenação
// result := ""
// for i := 0; i < 1000; i++ {
// result += "a" // aloca nova memória a cada vez
// }
// ✅ Eficiente: use strings.Builder
var builder strings.Builder
for i := 0; i < 1000; i++ {
builder.WriteString("a")
}
result := builder.String()
fmt.Println(len(result)) // 1000
// Pré-alocar capacidade para desempenho ainda melhor
var builder2 strings.Builder
builder2.Grow(1000) // Pré-alocar 1000 bytes
for i := 0; i < 1000; i++ {
builder2.WriteString("b")
}
fmt.Println(builder2.Len()) // 1000
}
strings.Builder internamente usa um slice []byte, evitando alocações frequentes de memória causadas pela imutabilidade das strings.
Código de Exemplo
Exemplo: Estatísticas e Análise de Strings (Dificuldade ⭐)
package main
import (
"fmt"
"strings"
"unicode"
)
// Analisa as contagens de diferentes tipos de caracteres em uma string
func analyzeString(s string) (letters, digits, spaces, others int) {
for _, r := range s {
switch {
case unicode.IsLetter(r):
letters++
case unicode.IsDigit(r):
digits++
case unicode.IsSpace(r):
spaces++
default:
others++
}
}
return
}
func main() {
text := "Hello, Go语言! 2024年 Version 2.0"
letters, digits, spaces, others := analyzeString(text)
fmt.Printf("Texto: %q\n", text)
fmt.Printf("Letras: %d\n", letters)
fmt.Printf("Dígitos: %d\n", digits)
fmt.Printf("Espaços: %d\n", spaces)
fmt.Printf("Outros: %d\n", others)
// Contar frequência de palavras
words := strings.Fields("the go the language the world")
freq := make(map[string]int)
for _, w := range words {
freq[strings.ToLower(w)]++
}
fmt.Println("\nFrequência de palavras:", freq)
}
Saída:
Texto: "Hello, Go语言! 2024年 Version 2.0"
Letras: 17
Dígitos: 6
Espaços: 5
Outros: 3
Frequência de palavras: map[go:1 language:1 the:3 world:1]
Exemplo: Analisador CSV (Dificuldade ⭐⭐)
package main
import (
"fmt"
"strings"
)
// Analisador simples de linhas CSV com suporte a campos entre aspas
func parseCSVLine(line string) []string {
var fields []string
var current strings.Builder
inQuotes := false
for _, r := range line {
switch {
case r == '"' && !inQuotes:
// Entrar na região entre aspas
inQuotes = true
case r == '"' && inQuotes:
// Sair da região entre aspas
inQuotes = false
case r == ',' && !inQuotes:
// Encontrou delimitador, salvar campo atual
fields = append(fields, current.String())
current.Reset()
default:
// Caractere normal
current.WriteRune(r)
}
}
// Salvar o último campo
fields = append(fields, current.String())
return fields
}
// Limpar e formatar campos
func cleanFields(fields []string) []string {
cleaned := make([]string, len(fields))
for i, f := range fields {
cleaned[i] = strings.TrimSpace(f)
}
return cleaned
}
func main() {
// Dados CSV simulados
csvData := []string{
`Alice,28,"Beijing, China"`,
`Bob,35,"New York, USA"`,
`Charlie,42,"London, UK"`,
}
fmt.Println("=== Resultados da Análise CSV ===")
for _, line := range csvData {
fields := parseCSVLine(line)
fields = cleanFields(fields)
fmt.Printf("Nome: %-10s Idade: %-4s Local: %s\n",
fields[0], fields[1], fields[2])
}
// Operação inversa: juntar um slice em uma linha CSV
record := []string{"David", "30", "Shanghai, China"}
csvLine := strings.Join(record, ",")
fmt.Println("\nLinha CSV gerada:", csvLine)
}
Saída:
=== Resultados da Análise CSV ===
Nome: Alice Idade: 28 Local: Beijing, China
Nome: Bob Idade: 35 Local: New York, USA
Nome: Charlie Idade: 42 Local: London, UK
Linha CSV gerada: David,30,Shanghai, China
Exemplo: Motor de Templates (Dificuldade ⭐⭐⭐)
package main
import (
"fmt"
"strconv"
"strings"
)
// Motor de template simples: substitui {{key}} pelos valores correspondentes
func renderTemplate(template string, data map[string]string) string {
var result strings.Builder
result.Grow(len(template) * 2) // Estimar capacidade
i := 0
for i < len(template) {
// Encontrar "{{"
if i+1 < len(template) && template[i] == '{' && template[i+1] == '{' {
// Encontrar o "}}" correspondente
end := strings.Index(template[i+2:], "}}")
if end != -1 {
key := strings.TrimSpace(template[i+2 : i+2+end])
if value, ok := data[key]; ok {
result.WriteString(value)
} else {
// Chave não encontrada, manter como está
result.WriteString("{{" + key + "}}")
}
i += end + 4 // Pular "}}"
continue
}
}
result.WriteByte(template[i])
i++
}
return result.String()
}
// Formatar saída de tabela
func formatTable(headers []string, rows [][]string) string {
// Calcular largura máxima para cada coluna
colWidths := make([]int, len(headers))
for i, h := range headers {
colWidths[i] = len(h)
}
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) && len(cell) > colWidths[i] {
colWidths[i] = len(cell)
}
}
}
var b strings.Builder
// Escrever cabeçalho
for i, h := range headers {
b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], h))
}
b.WriteString("\n")
// Escrever linha separadora
for i := range headers {
b.WriteString(strings.Repeat("-", colWidths[i]) + "-+-")
}
b.WriteString("\n")
// Escrever linhas de dados
for _, row := range rows {
for i, cell := range row {
if i < len(colWidths) {
b.WriteString(fmt.Sprintf("%-*s | ", colWidths[i], cell))
}
}
b.WriteString("\n")
}
return b.String()
}
// Converter uma string numérica para diferentes representações de base
func toBases(numStr string) (map[string]string, error) {
num, err := strconv.ParseInt(numStr, 10, 64)
if err != nil {
return nil, err
}
return map[string]string{
"decimal": strconv.FormatInt(num, 10),
"binary": strconv.FormatInt(num, 2),
"octal": strconv.FormatInt(num, 8),
"hexadecimal": strconv.FormatInt(num, 16),
}, nil
}
func main() {
// 1. Renderização de template
fmt.Println("=== Renderização de Template ===")
template := "Olá, {{name}}! Bem-vindo a {{city}}. Você tem {{count}} novas mensagens."
data := map[string]string{
"name": "Alice",
"city": "Beijing",
"count": "5",
}
fmt.Println(renderTemplate(template, data))
// 2. Formatação de tabela
fmt.Println("\n=== Formatação de Tabela ===")
headers := []string{"Nome", "Idade", "Cidade"}
rows := [][]string{
{"Alice", "28", "Beijing"},
{"Bob", "35", "New York"},
{"Charlie", "42", "London"},
}
fmt.Print(formatTable(headers, rows))
// 3. Conversão de base
fmt.Println("\n=== Conversão de Base ===")
bases, _ := toBases("255")
for name, value := range bases {
fmt.Printf("%-12s: %s\n", name, value)
}
}
Saída:
=== Renderização de Template ===
Olá, Alice! Bem-vindo a Beijing. Você tem 5 novas mensagens.
=== Formatação de Tabela ===
Nome | Idade | Cidade |
--------+-------+-----------+-
Alice | 28 | Beijing |
Bob | 35 | New York |
Charlie | 42 | London |
=== Conversão de Base ===
decimal : 255
binary : 11111111
octal : 377
hexadecimal : ff
Cenários de Aplicação Prática
Cenário 1: Analisador de Logs
package main
import (
"fmt"
"strconv"
"strings"
"time"
)
// Estrutura de entrada de log
type LogEntry struct {
Timestamp string
Level string
Message string
Source string
}
// Analisar uma linha de log
// Formato: [2024-01-15 10:30:00] [ERROR] Database connection failed (db-service)
func parseLogLine(line string) (*LogEntry, error) {
entry := &LogEntry{}
// Extrair timestamp
if start := strings.Index(line, "["); start != -1 {
if end := strings.Index(line, "]"); end != -1 {
entry.Timestamp = line[start+1 : end]
line = line[end+1:]
}
}
// Extrair nível de log
line = strings.TrimSpace(line)
if start := strings.Index(line, "["); start != -1 {
if end := strings.Index(line, "]"); end != -1 {
entry.Level = line[start+1 : end]
line = line[end+1:]
}
}
// Extrair mensagem e fonte
line = strings.TrimSpace(line)
if parenStart := strings.LastIndex(line, "("); parenStart != -1 {
if parenEnd := strings.LastIndex(line, ")"); parenEnd != -1 {
entry.Source = line[parenStart+1 : parenEnd]
entry.Message = strings.TrimSpace(line[:parenStart])
}
} else {
entry.Message = line
}
return entry, nil
}
// Analisar níveis de log
func analyzeLogs(entries []LogEntry) map[string]int {
stats := make(map[string]int)
for _, e := range entries {
stats[strings.ToUpper(e.Level)]++
}
return stats
}
// Filtrar logs contendo uma palavra-chave
func filterLogs(entries []LogEntry, keyword string) []LogEntry {
var filtered []LogEntry
keyword = strings.ToLower(keyword)
for _, e := range entries {
if strings.Contains(strings.ToLower(e.Message), keyword) {
filtered = append(filtered, e)
}
}
return filtered
}
func main() {
// Dados de log simulados
logLines := []string{
"[2024-01-15 10:30:00] [INFO] Application started (main-service)",
"[2024-01-15 10:30:05] [INFO] Connected to database (db-service)",
"[2024-01-15 10:31:00] [WARN] High memory usage detected (monitor)",
"[2024-01-15 10:32:00] [ERROR] Database connection timeout (db-service)",
"[2024-01-15 10:32:01] [ERROR] Retry failed, switching to backup (db-service)",
"[2024-01-15 10:33:00] [INFO] Backup database connected (db-service)",
"[2024-01-15 10:35:00] [DEBUG] Cache cleared (cache-service)",
}
// Analisar todos os logs
var entries []LogEntry
for _, line := range logLines {
entry, err := parseLogLine(line)
if err == nil {
entries = append(entries, *entry)
}
}
// Estatísticas de nível de log
fmt.Println("=== Estatísticas de Nível de Log ===")
stats := analyzeLogs(entries)
for level, count := range stats {
fmt.Printf(" %s: %d entradas\n", level, count)
}
// Filtrar logs de erro
fmt.Println("\n=== Logs de Erro ===")
for _, e := range entries {
if strings.ToUpper(e.Level) == "ERROR" {
fmt.Printf(" %s | %s | %s\n", e.Timestamp, e.Message, e.Source)
}
}
// Pesquisar por palavra-chave
fmt.Println("\n=== Logs contendo 'database' ===")
filtered := filterLogs(entries, "database")
for _, e := range filtered {
fmt.Printf(" [%s] %s\n", e.Level, e.Message)
}
}
Saída:
=== Estatísticas de Nível de Log ===
INFO: 3 entradas
WARN: 1 entradas
ERROR: 2 entradas
DEBUG: 1 entradas
=== Logs de Erro ===
2024-01-15 10:32:00 | Database connection timeout | db-service
2024-01-15 10:32:01 | Retry failed, switching to backup | db-service
=== Logs contendo 'database' ===
[INFO] Connected to database
[ERROR] Database connection timeout
[INFO] Backup database connected
Cenário 2: Validação e Limpeza de Entrada do Usuário
package main
import (
"fmt"
"regexp"
"strconv"
"strings"
"unicode"
)
// Limpar entrada do usuário para nome de usuário
func sanitizeUsername(name string) (string, error) {
// Remover espaços do início e do final
name = strings.TrimSpace(name)
// Verificar comprimento
if len(name) < 3 {
return "", fmt.Errorf("nome de usuário muito curto (mínimo 3 caracteres)")
}
if len(name) > 20 {
return "", fmt.Errorf("nome de usuário muito longo (máximo 20 caracteres)")
}
// Permitir apenas letras, dígitos e sublinhados
var cleaned strings.Builder
for _, r := range name {
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' {
cleaned.WriteRune(r)
}
}
result := cleaned.String()
if len(result) < 3 {
return "", fmt.Errorf("poucos caracteres válidos")
}
return strings.ToLower(result), nil
}
// Validar e analisar número de telefone
func parsePhone(phone string) (string, error) {
// Remover todos os espaços e hífens
phone = strings.ReplaceAll(phone, " ", "")
phone = strings.ReplaceAll(phone, "-", "")
// Verificar se começa com +55 (Brasil) ou código similar
if strings.HasPrefix(phone, "+55") {
phone = phone[3:]
} else if strings.HasPrefix(phone, "55") {
phone = phone[2:]
}
// Validar comprimento
if len(phone) < 10 || len(phone) > 11 {
return "", fmt.Errorf("comprimento incorreto do número de telefone: %d dígitos", len(phone))
}
// Validar se todos são dígitos
for _, r := range phone {
if !unicode.IsDigit(r) {
return "", fmt.Errorf("número de telefone contém caractere não numérico: %c", r)
}
}
return phone, nil
}
// Analisar string de tamanho com unidades
func parseSize(sizeStr string) (int64, error) {
sizeStr = strings.TrimSpace(strings.ToUpper(sizeStr))
// Extrair partes numérica e de unidade
var numPart strings.Builder
var unitPart strings.Builder
for _, r := range sizeStr {
if unicode.IsDigit(r) || r == '.' {
numPart.WriteRune(r)
} else if unicode.IsLetter(r) {
unitPart.WriteRune(r)
}
}
num, err := strconv.ParseFloat(numPart.String(), 64)
if err != nil {
return 0, fmt.Errorf("número inválido: %s", numPart.String())
}
// Converter para bytes com base na unidade
unit := unitPart.String()
multipliers := map[string]int64{
"B": 1,
"KB": 1024,
"MB": 1024 * 1024,
"GB": 1024 * 1024 * 1024,
"TB": 1024 * 1024 * 1024 * 1024,
}
multiplier, ok := multipliers[unit]
if !ok {
return 0, fmt.Errorf("unidade desconhecida: %s", unit)
}
return int64(num * float64(multiplier)), nil
}
func main() {
// Testes de limpeza de nome de usuário
fmt.Println("=== Validação de Nome de Usuário ===")
usernames := []string{" Alice_123 ", "ab", "A!@#B", "GoDeveloper2024"}
for _, u := range usernames {
result, err := sanitizeUsername(u)
if err != nil {
fmt.Printf(" %q → Erro: %v\n", u, err)
} else {
fmt.Printf(" %q → %q\n", u, result)
}
}
// Testes de análise de número de telefone
fmt.Println("\n=== Análise de Número de Telefone ===")
phones := []string{"11 98765 4321", "+55-11-98765-4321", "12345", "987654321012"}
for _, p := range phones {
result, err := parsePhone(p)
if err != nil {
fmt.Printf(" %q → Erro: %v\n", p, err)
} else {
fmt.Printf(" %q → %s\n", p, result)
}
}
// Testes de análise de tamanho de arquivo
fmt.Println("\n=== Análise de Tamanho de Arquivo ===")
sizes := []string{"1.5GB", "512MB", "1024KB", "100B", "2TB"}
for _, s := range sizes {
bytes, err := parseSize(s)
if err != nil {
fmt.Printf(" %s → Erro: %v\n", s, err)
} else {
fmt.Printf(" %s → %d bytes\n", s, bytes)
}
}
}
Saída:
=== Validação de Nome de Usuário ===
" Alice_123 " → "alice_123"
"ab" → Erro: nome de usuário muito curto (mínimo 3 caracteres)
"A!@#B" → Erro: poucos caracteres válidos
"GoDeveloper2024" → "godeveloper2024"
=== Análise de Número de Telefone ===
"11 98765 4321" → 11987654321
"+55-11-98765-4321" → 11987654321
"12345" → Erro: comprimento incorreto do número de telefone: 5 dígitos
"987654321012" → Erro: comprimento incorreto do número de telefone: 12 dígitos
=== Análise de Tamanho de Arquivo ===
1.5GB → 1610612736 bytes
512MB → 536870912 bytes
1024KB → 1048576 bytes
100B → 100 bytes
2TB → 2199023255552 bytes
❓ Perguntas Frequentes
P1: Por que len("Go语言") retorna 8 em vez de 4?
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "Go语言"
// len() retorna bytes, não caracteres
fmt.Println("len():", len(s)) // 8
// Caracteres chineses ocupam 3 bytes cada em UTF-8
// G(1) + o(1) + 语(3) + 言(3) = 8
// Maneira correta de obter a contagem de caracteres
fmt.Println("RuneCountInString():", utf8.RuneCountInString(s)) // 4
// Ou use range para contar
count := 0
for range s {
count++
}
fmt.Println("contagem com range:", count) // 4
}
Ponto-chave: Ao lidar com caracteres multibyte como chinês, sempre use utf8.RuneCountInString() ou range para obter a contagem real de caracteres.
P2: Concatenação de strings — usar + ou strings.Builder?
package main
import (
"fmt"
"strings"
)
func main() {
// Algumas concatenações: use + (o compilador otimiza)
s := "Hello" + " " + "World"
fmt.Println(s)
// Muitas concatenações: use strings.Builder
var builder strings.Builder
for i := 0; i < 10000; i++ {
builder.WriteString("a")
}
fmt.Println("Comprimento:", builder.Len())
// Pré-alocação melhora ainda mais o desempenho
var builder2 strings.Builder
builder2.Grow(10000) // Pré-alocar 10000 bytes
for i := 0; i < 10000; i++ {
builder2.WriteString("b")
}
fmt.Println("Comprimento:", builder2.Len())
}
Regras Práticas:
| Cenário | Abordagem Recomendada |
|---|---|
| 2-3 concatenações de strings | + ou fmt.Sprintf |
| Concatenação em loop (contagem conhecida) | strings.Builder + Grow() |
| Concatenação em loop (contagem desconhecida) | strings.Builder |
P3: Como verificar se uma string contém apenas caracteres específicos?
package main
import (
"fmt"
"strings"
"unicode"
)
func main() {
s := "Hello123"
// Verificar se contém apenas letras e dígitos
isAlphanumeric := true
for _, r := range s {
if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
isAlphanumeric = false
break
}
}
fmt.Println("Apenas alfanumérico:", isAlphanumeric)
// Verificar se contém apenas letras ASCII
isASCII := true
for _, r := range s {
if r > 127 {
isASCII = false
break
}
}
fmt.Println("Apenas ASCII:", isASCII)
// Verificar se contém apenas um conjunto específico de caracteres
allowed := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
containsOnlyAllowed := true
for _, r := range s {
if !strings.ContainsRune(allowed, r) {
containsOnlyAllowed = false
break
}
}
fmt.Println("Dentro do intervalo permitido:", containsOnlyAllowed)
}
P4: Quando usar strings.Contains vs expressões regulares?
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
text := "Meu email é test@example.com, telefone é 11987654321"
// Pesquisa simples → use o pacote strings (mais rápido)
fmt.Println(strings.Contains(text, "example.com")) // true
// Correspondência de padrões → use expressões regulares
emailRegex := regexp.MustCompile(`[\w.]+@[\w.]+\.\w+`)
fmt.Println("Email:", emailRegex.FindString(text))
phoneRegex := regexp.MustCompile(`1[3-9]\d{9}`)
fmt.Println("Telefone:", phoneRegex.FindString(text))
}
Diretrizes:
- Pesquisa de string fixa: Use o pacote strings — melhor desempenho
- Correspondência de padrões (ex.: email, formato de telefone): Use o pacote regexp
- Evite compilar expressões regulares repetidamente dentro de loops; pré-compile-as
📖 Resumo
| Tópico | Conteúdo Principal |
|---|---|
| Pacote strings | Contains, HasPrefix, HasSuffix, Index, Replace, Split, Join, Trim, ToUpper, ToLower, Count, Repeat, Fields |
| Pacote strconv | Atoi, Itoa, ParseBool, ParseFloat, FormatInt, FormatFloat |
| unicode/utf8 | RuneCountInString, ValidString, unicode.IsLetter/IsDigit/IsSpace |
| strings.Builder | WriteString, WriteRune, WriteByte, Grow, String, Len |
| Princípios Fundamentais | Strings são imutáveis, len retorna bytes, range itera por rune, use Builder para muitas concatenações |
📝 Exercícios
Exercício 1: Básico — Inversão de String
Escreva uma função reverseString(s string) string que inverte uma string. Ela deve lidar corretamente com caracteres chineses.
Dica: Você não pode simplesmente converter a string para []byte e invertê-la, porque caracteres chineses ocupam múltiplos bytes.
// Resultados esperados
reverseString("Hello") // "olleH"
reverseString("Go语言") // "言语oG"
Exercício 2: Intermediário — Conversão CamelCase e Snake_case
Escreva duas funções:
camelToSnake(s string) string: Converter camelCase para snake_casesnakeToCamel(s string) string: Converter snake_case para camelCase
Dica: Use unicode.IsUpper para detectar posições de letras maiúsculas.
// Resultados esperados
camelToSnake("helloWorld") // "hello_world"
camelToSnake("HTTPResponse") // "http_response"
snakeToCamel("hello_world") // "helloWorld"
snakeToCamel("http_response") // "httpResponse"
Exercício 3: Desafio — Extrator Simples de Cabeçalhos Markdown
Escreva uma função extractHeadings(md string) []string que extrai todos os cabeçalhos de um documento Markdown.
Dica: Cabeçalhos começam com #, e o número de símbolos # indica o nível do cabeçalho.
// Entrada
md := `# Heading 1
Este é o texto do corpo
## Heading 2
### Heading 3
## Outro Heading 2`
// Saída esperada
// ["# Heading 1", "## Heading 2", "### Heading 3", "## Outro Heading 2"]
Próxima Lição
Parabéns por completar o processamento de strings! Na próxima lição, aprenderemos sobre Operações de E/S de Arquivos — como ler e escrever arquivos, manipular diretórios e usar E/S em buffer para melhor desempenho.



